Spaces:
Running
Running
<script> | |
// scripts/app.js | |
import { initUI } from './ui.js'; | |
import { initChat } from './chat.js'; | |
import { initVideo } from './video.js'; | |
document.addEventListener('DOMContentLoaded', () => { | |
// To handle dependencies between modules without a complex event system, | |
// we can expose some functions globally. This is a pragmatic approach for this stage. | |
window.showNotification = (title, message) => { | |
// A simplified version for video.js to call | |
const notification = document.getElementById('notification'); | |
document.getElementById('notification-title').textContent = title; | |
document.getElementById('notification-message').textContent = message; | |
notification.classList.remove('hidden'); | |
notification.classList.add('show'); | |
setTimeout(() => notification.classList.remove('show'), 3000); | |
}; | |
window.loadRecommendations = (filter) => { | |
// A simplified version for chat.js to call | |
// In a real app, ui.js would export loadRecommendations for chat.js to import. | |
const container = document.getElementById('recommendations-container'); | |
// ... Add logic to load recommendations based on filter | |
}; | |
// Initialize all modules | |
initUI(); | |
initChat(); | |
initVideo(); | |
console.log("StreamAI application initialized."); | |
}); | |
// Configuration for Cloudflare Workers AI | |
const AI_CONFIG = { | |
accountId: 'oFD0IMs0aV8eKMMMdTEF2zRQmtzvKMH43LX5ZWUJ', | |
gatewayId: 'streamai_gateway', | |
apiToken: 'masked_for_security', // In a real app, this would be handled server-side | |
model: '@cf/meta/llama-2-7b-chat-int8' | |
}; | |
// Sample streaming data with SMPlus VHX added to the top | |
const streamingData = [ | |
{ | |
title: "SMPlus Exclusive Series", | |
type: "TV Series", | |
genre: "Drama, Action", | |
platform: "SMPlus VHX", | |
rating: "4.9", | |
year: "2023", | |
description: "An exclusive action-packed drama series only available on SMPlus VHX.", | |
broadcastTime: "2023-12-15T20:00:00" | |
}, | |
{ | |
title: "The Grand Adventure", | |
type: "Movie", | |
genre: "Adventure, Comedy", | |
platform: "Netflix", | |
rating: "4.8", | |
year: "2022", | |
description: "A hilarious journey across continents with unexpected twists.", | |
broadcastTime: "2023-12-10T19:30:00" | |
}, | |
{ | |
title: "Dark Secrets", | |
type: "TV Series", | |
genre: "Drama, Thriller", | |
platform: "HBO Max", | |
rating: "4.7", | |
year: "2021", | |
description: "A small town's dark past resurfaces with shocking revelations.", | |
broadcastTime: "2023-12-12T21:00:00" | |
}, | |
{ | |
title: "Space Explorers", | |
type: "Documentary", | |
genre: "Science, Space", | |
platform: "Disney+", | |
rating: "4.9", | |
year: "2023", | |
description: "The latest discoveries from the frontiers of space exploration.", | |
broadcastTime: "2023-12-14T18:00:00" | |
}, | |
{ | |
title: "Romantic Getaway", | |
type: "Movie", | |
genre: "Romance, Comedy", | |
platform: "Amazon Prime", | |
rating: "4.5", | |
year: "2021", | |
description: "Two strangers find love during an unexpected vacation.", | |
broadcastTime: "2023-12-16T20:30:00" | |
}, | |
{ | |
title: "Tech Today", | |
type: "News Show", | |
genre: "Technology, News", | |
platform: "SMPlus VHX", | |
rating: "4.6", | |
year: "2023", | |
description: "Daily tech news and gadget reviews from around the world.", | |
broadcastTime: "2023-12-17T09:00:00" | |
}, | |
{ | |
title: "Cooking Masters", | |
type: "Reality Show", | |
genre: "Food, Competition", | |
platform: "Netflix", | |
rating: "4.7", | |
year: "2023", | |
description: "Top chefs compete in intense culinary challenges.", | |
broadcastTime: "2023-12-18T20:00:00" | |
}, | |
{ | |
title: "History Unearthed", | |
type: "Documentary", | |
genre: "History, Education", | |
platform: "HBO Max", | |
rating: "4.8", | |
year: "2023", | |
description: "Fascinating historical discoveries and their modern implications.", | |
broadcastTime: "2023-12-19T21:00:00" | |
} | |
]; | |
// Sample RSS feed data | |
const rssFeedData = { | |
all: [ | |
{ | |
title: "New Episode: SMPlus Exclusive Series", | |
source: "SMPlus VHX", | |
time: "2 hours ago", | |
excerpt: "The latest episode of our exclusive series is now streaming with intense action scenes.", | |
category: "personalized" | |
}, | |
{ | |
title: "Trending: The Grand Adventure hits #1", | |
source: "Netflix", | |
time: "5 hours ago", | |
excerpt: "The comedy adventure movie is now the most-watched title on Netflix this week.", | |
category: "trending" | |
}, | |
{ | |
title: "Breaking: New streaming partnership announced", | |
source: "Streaming News", | |
time: "1 day ago", | |
excerpt: "Major platforms announce new content sharing agreement starting next month.", | |
category: "news" | |
}, | |
{ | |
title: "Recommended for you: Space Explorers", | |
source: "Disney+", | |
time: "1 day ago", | |
excerpt: "Based on your interest in science documentaries, we recommend this new series.", | |
category: "personalized" | |
}, | |
{ | |
title: "Upcoming: Romantic Getaway special event", | |
source: "Amazon Prime", | |
time: "2 days ago", | |
excerpt: "Join the cast for a live Q&A before the movie premiere this weekend.", | |
category: "trending" | |
} | |
], | |
news: [ | |
{ | |
title: "Breaking: New streaming partnership announced", | |
source: "Streaming News", | |
time: "1 day ago", | |
excerpt: "Major platforms announce new content sharing agreement starting next month.", | |
category: "news" | |
}, | |
{ | |
title: "Streaming industry report Q4 2023", | |
source: "Tech Insights", | |
time: "3 days ago", | |
excerpt: "Latest statistics show continued growth in streaming subscriptions worldwide.", | |
category: "news" | |
} | |
], | |
trending: [ | |
{ | |
title: "Trending: The Grand Adventure hits #1", | |
source: "Netflix", | |
time: "5 hours ago", | |
excerpt: "The comedy adventure movie is now the most-watched title on Netflix this week.", | |
category: "trending" | |
}, | |
{ | |
title: "Upcoming: Romantic Getaway special event", | |
source: "Amazon Prime", | |
time: "2 days ago", | |
excerpt: "Join the cast for a live Q&A before the movie premiere this weekend.", | |
category: "trending" | |
} | |
], | |
personalized: [ | |
{ | |
title: "New Episode: SMPlus Exclusive Series", | |
source: "SMPlus VHX", | |
time: "2 hours ago", | |
excerpt: "The latest episode of our exclusive series is now streaming with intense action scenes.", | |
category: "personalized" | |
}, | |
{ | |
title: "Recommended for you: Space Explorers", | |
source: "Disney+", | |
time: "1 day ago", | |
excerpt: "Based on your interest in science documentaries, we recommend this new series.", | |
category: "personalized" | |
} | |
] | |
}; | |
// Video production variables | |
let isRecording = false; | |
let recordingInterval; | |
let recordedClips = []; | |
let mediaRecorder; | |
let audioContext; | |
let audioStream; | |
let videoStream; | |
let currentClipTime = 0; | |
let currentClipInterval; | |
// Bluetooth sharing variables | |
let isBluetoothConnected = false; | |
let bluetoothDevice; | |
let bluetoothServer; | |
let bluetoothService; | |
let bluetoothCharacteristic; | |
let sharedClips = []; | |
let groupMembers = []; | |
let username = "User" + Math.floor(Math.random() * 1000); | |
let currentCategory = "general"; | |
// Initialize chat | |
document.addEventListener('DOMContentLoaded', function() { | |
const sendBtn = document.getElementById('send-btn'); | |
const userInput = document.getElementById('user-input'); | |
const chatMessages = document.getElementById('chat-messages'); | |
const productionButton = document.getElementById('production-button'); | |
const productionPanel = document.getElementById('production-panel'); | |
const startRecordingBtn = document.getElementById('start-recording'); | |
const stopRecordingBtn = document.getElementById('stop-recording'); | |
const generateVideoBtn = document.getElementById('generate-video'); | |
const clearClipsBtn = document.getElementById('clear-clips'); | |
const rssFilterBtns = document.querySelectorAll('.rss-filter-btn'); | |
const recordTab = document.getElementById('record-tab'); | |
const editTab = document.getElementById('edit-tab'); | |
const shareTab = document.getElementById('share-tab'); | |
const rankTab = document.getElementById('rank-tab'); | |
const recordSection = document.getElementById('record-section'); | |
const editSection = document.getElementById('edit-section'); | |
const shareSection = document.getElementById('share-section'); | |
const rankSection = document.getElementById('rank-section'); | |
const bluetoothConnectBtn = document.getElementById('bluetooth-connect'); | |
const shareClipsBtn = document.getElementById('share-clips'); | |
const usernameInput = document.getElementById('username'); | |
const contentCategorySelect = document.getElementById('content-category'); | |
const rankingAlgorithmSelect = document.getElementById('ranking-algorithm'); | |
// Set default username | |
usernameInput.value = username; | |
// Load sample recommendations | |
loadRecommendations(); | |
// Load RSS feed | |
loadRSSFeed('all'); | |
// Send message on button click | |
sendBtn.addEventListener('click', sendMessage); | |
// Send message on Enter key | |
userInput.addEventListener('keypress', function(e) { | |
if (e.key === 'Enter') { | |
sendMessage(); | |
} | |
}); | |
// Toggle production panel | |
productionButton.addEventListener('click', function() { | |
productionPanel.classList.toggle('open'); | |
}); | |
// Start recording | |
startRecordingBtn.addEventListener('click', startRecording); | |
// Stop recording | |
stopRecordingBtn.addEventListener('click', stopRecording); | |
// Generate video | |
generateVideoBtn.addEventListener('click', generateShortVideo); | |
// Clear clips | |
clearClipsBtn.addEventListener('click', clearClips); | |
// Filter RSS feed | |
rssFilterBtns.forEach(btn => { | |
btn.addEventListener('click', function() { | |
// Update active button | |
rssFilterBtns.forEach(b => { | |
b.classList.remove('bg-indigo-100', 'text-indigo-700'); | |
b.classList.add('bg-gray-100', 'text-gray-700'); | |
}); | |
this.classList.remove('bg-gray-100', 'text-gray-700'); | |
this.classList.add('bg-indigo-100', 'text-indigo-700'); | |
// Load filtered feed | |
loadRSSFeed(this.dataset.filter); | |
}); | |
}); | |
// Switch between record and edit tabs | |
recordTab.addEventListener('click', function() { | |
recordTab.classList.remove('inactive'); | |
recordTab.classList.add('active'); | |
editTab.classList.remove('active'); | |
editTab.classList.add('inactive'); | |
shareTab.classList.remove('active'); | |
shareTab.classList.add('inactive'); | |
rankTab.classList.remove('active'); | |
rankTab.classList.add('inactive'); | |
recordSection.classList.remove('hidden'); | |
editSection.classList.add('hidden'); | |
shareSection.classList.add('hidden'); | |
rankSection.classList.add('hidden'); | |
}); | |
editTab.addEventListener('click', function() { | |
if (recordedClips.length === 0) { | |
showNotification("No Clips", "Record some clips first to edit them"); | |
return; | |
} | |
editTab.classList.remove('inactive'); | |
editTab.classList.add('active'); | |
recordTab.classList.remove('active'); | |
recordTab.classList.add('inactive'); | |
shareTab.classList.remove('active'); | |
shareTab.classList.add('inactive'); | |
rankTab.classList.remove('active'); | |
rankTab.classList.add('inactive'); | |
recordSection.classList.add('hidden'); | |
editSection.classList.remove('hidden'); | |
shareSection.classList.add('hidden'); | |
rankSection.classList.add('hidden'); | |
}); | |
shareTab.addEventListener('click', function() { | |
shareTab.classList.remove('inactive'); | |
shareTab.classList.add('active'); | |
recordTab.classList.remove('active'); | |
recordTab.classList.add('inactive'); | |
editTab.classList.remove('active'); | |
editTab.classList.add('inactive'); | |
rankTab.classList.remove('active'); | |
rankTab.classList.add('inactive'); | |
recordSection.classList.add('hidden'); | |
editSection.classList.add('hidden'); | |
shareSection.classList.remove('hidden'); | |
rankSection.classList.add('hidden'); | |
}); | |
rankTab.addEventListener('click', function() { | |
if (sharedClips.length === 0) { | |
showNotification("No Shared Clips", "Share some clips first to see rankings"); | |
return; | |
} | |
rankTab.classList.remove('inactive'); | |
rankTab.classList.add('active'); | |
recordTab.classList.remove('active'); | |
recordTab.classList.add('inactive'); | |
editTab.classList.remove('active'); | |
editTab.classList.add('inactive'); | |
shareTab.classList.remove('active'); | |
shareTab.classList.add('inactive'); | |
recordSection.classList.add('hidden'); | |
editSection.classList.add('hidden'); | |
shareSection.classList.add('hidden'); | |
rankSection.classList.remove('hidden'); | |
// Update rankings when tab is opened | |
updateRankings(); | |
}); | |
// Connect to Bluetooth | |
bluetoothConnectBtn.addEventListener('click', connectBluetooth); | |
// Share clips | |
shareClipsBtn.addEventListener('click', shareClipsWithGroup); | |
// Update username when changed | |
usernameInput.addEventListener('change', function() { | |
username = this.value || "User" + Math.floor(Math.random() * 1000); | |
}); | |
// Update category when changed | |
contentCategorySelect.addEventListener('change', function() { | |
currentCategory = this.value; | |
}); | |
// Update rankings when algorithm changes | |
rankingAlgorithmSelect.addEventListener('change', updateRankings); | |
}); | |
// Show notification | |
function showNotification(title, message) { | |
const notification = document.getElementById('notification'); | |
const titleElement = document.getElementById('notification-title'); | |
const messageElement = document.getElementById('notification-message'); | |
titleElement.textContent = title; | |
messageElement.textContent = message; | |
notification.classList.remove('hidden'); | |
notification.classList.add('show'); | |
setTimeout(() => { | |
notification.classList.remove('show'); | |
setTimeout(() => notification.classList.add('hidden'), 300); | |
}, 3000); | |
} | |
// Quick prompt buttons | |
function quickPrompt(prompt) { | |
document.getElementById('user-input').value = prompt; | |
sendMessage(); | |
} | |
// Send message to AI | |
async function sendMessage() { | |
const userInput = document.getElementById('user-input'); | |
const chatMessages = document.getElementById('chat-messages'); | |
if (userInput.value.trim() === '') return; | |
// Add user message to chat | |
const userMessage = document.createElement('div'); | |
userMessage.className = 'chat-bubble user-bubble p-4 w-3/4 ml-auto mb-4 fade-in'; | |
userMessage.innerHTML = `<p>${userInput.value}</p>`; | |
chatMessages.appendChild(userMessage); | |
// Show typing indicator | |
const typingIndicator = document.createElement('div'); | |
typingIndicator.className = 'chat-bubble ai-bubble p-4 w-1/2 mb-4'; | |
typingIndicator.innerHTML = '<div class="typing-indicator"><span></span><span></span><span></span></div>'; | |
chatMessages.appendChild(typingIndicator); | |
// Scroll to bottom | |
chatMessages.scrollTop = chatMessages.scrollHeight; | |
// Save user message | |
const userMessageText = userInput.value; | |
userInput.value = ''; | |
try { | |
// Call Cloudflare Workers AI | |
const aiResponse = await queryCloudflareAI(userMessageText); | |
// Remove typing indicator | |
chatMessages.removeChild(typingIndicator); | |
// Add AI response to chat | |
const aiMessage = document.createElement('div'); | |
aiMessage.className = 'chat-bubble ai-bubble p-4 w-3/4 fade-in'; | |
aiMessage.innerHTML = `<p>${aiResponse}</p>`; | |
chatMessages.appendChild(aiMessage); | |
// Update recommendations based on AI response | |
updateRecommendationsFromAI(aiResponse); | |
} catch (error) { | |
// Remove typing indicator | |
chatMessages.removeChild(typingIndicator); | |
// Show error message | |
const errorMessage = document.createElement('div'); | |
errorMessage.className = 'chat-bubble ai-bubble p-4 w-3/4 fade-in'; | |
errorMessage.innerHTML = `<p class="text-red-500">Sorry, I'm having trouble connecting to the AI service. Please try again later.</p>`; | |
chatMessages.appendChild(errorMessage); | |
} | |
// Scroll to bottom | |
chatMessages.scrollTop = chatMessages.scrollHeight; | |
} | |
// Query Cloudflare Workers AI | |
async function queryCloudflareAI(prompt) { | |
// In a production environment, this would be handled by a backend service | |
// to keep the API token secure. For this demo, we'll simulate the response. | |
console.log(`[DEBUG] Would call Cloudflare AI with prompt: "${prompt}"`); | |
// Simulate API call delay | |
await new Promise(resolve => setTimeout(resolve, 1500)); | |
// Simulate different responses based on prompt | |
const lowerPrompt = prompt.toLowerCase(); | |
if (lowerPrompt.includes('comedy') || lowerPrompt.includes('funny')) { | |
return "I'd recommend these comedy options that should give you a good laugh:\n\n1. 'The Grand Adventure' (Netflix) - A hilarious journey with unexpected twists\n2. 'Office Shenanigans' (Hulu) - Workplace comedy at its finest\n\nComedy can really lift your mood! Would you like more suggestions?"; | |
} | |
else if (lowerPrompt.includes('thriller') || lowerPrompt.includes('suspense')) { | |
return "For thrilling content that will keep you on the edge of your seat, consider:\n\n1. 'Dark Secrets' (HBO Max) - A town's dark past resurfaces\n2. 'Midnight Caller' (Amazon Prime) - A psychological thriller about a mysterious phone call\n\nThese should provide plenty of suspense!"; | |
} | |
else if (lowerPrompt.includes('romance') || lowerPrompt.includes('love')) { | |
return "Romantic stories can be so heartwarming! Here are my top picks:\n\n1. 'Romantic Getaway' (Amazon Prime) - Two strangers find love on vacation\n2. 'Love in Paris' (Netflix) - A classic romantic tale set in the City of Love\n\nLet me know if you'd like something more specific!"; | |
} | |
else if (lowerPrompt.includes('recommend') || lowerPrompt.includes('suggest')) { | |
return "Based on your request, I'd recommend these excellent streaming options:\n\n1. 'SMPlus Exclusive Series' (SMPlus VHX) - Action-packed drama series\n2. 'Space Explorers' (Disney+) - Fascinating documentary about space\n\nI've updated the recommendations section with more options for you!"; | |
} | |
else { | |
return "I'm here to help you find the perfect streaming content! Could you tell me more about what you're looking for? For example, you could say 'recommend a sci-fi movie' or 'what should I watch if I feel like laughing?'"; | |
} | |
} | |
// Update recommendations based on AI response | |
function updateRecommendationsFromAI(aiResponse) { | |
let filter = 'all'; | |
if (aiResponse.includes('comedy')) { | |
filter = 'comedy'; | |
} else if (aiResponse.includes('thriller') || aiResponse.includes('suspense')) { | |
filter = 'thriller'; | |
} else if (aiResponse.includes('romance') || aiResponse.includes('love')) { | |
filter = 'romance'; | |
} | |
loadRecommendations(filter); | |
} | |
// Save show and set broadcast reminder | |
function saveShow(title, broadcastTime) { | |
// In a real app, this would save to a database | |
console.log(`Saved show: ${title}`); | |
// Show notification | |
showNotification("Reminder Set!", `We'll notify you when "${title}" is about to broadcast.`); | |
// In a real app, you would schedule a notification for the broadcast time | |
if (broadcastTime) { | |
const broadcastDate = new Date(broadcastTime); | |
const now = new Date(); | |
// Only schedule if broadcast is in the future | |
if (broadcastDate > now) { | |
const timeUntilBroadcast = broadcastDate - now; | |
// Schedule notification 30 minutes before broadcast | |
setTimeout(() => { | |
showNotification("Starting Soon!", `"${title}" will begin broadcasting in 30 minutes!`); | |
}, timeUntilBroadcast - (30 * 60 * 1000)); | |
} | |
} | |
} | |
// Load recommendations | |
function loadRecommendations(filter = 'all') { | |
const container = document.getElementById('recommendations-container'); | |
container.innerHTML = ''; | |
let filteredData = streamingData; | |
if (filter === 'comedy') { | |
filteredData = streamingData.filter(item => item.genre.toLowerCase().includes('comedy')); | |
} else if (filter === 'thriller') { | |
filteredData = streamingData.filter(item => item.genre.toLowerCase().includes('thriller')); | |
} else if (filter === 'romance') { | |
filteredData = streamingData.filter(item => item.genre.toLowerCase().includes('romance')); | |
} | |
filteredData.forEach(item => { | |
const card = document.createElement('div'); | |
card.className = 'stream-card bg-white rounded-lg overflow-hidden shadow-md hover:shadow-xl transition duration-300 fade-in p-4'; | |
card.innerHTML = ` | |
<div class="mb-3"> | |
<div class="flex justify-between items-start"> | |
<h3 class="font-bold text-base">${item.title}</h3> | |
<span class="bg-yellow-100 text-yellow-800 text-xs px-2 py-1 rounded-full flex items-center"> | |
<i class="fas fa-star text-yellow-500 mr-1 text-xs"></i> ${item.rating} | |
</span> | |
</div> | |
<p class="text-gray-600 text-xs mb-1">${item.type} • ${item.genre} • ${item.year}</p> | |
<div class="text-xs text-indigo-600 mb-2">${item.platform}</div> | |
</div> | |
<p class="text-gray-700 text-sm mb-4">${item.description}</p> | |
<div class="flex justify-between items-center"> | |
<button class="text-indigo-600 hover:text-indigo-800 text-xs font-medium" onclick="saveShow('${item.title}', '${item.broadcastTime}')"> | |
<i class="far fa-bookmark mr-1"></i> Save | |
</button> | |
<a href="${item.platform === 'SMPlus VHX' ? 'https://smplus.vhx.tv' : '#'}" target="_blank" class="bg-indigo-600 hover:bg-indigo-700 text-white px-3 py-1 rounded-full text-xs font-medium transition"> | |
<i class="fas fa-play mr-1"></i> Watch | |
</a> | |
</div> | |
`; | |
container.appendChild(card); | |
}); | |
} | |
// Load RSS feed | |
function loadRSSFeed(filter) { | |
const container = document.getElementById('rss-feed'); | |
container.innerHTML = ''; | |
const feedItems = rssFeedData[filter] || rssFeedData.all; | |
feedItems.forEach(item => { | |
const feedItem = document.createElement('div'); | |
feedItem.className = 'rss-item bg-gray-50 p-3 rounded-lg'; | |
feedItem.innerHTML = ` | |
<h4 class="font-medium text-sm mb-1">${item.title}</h4> | |
<div class="flex items-center text-xs text-gray-500 mb-2"> | |
<span>${item.source}</span> | |
<span class="mx-2">•</span> | |
<span>${item.time}</span> | |
</div> | |
<p class="text-xs text-gray-700">${item.excerpt}</p> | |
`; | |
container.appendChild(feedItem); | |
}); | |
} | |
// Start recording video and audio | |
async function startRecording() { | |
try { | |
// Get user media | |
videoStream = await navigator.mediaDevices.getDisplayMedia({ | |
video: true, | |
audio: true | |
}); | |
audioStream = await navigator.mediaDevices.getUserMedia({ | |
audio: true | |
}); | |
// Create audio context | |
audioContext = new AudioContext(); | |
const source = audioContext.createMediaStreamSource(audioStream); | |
const destination = audioContext.createMediaStreamDestination(); | |
source.connect(destination); | |
// Combine video and audio streams | |
const combinedStream = new MediaStream([ | |
...videoStream.getVideoTracks(), | |
...destination.stream.getAudioTracks() | |
]); | |
// Create media recorder | |
mediaRecorder = new MediaRecorder(combinedStream, { | |
mimeType: 'video/webm' | |
}); | |
// Update preview with live recording | |
const preview = document.getElementById('clip-preview'); | |
preview.innerHTML = '<video autoplay muted></video>'; | |
const previewVideo = preview.querySelector('video'); | |
previewVideo.srcObject = combinedStream; | |
// Set recording state | |
isRecording = true; | |
document.getElementById('recording-indicator').classList.remove('hidden'); | |
document.getElementById('start-recording').classList.add('hidden'); | |
document.getElementById('stop-recording').classList.remove('hidden'); | |
// Start progress bar animation | |
currentClipTime = 0; | |
const progressFill = document.getElementById('progress-fill'); | |
progressFill.style.width = '0%'; | |
currentClipInterval = setInterval(() => { | |
currentClipTime += 100; | |
const progressPercent = (currentClipTime / 5000) * 100; | |
progressFill.style.width = `${progressPercent}%`; | |
}, 100); | |
// Start recording in 5-second clips | |
let clipCount = 0; | |
recordingInterval = setInterval(() => { | |
if (clipCount > 0) { | |
// Stop current recording | |
mediaRecorder.stop(); | |
} | |
// Start new recording | |
mediaRecorder.start(); | |
clipCount++; | |
// Reset progress bar | |
currentClipTime = 0; | |
progressFill.style.width = '0%'; | |
// Store clip data when available | |
mediaRecorder.ondataavailable = (e) => { | |
const clip = { | |
blob: e.data, | |
timestamp: new Date().toLocaleTimeString(), | |
url: URL.createObjectURL(e.data), | |
username: username, | |
category: currentCategory, | |
engagementScore: Math.random() * 5, // Simulated metrics | |
qualityScore: 3 + Math.random() * 2, | |
consistencyScore: 3 + Math.random() * 2 | |
}; | |
recordedClips.push(clip); | |
updateClipsList(); | |
}; | |
}, 5000); | |
showNotification("Recording Started", "Recording 5-second clips of your screen and audio"); | |
} catch (error) { | |
console.error("Error starting recording:", error); | |
showNotification("Recording Error", "Could not start recording. Please check permissions."); | |
stopRecording(); | |
} | |
} | |
// Stop recording | |
function stopRecording() { | |
if (mediaRecorder && mediaRecorder.state !== 'inactive') { | |
mediaRecorder.stop(); | |
} | |
clearInterval(recordingInterval); | |
clearInterval(currentClipInterval); | |
isRecording = false; | |
// Stop all tracks | |
if (videoStream) { | |
videoStream.getTracks().forEach(track => track.stop()); | |
} | |
if (audioStream) { | |
audioStream.getTracks().forEach(track => track.stop()); | |
} | |
// Close audio context | |
if (audioContext && audioContext.state !== 'closed') { | |
audioContext.close(); | |
} | |
// Reset preview | |
const preview = document.getElementById('clip-preview'); | |
preview.innerHTML = ` | |
<div class="clip-preview-placeholder"> | |
<i class="fas fa-video"></i> | |
<p>Preview will appear here</p> | |
</div> | |
`; | |
// Reset progress bar | |
document.getElementById('progress-fill').style.width = '0%'; | |
// Update UI | |
document.getElementById('recording-indicator').classList.add('hidden'); | |
document.getElementById('start-recording').classList.remove('hidden'); | |
document.getElementById('stop-recording').classList.add('hidden'); | |
showNotification("Recording Stopped", `Captured ${recordedClips.length} clips ready for production`); | |
} | |
// Update clips list | |
function updateClipsList() { | |
const container = document.getElementById('clips-container'); | |
container.innerHTML = ''; | |
if (recordedClips.length === 0) { | |
container.innerHTML = '<p class="text-gray-500 text-sm">Clips will appear here...</p>'; | |
return; | |
} | |
recordedClips.forEach((clip, index) => { | |
const clipItem = document.createElement('div'); | |
clipItem.className = 'clip-item'; | |
clipItem.innerHTML = ` | |
<i class="fas fa-video text-gray-500 mr-2"></i> | |
<span class="text-sm flex-1">Clip ${index + 1}</span> | |
<span class="text-xs text-gray-500">${clip.timestamp}</span> | |
`; | |
// Add click handler to preview clip | |
clipItem.addEventListener('click', () => { | |
const preview = document.getElementById('clip-preview'); | |
preview.innerHTML = '<video controls></video>'; | |
const previewVideo = preview.querySelector('video'); | |
previewVideo.src = clip.url; | |
}); | |
container.appendChild(clipItem); | |
}); | |
} | |
// Clear all clips | |
function clearClips() { | |
if (recordedClips.length === 0) return; | |
// In a real app, we would properly revoke the object URLs | |
recordedClips.forEach(clip => { | |
if (clip.url) { | |
URL.revokeObjectURL(clip.url); | |
} | |
}); | |
recordedClips = []; | |
updateClipsList(); | |
// Reset preview | |
const preview = document.getElementById('clip-preview'); | |
preview.innerHTML = ` | |
<div class="clip-preview-placeholder"> | |
<i class="fas fa-video"></i> | |
<p>Preview will appear here</p> | |
</div> | |
`; | |
showNotification("Clips Cleared", "All recorded clips have been removed"); | |
} | |
// Generate short video from clips | |
function generateShortVideo() { | |
if (recordedClips.length === 0) { | |
showNotification("No Clips", "Please record some clips first"); | |
return; | |
} | |
// In a real app, this would use a video editing library to combine clips | |
// and sync with audio. For this demo, we'll simulate the process. | |
showNotification("Video Generation", "Processing your clips into a short video..."); | |
setTimeout(() => { | |
// Simulate processing time | |
const videoUrl = URL.createObjectURL(new Blob(["Simulated video content"], { type: 'video/mp4' })); | |
// Create download link | |
const a = document.createElement('a'); | |
a.href = videoUrl; | |
a.download = 'streamai-short.mp4'; | |
a.click(); | |
showNotification("Video Ready", "Your short video has been generated and downloaded"); | |
}, 3000); | |
} | |
// Connect to Bluetooth devices | |
async function connectBluetooth() { | |
try { | |
// Request Bluetooth device | |
bluetoothDevice = await navigator.bluetooth.requestDevice({ | |
acceptAllDevices: true, | |
optionalServices: ['generic_access'] | |
}); | |
// Connect to the GATT Server | |
bluetoothServer = await bluetoothDevice.gatt.connect(); | |
// Get the service | |
bluetoothService = await bluetoothServer.getPrimaryService('generic_access'); | |
// Get the characteristic | |
bluetoothCharacteristic = await bluetoothService.getCharacteristic('device_name'); | |
// Update connection status | |
isBluetoothConnected = true; | |
const statusElement = document.getElementById('connection-status'); | |
statusElement.innerHTML = ` | |
<i class="fas fa-circle bluetooth-connected mr-1"></i> | |
<span>Connected to ${bluetoothDevice.name || 'device'}</span> | |
`; | |
// Simulate discovering group members | |
groupMembers = [ | |
{ name: "User" + Math.floor(Math.random() * 1000), device: "Device 1" }, | |
{ name: "User" + Math.floor(Math.random() * 1000), device: "Device 2" }, | |
{ name: "User" + Math.floor(Math.random() * 1000), device: "Device 3" } | |
]; | |
showNotification("Bluetooth Connected", `Connected to ${bluetoothDevice.name}. Found ${groupMembers.length} group members.`); | |
} catch (error) { | |
console.error("Bluetooth connection error:", error); | |
isBluetoothConnected = false; | |
document.getElementById('connection-status').innerHTML = ` | |
<i class="fas fa-circle bluetooth-disconnected mr-1"></i> | |
<span>Not connected</span> | |
`; | |
showNotification("Connection Failed", "Could not connect to Bluetooth device"); | |
} | |
} | |
// Share clips with Bluetooth group | |
async function shareClipsWithGroup() { | |
if (!isBluetoothConnected) { | |
showNotification("Not Connected", "Please connect to Bluetooth first"); | |
return; | |
} | |
if (recordedClips.length === 0) { | |
showNotification("No Clips", "Please record some clips first"); | |
return; | |
} | |
// In a real app, this would actually send the clips over Bluetooth | |
// For this demo, we'll simulate the sharing process | |
showNotification("Sharing Clips", "Sharing your clips with the group..."); | |
// Simulate sharing delay | |
setTimeout(() => { | |
// Add metadata to clips and add to shared clips | |
recordedClips.forEach(clip => { | |
const sharedClip = { | |
...clip, | |
sharedBy: username, | |
category: currentCategory, | |
timestamp: new Date().toLocaleTimeString(), | |
likes: Math.floor(Math.random() * 10), | |
views: Math.floor(Math.random() * 50) | |
}; | |
sharedClips.push(sharedClip); | |
}); | |
// Update shared clips display | |
updateSharedClipsList(); | |
showNotification("Clips Shared", `Shared ${recordedClips.length} clips with ${groupMembers.length} group members`); | |
}, 2000); | |
} | |
// Update shared clips list | |
function updateSharedClipsList() { | |
const container = document.getElementById('shared-clips-container'); | |
container.innerHTML = ''; | |
if (sharedClips.length === 0) { | |
container.innerHTML = '<p class="text-gray-500 text-sm">Shared clips will appear here...</p>'; | |
return; | |
} | |
sharedClips.forEach((clip, index) => { | |
const clipItem = document.createElement('div'); | |
clipItem.className = 'clip-item'; | |
clipItem.innerHTML = ` | |
<i class="fas fa-video text-gray-500 mr-2"></i> | |
<span class="text-sm flex-1">Clip ${index + 1}</span> | |
<span class="user-tag">${clip.sharedBy}</span> | |
<span class="category-tag">${clip.category}</span> | |
`; | |
// Add click handler to preview clip | |
clipItem.addEventListener('click', () => { | |
const preview = document.getElementById('clip-preview'); | |
preview.innerHTML = '<video controls></video>'; | |
const previewVideo = preview.querySelector('video'); | |
previewVideo.src = clip.url; | |
}); | |
container.appendChild(clipItem); | |
}); | |
} | |
// Benchmarking algorithm to rank clips | |
function benchmarkClips() { | |
if (sharedClips.length === 0) return []; | |
// Calculate scores for each clip | |
sharedClips.forEach(clip => { | |
// Composite score is weighted average of all metrics | |
clip.compositeScore = ( | |
clip.engagementScore * 0.4 + | |
clip.qualityScore * 0.3 + | |
clip.consistencyScore * 0.3 | |
); | |
// Add some randomness to simulate different algorithms | |
clip.engagementScore = Math.min(5, clip.engagementScore + Math.random() * 0.5 - 0.25); | |
clip.qualityScore = Math.min(5, clip.qualityScore + Math.random() * 0.5 - 0.25); | |
clip.consistencyScore = Math.min(5, clip.consistencyScore + Math.random() * 0.5 - 0.25); | |
}); | |
// Sort based on selected algorithm | |
const algorithm = document.getElementById('ranking-algorithm').value; | |
let sortedClips = [...sharedClips]; | |
switch (algorithm) { | |
case 'engagement': | |
sortedClips.sort((a, b) => b.engagementScore - a.engagementScore); | |
break; | |
case 'quality': | |
sortedClips.sort((a, b) => b.qualityScore - a.qualityScore); | |
break; | |
case 'consistency': | |
sortedClips.sort((a, b) => b.consistencyScore - a.consistencyScore); | |
break; | |
case 'composite': | |
default: | |
sortedClips.sort((a, b) => b.compositeScore - a.compositeScore); | |
break; | |
} | |
return sortedClips; | |
} | |
// Update rankings display | |
function updateRankings() { | |
const container = document.getElementById('rankings-container'); | |
container.innerHTML = ''; | |
const rankedClips = benchmarkClips(); | |
if (rankedClips.length === 0) { | |
container.innerHTML = '<p class="text-gray-500 text-sm">Rankings will appear here when available...</p>'; | |
return; | |
} | |
</script> |