privateuserh commited on
Commit
089164a
·
verified ·
1 Parent(s): bdecbaf

Delete scripts/app.js

Browse files
Files changed (1) hide show
  1. scripts/app.js +0 -1018
scripts/app.js DELETED
@@ -1,1018 +0,0 @@
1
- // scripts/app.js
2
-
3
- import { initUI } from './ui.js';
4
- import { initChat } from './chat.js';
5
- import { initVideo } from './video.js';
6
-
7
- document.addEventListener('DOMContentLoaded', () => {
8
- // To handle dependencies between modules without a complex event system,
9
- // we can expose some functions globally. This is a pragmatic approach for this stage.
10
- window.showNotification = (title, message) => {
11
- // A simplified version for video.js to call
12
- const notification = document.getElementById('notification');
13
- document.getElementById('notification-title').textContent = title;
14
- document.getElementById('notification-message').textContent = message;
15
- notification.classList.remove('hidden');
16
- notification.classList.add('show');
17
- setTimeout(() => notification.classList.remove('show'), 3000);
18
- };
19
-
20
- window.loadRecommendations = (filter) => {
21
- // A simplified version for chat.js to call
22
- // In a real app, ui.js would export loadRecommendations for chat.js to import.
23
- const container = document.getElementById('recommendations-container');
24
- // ... Add logic to load recommendations based on filter
25
- };
26
-
27
-
28
- // Initialize all modules
29
- initUI();
30
- initChat();
31
- initVideo();
32
-
33
- console.log("StreamAI application initialized.");
34
- });
35
- // Configuration for Cloudflare Workers AI
36
- const AI_CONFIG = {
37
- accountId: 'oFD0IMs0aV8eKMMMdTEF2zRQmtzvKMH43LX5ZWUJ',
38
- gatewayId: 'streamai_gateway',
39
- apiToken: 'masked_for_security', // In a real app, this would be handled server-side
40
- model: '@cf/meta/llama-2-7b-chat-int8'
41
- };
42
-
43
-
44
- // Sample streaming data with SMPlus VHX added to the top
45
- const streamingData = [
46
- {
47
- title: "SMPlus Exclusive Series",
48
- type: "TV Series",
49
- genre: "Drama, Action",
50
- platform: "SMPlus VHX",
51
- rating: "4.9",
52
- year: "2023",
53
- description: "An exclusive action-packed drama series only available on SMPlus VHX.",
54
- broadcastTime: "2023-12-15T20:00:00"
55
- },
56
- {
57
- title: "The Grand Adventure",
58
- type: "Movie",
59
- genre: "Adventure, Comedy",
60
- platform: "Netflix",
61
- rating: "4.8",
62
- year: "2022",
63
- description: "A hilarious journey across continents with unexpected twists.",
64
- broadcastTime: "2023-12-10T19:30:00"
65
- },
66
- {
67
- title: "Dark Secrets",
68
- type: "TV Series",
69
- genre: "Drama, Thriller",
70
- platform: "HBO Max",
71
- rating: "4.7",
72
- year: "2021",
73
- description: "A small town's dark past resurfaces with shocking revelations.",
74
- broadcastTime: "2023-12-12T21:00:00"
75
- },
76
- {
77
- title: "Space Explorers",
78
- type: "Documentary",
79
- genre: "Science, Space",
80
- platform: "Disney+",
81
- rating: "4.9",
82
- year: "2023",
83
- description: "The latest discoveries from the frontiers of space exploration.",
84
- broadcastTime: "2023-12-14T18:00:00"
85
- },
86
- {
87
- title: "Romantic Getaway",
88
- type: "Movie",
89
- genre: "Romance, Comedy",
90
- platform: "Amazon Prime",
91
- rating: "4.5",
92
- year: "2021",
93
- description: "Two strangers find love during an unexpected vacation.",
94
- broadcastTime: "2023-12-16T20:30:00"
95
- },
96
- {
97
- title: "Tech Today",
98
- type: "News Show",
99
- genre: "Technology, News",
100
- platform: "SMPlus VHX",
101
- rating: "4.6",
102
- year: "2023",
103
- description: "Daily tech news and gadget reviews from around the world.",
104
- broadcastTime: "2023-12-17T09:00:00"
105
- },
106
- {
107
- title: "Cooking Masters",
108
- type: "Reality Show",
109
- genre: "Food, Competition",
110
- platform: "Netflix",
111
- rating: "4.7",
112
- year: "2023",
113
- description: "Top chefs compete in intense culinary challenges.",
114
- broadcastTime: "2023-12-18T20:00:00"
115
- },
116
- {
117
- title: "History Unearthed",
118
- type: "Documentary",
119
- genre: "History, Education",
120
- platform: "HBO Max",
121
- rating: "4.8",
122
- year: "2023",
123
- description: "Fascinating historical discoveries and their modern implications.",
124
- broadcastTime: "2023-12-19T21:00:00"
125
- }
126
- ];
127
- // Sample RSS feed data
128
- const rssFeedData = {
129
- all: [
130
- {
131
- title: "New Episode: SMPlus Exclusive Series",
132
- source: "SMPlus VHX",
133
- time: "2 hours ago",
134
- excerpt: "The latest episode of our exclusive series is now streaming with intense action scenes.",
135
- category: "personalized"
136
- },
137
- {
138
- title: "Trending: The Grand Adventure hits #1",
139
- source: "Netflix",
140
- time: "5 hours ago",
141
- excerpt: "The comedy adventure movie is now the most-watched title on Netflix this week.",
142
- category: "trending"
143
- },
144
- {
145
- title: "Breaking: New streaming partnership announced",
146
- source: "Streaming News",
147
- time: "1 day ago",
148
- excerpt: "Major platforms announce new content sharing agreement starting next month.",
149
- category: "news"
150
- },
151
- {
152
- title: "Recommended for you: Space Explorers",
153
- source: "Disney+",
154
- time: "1 day ago",
155
- excerpt: "Based on your interest in science documentaries, we recommend this new series.",
156
- category: "personalized"
157
- },
158
- {
159
- title: "Upcoming: Romantic Getaway special event",
160
- source: "Amazon Prime",
161
- time: "2 days ago",
162
- excerpt: "Join the cast for a live Q&A before the movie premiere this weekend.",
163
- category: "trending"
164
- }
165
- ],
166
- news: [
167
- {
168
- title: "Breaking: New streaming partnership announced",
169
- source: "Streaming News",
170
- time: "1 day ago",
171
- excerpt: "Major platforms announce new content sharing agreement starting next month.",
172
- category: "news"
173
- },
174
- {
175
- title: "Streaming industry report Q4 2023",
176
- source: "Tech Insights",
177
- time: "3 days ago",
178
- excerpt: "Latest statistics show continued growth in streaming subscriptions worldwide.",
179
- category: "news"
180
- }
181
- ],
182
- trending: [
183
- {
184
- title: "Trending: The Grand Adventure hits #1",
185
- source: "Netflix",
186
- time: "5 hours ago",
187
- excerpt: "The comedy adventure movie is now the most-watched title on Netflix this week.",
188
- category: "trending"
189
- },
190
- {
191
- title: "Upcoming: Romantic Getaway special event",
192
- source: "Amazon Prime",
193
- time: "2 days ago",
194
- excerpt: "Join the cast for a live Q&A before the movie premiere this weekend.",
195
- category: "trending"
196
- }
197
- ],
198
- personalized: [
199
- {
200
- title: "New Episode: SMPlus Exclusive Series",
201
- source: "SMPlus VHX",
202
- time: "2 hours ago",
203
- excerpt: "The latest episode of our exclusive series is now streaming with intense action scenes.",
204
- category: "personalized"
205
- },
206
- {
207
- title: "Recommended for you: Space Explorers",
208
- source: "Disney+",
209
- time: "1 day ago",
210
- excerpt: "Based on your interest in science documentaries, we recommend this new series.",
211
- category: "personalized"
212
- }
213
- ]
214
- };
215
- // Video production variables
216
- let isRecording = false;
217
- let recordingInterval;
218
- let recordedClips = [];
219
- let mediaRecorder;
220
- let audioContext;
221
- let audioStream;
222
- let videoStream;
223
- let currentClipTime = 0;
224
- let currentClipInterval;
225
-
226
-
227
- // Bluetooth sharing variables
228
- let isBluetoothConnected = false;
229
- let bluetoothDevice;
230
- let bluetoothServer;
231
- let bluetoothService;
232
- let bluetoothCharacteristic;
233
- let sharedClips = [];
234
- let groupMembers = [];
235
- let username = "User" + Math.floor(Math.random() * 1000);
236
- let currentCategory = "general";
237
-
238
-
239
- // Initialize chat
240
- document.addEventListener('DOMContentLoaded', function() {
241
- const sendBtn = document.getElementById('send-btn');
242
- const userInput = document.getElementById('user-input');
243
- const chatMessages = document.getElementById('chat-messages');
244
- const productionButton = document.getElementById('production-button');
245
- const productionPanel = document.getElementById('production-panel');
246
- const startRecordingBtn = document.getElementById('start-recording');
247
- const stopRecordingBtn = document.getElementById('stop-recording');
248
- const generateVideoBtn = document.getElementById('generate-video');
249
- const clearClipsBtn = document.getElementById('clear-clips');
250
- const rssFilterBtns = document.querySelectorAll('.rss-filter-btn');
251
- const recordTab = document.getElementById('record-tab');
252
- const editTab = document.getElementById('edit-tab');
253
- const shareTab = document.getElementById('share-tab');
254
- const rankTab = document.getElementById('rank-tab');
255
- const recordSection = document.getElementById('record-section');
256
- const editSection = document.getElementById('edit-section');
257
- const shareSection = document.getElementById('share-section');
258
- const rankSection = document.getElementById('rank-section');
259
- const bluetoothConnectBtn = document.getElementById('bluetooth-connect');
260
- const shareClipsBtn = document.getElementById('share-clips');
261
- const usernameInput = document.getElementById('username');
262
- const contentCategorySelect = document.getElementById('content-category');
263
- const rankingAlgorithmSelect = document.getElementById('ranking-algorithm');
264
-
265
- // Set default username
266
- usernameInput.value = username;
267
-
268
- // Load sample recommendations
269
- loadRecommendations();
270
-
271
- // Load RSS feed
272
- loadRSSFeed('all');
273
-
274
- // Send message on button click
275
- sendBtn.addEventListener('click', sendMessage);
276
-
277
- // Send message on Enter key
278
- userInput.addEventListener('keypress', function(e) {
279
- if (e.key === 'Enter') {
280
- sendMessage();
281
- }
282
- });
283
- // Toggle production panel
284
- productionButton.addEventListener('click', function() {
285
- productionPanel.classList.toggle('open');
286
- });
287
-
288
- // Start recording
289
- startRecordingBtn.addEventListener('click', startRecording);
290
-
291
- // Stop recording
292
- stopRecordingBtn.addEventListener('click', stopRecording);
293
-
294
- // Generate video
295
- generateVideoBtn.addEventListener('click', generateShortVideo);
296
-
297
- // Clear clips
298
- clearClipsBtn.addEventListener('click', clearClips);
299
-
300
- // Filter RSS feed
301
- rssFilterBtns.forEach(btn => {
302
- btn.addEventListener('click', function() {
303
- // Update active button
304
- rssFilterBtns.forEach(b => {
305
- b.classList.remove('bg-indigo-100', 'text-indigo-700');
306
- b.classList.add('bg-gray-100', 'text-gray-700');
307
- });
308
- this.classList.remove('bg-gray-100', 'text-gray-700');
309
- this.classList.add('bg-indigo-100', 'text-indigo-700');
310
-
311
- // Load filtered feed
312
- loadRSSFeed(this.dataset.filter);
313
- });
314
- });
315
-
316
- // Switch between record and edit tabs
317
- recordTab.addEventListener('click', function() {
318
- recordTab.classList.remove('inactive');
319
- recordTab.classList.add('active');
320
- editTab.classList.remove('active');
321
- editTab.classList.add('inactive');
322
- shareTab.classList.remove('active');
323
- shareTab.classList.add('inactive');
324
- rankTab.classList.remove('active');
325
- rankTab.classList.add('inactive');
326
- recordSection.classList.remove('hidden');
327
- editSection.classList.add('hidden');
328
- shareSection.classList.add('hidden');
329
- rankSection.classList.add('hidden');
330
- });
331
-
332
- editTab.addEventListener('click', function() {
333
- if (recordedClips.length === 0) {
334
- showNotification("No Clips", "Record some clips first to edit them");
335
- return;
336
- }
337
-
338
- editTab.classList.remove('inactive');
339
- editTab.classList.add('active');
340
- recordTab.classList.remove('active');
341
- recordTab.classList.add('inactive');
342
- shareTab.classList.remove('active');
343
- shareTab.classList.add('inactive');
344
- rankTab.classList.remove('active');
345
- rankTab.classList.add('inactive');
346
- recordSection.classList.add('hidden');
347
- editSection.classList.remove('hidden');
348
- shareSection.classList.add('hidden');
349
- rankSection.classList.add('hidden');
350
- });
351
- shareTab.addEventListener('click', function() {
352
- shareTab.classList.remove('inactive');
353
- shareTab.classList.add('active');
354
- recordTab.classList.remove('active');
355
- recordTab.classList.add('inactive');
356
- editTab.classList.remove('active');
357
- editTab.classList.add('inactive');
358
- rankTab.classList.remove('active');
359
- rankTab.classList.add('inactive');
360
- recordSection.classList.add('hidden');
361
- editSection.classList.add('hidden');
362
- shareSection.classList.remove('hidden');
363
- rankSection.classList.add('hidden');
364
- });
365
-
366
- rankTab.addEventListener('click', function() {
367
- if (sharedClips.length === 0) {
368
- showNotification("No Shared Clips", "Share some clips first to see rankings");
369
- return;
370
- }
371
-
372
- rankTab.classList.remove('inactive');
373
- rankTab.classList.add('active');
374
- recordTab.classList.remove('active');
375
- recordTab.classList.add('inactive');
376
- editTab.classList.remove('active');
377
- editTab.classList.add('inactive');
378
- shareTab.classList.remove('active');
379
- shareTab.classList.add('inactive');
380
- recordSection.classList.add('hidden');
381
- editSection.classList.add('hidden');
382
- shareSection.classList.add('hidden');
383
- rankSection.classList.remove('hidden');
384
-
385
- // Update rankings when tab is opened
386
- updateRankings();
387
- });
388
-
389
- // Connect to Bluetooth
390
- bluetoothConnectBtn.addEventListener('click', connectBluetooth);
391
-
392
- // Share clips
393
- shareClipsBtn.addEventListener('click', shareClipsWithGroup);
394
-
395
- // Update username when changed
396
- usernameInput.addEventListener('change', function() {
397
- username = this.value || "User" + Math.floor(Math.random() * 1000);
398
- });
399
-
400
- // Update category when changed
401
- contentCategorySelect.addEventListener('change', function() {
402
- currentCategory = this.value;
403
- });
404
-
405
- // Update rankings when algorithm changes
406
- rankingAlgorithmSelect.addEventListener('change', updateRankings);
407
- });
408
-
409
-
410
- // Show notification
411
- function showNotification(title, message) {
412
- const notification = document.getElementById('notification');
413
- const titleElement = document.getElementById('notification-title');
414
- const messageElement = document.getElementById('notification-message');
415
-
416
- titleElement.textContent = title;
417
- messageElement.textContent = message;
418
-
419
- notification.classList.remove('hidden');
420
- notification.classList.add('show');
421
-
422
- setTimeout(() => {
423
- notification.classList.remove('show');
424
- setTimeout(() => notification.classList.add('hidden'), 300);
425
- }, 3000);
426
- }
427
- // Quick prompt buttons
428
- function quickPrompt(prompt) {
429
- document.getElementById('user-input').value = prompt;
430
- sendMessage();
431
- }
432
-
433
-
434
- // Send message to AI
435
- async function sendMessage() {
436
- const userInput = document.getElementById('user-input');
437
- const chatMessages = document.getElementById('chat-messages');
438
-
439
- if (userInput.value.trim() === '') return;
440
-
441
- // Add user message to chat
442
- const userMessage = document.createElement('div');
443
- userMessage.className = 'chat-bubble user-bubble p-4 w-3/4 ml-auto mb-4 fade-in';
444
- userMessage.innerHTML = `<p>${userInput.value}</p>`;
445
- chatMessages.appendChild(userMessage);
446
-
447
- // Show typing indicator
448
- const typingIndicator = document.createElement('div');
449
- typingIndicator.className = 'chat-bubble ai-bubble p-4 w-1/2 mb-4';
450
- typingIndicator.innerHTML = '<div class="typing-indicator"><span></span><span></span><span></span></div>';
451
- chatMessages.appendChild(typingIndicator);
452
-
453
- // Scroll to bottom
454
- chatMessages.scrollTop = chatMessages.scrollHeight;
455
-
456
- // Save user message
457
- const userMessageText = userInput.value;
458
- userInput.value = '';
459
-
460
- try {
461
- // Call Cloudflare Workers AI
462
- const aiResponse = await queryCloudflareAI(userMessageText);
463
-
464
- // Remove typing indicator
465
- chatMessages.removeChild(typingIndicator);
466
-
467
- // Add AI response to chat
468
- const aiMessage = document.createElement('div');
469
- aiMessage.className = 'chat-bubble ai-bubble p-4 w-3/4 fade-in';
470
- aiMessage.innerHTML = `<p>${aiResponse}</p>`;
471
- chatMessages.appendChild(aiMessage);
472
-
473
- // Update recommendations based on AI response
474
- updateRecommendationsFromAI(aiResponse);
475
-
476
- } catch (error) {
477
- // Remove typing indicator
478
- chatMessages.removeChild(typingIndicator);
479
-
480
- // Show error message
481
- const errorMessage = document.createElement('div');
482
- errorMessage.className = 'chat-bubble ai-bubble p-4 w-3/4 fade-in';
483
- errorMessage.innerHTML = `<p class="text-red-500">Sorry, I'm having trouble connecting to the AI service. Please try again later.</p>`;
484
- chatMessages.appendChild(errorMessage);
485
- }
486
-
487
- // Scroll to bottom
488
- chatMessages.scrollTop = chatMessages.scrollHeight;
489
- }
490
- // Query Cloudflare Workers AI
491
- async function queryCloudflareAI(prompt) {
492
- // In a production environment, this would be handled by a backend service
493
- // to keep the API token secure. For this demo, we'll simulate the response.
494
-
495
- console.log(`[DEBUG] Would call Cloudflare AI with prompt: "${prompt}"`);
496
-
497
- // Simulate API call delay
498
- await new Promise(resolve => setTimeout(resolve, 1500));
499
-
500
- // Simulate different responses based on prompt
501
- const lowerPrompt = prompt.toLowerCase();
502
-
503
- if (lowerPrompt.includes('comedy') || lowerPrompt.includes('funny')) {
504
- 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?";
505
- }
506
- else if (lowerPrompt.includes('thriller') || lowerPrompt.includes('suspense')) {
507
- 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!";
508
- }
509
- else if (lowerPrompt.includes('romance') || lowerPrompt.includes('love')) {
510
- 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!";
511
- }
512
- else if (lowerPrompt.includes('recommend') || lowerPrompt.includes('suggest')) {
513
- 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!";
514
- }
515
- else {
516
- 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?'";
517
- }
518
- }
519
-
520
-
521
- // Update recommendations based on AI response
522
- function updateRecommendationsFromAI(aiResponse) {
523
- let filter = 'all';
524
-
525
- if (aiResponse.includes('comedy')) {
526
- filter = 'comedy';
527
- } else if (aiResponse.includes('thriller') || aiResponse.includes('suspense')) {
528
- filter = 'thriller';
529
- } else if (aiResponse.includes('romance') || aiResponse.includes('love')) {
530
- filter = 'romance';
531
- }
532
-
533
- loadRecommendations(filter);
534
- }
535
-
536
-
537
- // Save show and set broadcast reminder
538
- function saveShow(title, broadcastTime) {
539
- // In a real app, this would save to a database
540
- console.log(`Saved show: ${title}`);
541
-
542
- // Show notification
543
- showNotification("Reminder Set!", `We'll notify you when "${title}" is about to broadcast.`);
544
-
545
- // In a real app, you would schedule a notification for the broadcast time
546
- if (broadcastTime) {
547
- const broadcastDate = new Date(broadcastTime);
548
- const now = new Date();
549
-
550
- // Only schedule if broadcast is in the future
551
- if (broadcastDate > now) {
552
- const timeUntilBroadcast = broadcastDate - now;
553
-
554
- // Schedule notification 30 minutes before broadcast
555
- setTimeout(() => {
556
- showNotification("Starting Soon!", `"${title}" will begin broadcasting in 30 minutes!`);
557
- }, timeUntilBroadcast - (30 * 60 * 1000));
558
- }
559
- }
560
- }
561
- // Load recommendations
562
- function loadRecommendations(filter = 'all') {
563
- const container = document.getElementById('recommendations-container');
564
- container.innerHTML = '';
565
-
566
- let filteredData = streamingData;
567
-
568
- if (filter === 'comedy') {
569
- filteredData = streamingData.filter(item => item.genre.toLowerCase().includes('comedy'));
570
- } else if (filter === 'thriller') {
571
- filteredData = streamingData.filter(item => item.genre.toLowerCase().includes('thriller'));
572
- } else if (filter === 'romance') {
573
- filteredData = streamingData.filter(item => item.genre.toLowerCase().includes('romance'));
574
- }
575
-
576
- filteredData.forEach(item => {
577
- const card = document.createElement('div');
578
- card.className = 'stream-card bg-white rounded-lg overflow-hidden shadow-md hover:shadow-xl transition duration-300 fade-in p-4';
579
- card.innerHTML = `
580
- <div class="mb-3">
581
- <div class="flex justify-between items-start">
582
- <h3 class="font-bold text-base">${item.title}</h3>
583
- <span class="bg-yellow-100 text-yellow-800 text-xs px-2 py-1 rounded-full flex items-center">
584
- <i class="fas fa-star text-yellow-500 mr-1 text-xs"></i> ${item.rating}
585
- </span>
586
- </div>
587
- <p class="text-gray-600 text-xs mb-1">${item.type} • ${item.genre} • ${item.year}</p>
588
- <div class="text-xs text-indigo-600 mb-2">${item.platform}</div>
589
- </div>
590
- <p class="text-gray-700 text-sm mb-4">${item.description}</p>
591
- <div class="flex justify-between items-center">
592
- <button class="text-indigo-600 hover:text-indigo-800 text-xs font-medium" onclick="saveShow('${item.title}', '${item.broadcastTime}')">
593
- <i class="far fa-bookmark mr-1"></i> Save
594
- </button>
595
- <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">
596
- <i class="fas fa-play mr-1"></i> Watch
597
- </a>
598
- </div>
599
- `;
600
- container.appendChild(card);
601
- });
602
- }
603
-
604
-
605
- // Load RSS feed
606
- function loadRSSFeed(filter) {
607
- const container = document.getElementById('rss-feed');
608
- container.innerHTML = '';
609
-
610
- const feedItems = rssFeedData[filter] || rssFeedData.all;
611
-
612
- feedItems.forEach(item => {
613
- const feedItem = document.createElement('div');
614
- feedItem.className = 'rss-item bg-gray-50 p-3 rounded-lg';
615
- feedItem.innerHTML = `
616
- <h4 class="font-medium text-sm mb-1">${item.title}</h4>
617
- <div class="flex items-center text-xs text-gray-500 mb-2">
618
- <span>${item.source}</span>
619
- <span class="mx-2">•</span>
620
- <span>${item.time}</span>
621
- </div>
622
- <p class="text-xs text-gray-700">${item.excerpt}</p>
623
- `;
624
- container.appendChild(feedItem);
625
- });
626
- }
627
- // Start recording video and audio
628
- async function startRecording() {
629
- try {
630
- // Get user media
631
- videoStream = await navigator.mediaDevices.getDisplayMedia({
632
- video: true,
633
- audio: true
634
- });
635
-
636
- audioStream = await navigator.mediaDevices.getUserMedia({
637
- audio: true
638
- });
639
-
640
- // Create audio context
641
- audioContext = new AudioContext();
642
- const source = audioContext.createMediaStreamSource(audioStream);
643
- const destination = audioContext.createMediaStreamDestination();
644
- source.connect(destination);
645
-
646
- // Combine video and audio streams
647
- const combinedStream = new MediaStream([
648
- ...videoStream.getVideoTracks(),
649
- ...destination.stream.getAudioTracks()
650
- ]);
651
-
652
- // Create media recorder
653
- mediaRecorder = new MediaRecorder(combinedStream, {
654
- mimeType: 'video/webm'
655
- });
656
-
657
- // Update preview with live recording
658
- const preview = document.getElementById('clip-preview');
659
- preview.innerHTML = '<video autoplay muted></video>';
660
- const previewVideo = preview.querySelector('video');
661
- previewVideo.srcObject = combinedStream;
662
-
663
- // Set recording state
664
- isRecording = true;
665
- document.getElementById('recording-indicator').classList.remove('hidden');
666
- document.getElementById('start-recording').classList.add('hidden');
667
- document.getElementById('stop-recording').classList.remove('hidden');
668
-
669
- // Start progress bar animation
670
- currentClipTime = 0;
671
- const progressFill = document.getElementById('progress-fill');
672
- progressFill.style.width = '0%';
673
-
674
- currentClipInterval = setInterval(() => {
675
- currentClipTime += 100;
676
- const progressPercent = (currentClipTime / 5000) * 100;
677
- progressFill.style.width = `${progressPercent}%`;
678
- }, 100);
679
-
680
- // Start recording in 5-second clips
681
- let clipCount = 0;
682
- recordingInterval = setInterval(() => {
683
- if (clipCount > 0) {
684
- // Stop current recording
685
- mediaRecorder.stop();
686
- }
687
-
688
- // Start new recording
689
- mediaRecorder.start();
690
- clipCount++;
691
-
692
- // Reset progress bar
693
- currentClipTime = 0;
694
- progressFill.style.width = '0%';
695
-
696
- // Store clip data when available
697
- mediaRecorder.ondataavailable = (e) => {
698
- const clip = {
699
- blob: e.data,
700
- timestamp: new Date().toLocaleTimeString(),
701
- url: URL.createObjectURL(e.data),
702
- username: username,
703
- category: currentCategory,
704
- engagementScore: Math.random() * 5, // Simulated metrics
705
- qualityScore: 3 + Math.random() * 2,
706
- consistencyScore: 3 + Math.random() * 2
707
- };
708
- recordedClips.push(clip);
709
- updateClipsList();
710
- };
711
- }, 5000);
712
-
713
- showNotification("Recording Started", "Recording 5-second clips of your screen and audio");
714
-
715
- } catch (error) {
716
- console.error("Error starting recording:", error);
717
- showNotification("Recording Error", "Could not start recording. Please check permissions.");
718
- stopRecording();
719
- }
720
- }
721
- // Stop recording
722
- function stopRecording() {
723
- if (mediaRecorder && mediaRecorder.state !== 'inactive') {
724
- mediaRecorder.stop();
725
- }
726
-
727
- clearInterval(recordingInterval);
728
- clearInterval(currentClipInterval);
729
- isRecording = false;
730
-
731
- // Stop all tracks
732
- if (videoStream) {
733
- videoStream.getTracks().forEach(track => track.stop());
734
- }
735
- if (audioStream) {
736
- audioStream.getTracks().forEach(track => track.stop());
737
- }
738
-
739
- // Close audio context
740
- if (audioContext && audioContext.state !== 'closed') {
741
- audioContext.close();
742
- }
743
-
744
- // Reset preview
745
- const preview = document.getElementById('clip-preview');
746
- preview.innerHTML = `
747
- <div class="clip-preview-placeholder">
748
- <i class="fas fa-video"></i>
749
- <p>Preview will appear here</p>
750
- </div>
751
- `;
752
-
753
- // Reset progress bar
754
- document.getElementById('progress-fill').style.width = '0%';
755
-
756
- // Update UI
757
- document.getElementById('recording-indicator').classList.add('hidden');
758
- document.getElementById('start-recording').classList.remove('hidden');
759
- document.getElementById('stop-recording').classList.add('hidden');
760
-
761
- showNotification("Recording Stopped", `Captured ${recordedClips.length} clips ready for production`);
762
- }
763
-
764
-
765
- // Update clips list
766
- function updateClipsList() {
767
- const container = document.getElementById('clips-container');
768
- container.innerHTML = '';
769
-
770
- if (recordedClips.length === 0) {
771
- container.innerHTML = '<p class="text-gray-500 text-sm">Clips will appear here...</p>';
772
- return;
773
- }
774
-
775
- recordedClips.forEach((clip, index) => {
776
- const clipItem = document.createElement('div');
777
- clipItem.className = 'clip-item';
778
- clipItem.innerHTML = `
779
- <i class="fas fa-video text-gray-500 mr-2"></i>
780
- <span class="text-sm flex-1">Clip ${index + 1}</span>
781
- <span class="text-xs text-gray-500">${clip.timestamp}</span>
782
- `;
783
-
784
- // Add click handler to preview clip
785
- clipItem.addEventListener('click', () => {
786
- const preview = document.getElementById('clip-preview');
787
- preview.innerHTML = '<video controls></video>';
788
- const previewVideo = preview.querySelector('video');
789
- previewVideo.src = clip.url;
790
- });
791
-
792
- container.appendChild(clipItem);
793
- });
794
- }
795
- // Clear all clips
796
- function clearClips() {
797
- if (recordedClips.length === 0) return;
798
-
799
- // In a real app, we would properly revoke the object URLs
800
- recordedClips.forEach(clip => {
801
- if (clip.url) {
802
- URL.revokeObjectURL(clip.url);
803
- }
804
- });
805
-
806
- recordedClips = [];
807
- updateClipsList();
808
-
809
- // Reset preview
810
- const preview = document.getElementById('clip-preview');
811
- preview.innerHTML = `
812
- <div class="clip-preview-placeholder">
813
- <i class="fas fa-video"></i>
814
- <p>Preview will appear here</p>
815
- </div>
816
- `;
817
-
818
- showNotification("Clips Cleared", "All recorded clips have been removed");
819
- }
820
-
821
-
822
- // Generate short video from clips
823
- function generateShortVideo() {
824
- if (recordedClips.length === 0) {
825
- showNotification("No Clips", "Please record some clips first");
826
- return;
827
- }
828
-
829
- // In a real app, this would use a video editing library to combine clips
830
- // and sync with audio. For this demo, we'll simulate the process.
831
-
832
- showNotification("Video Generation", "Processing your clips into a short video...");
833
-
834
- setTimeout(() => {
835
- // Simulate processing time
836
- const videoUrl = URL.createObjectURL(new Blob(["Simulated video content"], { type: 'video/mp4' }));
837
-
838
- // Create download link
839
- const a = document.createElement('a');
840
- a.href = videoUrl;
841
- a.download = 'streamai-short.mp4';
842
- a.click();
843
-
844
- showNotification("Video Ready", "Your short video has been generated and downloaded");
845
- }, 3000);
846
- }
847
-
848
-
849
- // Connect to Bluetooth devices
850
- async function connectBluetooth() {
851
- try {
852
- // Request Bluetooth device
853
- bluetoothDevice = await navigator.bluetooth.requestDevice({
854
- acceptAllDevices: true,
855
- optionalServices: ['generic_access']
856
- });
857
-
858
- // Connect to the GATT Server
859
- bluetoothServer = await bluetoothDevice.gatt.connect();
860
-
861
- // Get the service
862
- bluetoothService = await bluetoothServer.getPrimaryService('generic_access');
863
-
864
- // Get the characteristic
865
- bluetoothCharacteristic = await bluetoothService.getCharacteristic('device_name');
866
-
867
- // Update connection status
868
- isBluetoothConnected = true;
869
- const statusElement = document.getElementById('connection-status');
870
- statusElement.innerHTML = `
871
- <i class="fas fa-circle bluetooth-connected mr-1"></i>
872
- <span>Connected to ${bluetoothDevice.name || 'device'}</span>
873
- `;
874
-
875
- // Simulate discovering group members
876
- groupMembers = [
877
- { name: "User" + Math.floor(Math.random() * 1000), device: "Device 1" },
878
- { name: "User" + Math.floor(Math.random() * 1000), device: "Device 2" },
879
- { name: "User" + Math.floor(Math.random() * 1000), device: "Device 3" }
880
- ];
881
- showNotification("Bluetooth Connected", `Connected to ${bluetoothDevice.name}. Found ${groupMembers.length} group members.`);
882
-
883
- } catch (error) {
884
- console.error("Bluetooth connection error:", error);
885
- isBluetoothConnected = false;
886
- document.getElementById('connection-status').innerHTML = `
887
- <i class="fas fa-circle bluetooth-disconnected mr-1"></i>
888
- <span>Not connected</span>
889
- `;
890
- showNotification("Connection Failed", "Could not connect to Bluetooth device");
891
- }
892
- }
893
-
894
-
895
- // Share clips with Bluetooth group
896
- async function shareClipsWithGroup() {
897
- if (!isBluetoothConnected) {
898
- showNotification("Not Connected", "Please connect to Bluetooth first");
899
- return;
900
- }
901
-
902
- if (recordedClips.length === 0) {
903
- showNotification("No Clips", "Please record some clips first");
904
- return;
905
- }
906
-
907
- // In a real app, this would actually send the clips over Bluetooth
908
- // For this demo, we'll simulate the sharing process
909
-
910
- showNotification("Sharing Clips", "Sharing your clips with the group...");
911
-
912
- // Simulate sharing delay
913
- setTimeout(() => {
914
- // Add metadata to clips and add to shared clips
915
- recordedClips.forEach(clip => {
916
- const sharedClip = {
917
- ...clip,
918
- sharedBy: username,
919
- category: currentCategory,
920
- timestamp: new Date().toLocaleTimeString(),
921
- likes: Math.floor(Math.random() * 10),
922
- views: Math.floor(Math.random() * 50)
923
- };
924
- sharedClips.push(sharedClip);
925
- });
926
-
927
- // Update shared clips display
928
- updateSharedClipsList();
929
-
930
- showNotification("Clips Shared", `Shared ${recordedClips.length} clips with ${groupMembers.length} group members`);
931
- }, 2000);
932
- }
933
- // Update shared clips list
934
- function updateSharedClipsList() {
935
- const container = document.getElementById('shared-clips-container');
936
- container.innerHTML = '';
937
-
938
- if (sharedClips.length === 0) {
939
- container.innerHTML = '<p class="text-gray-500 text-sm">Shared clips will appear here...</p>';
940
- return;
941
- }
942
-
943
- sharedClips.forEach((clip, index) => {
944
- const clipItem = document.createElement('div');
945
- clipItem.className = 'clip-item';
946
- clipItem.innerHTML = `
947
- <i class="fas fa-video text-gray-500 mr-2"></i>
948
- <span class="text-sm flex-1">Clip ${index + 1}</span>
949
- <span class="user-tag">${clip.sharedBy}</span>
950
- <span class="category-tag">${clip.category}</span>
951
- `;
952
-
953
- // Add click handler to preview clip
954
- clipItem.addEventListener('click', () => {
955
- const preview = document.getElementById('clip-preview');
956
- preview.innerHTML = '<video controls></video>';
957
- const previewVideo = preview.querySelector('video');
958
- previewVideo.src = clip.url;
959
- });
960
-
961
- container.appendChild(clipItem);
962
- });
963
- }
964
-
965
-
966
- // Benchmarking algorithm to rank clips
967
- function benchmarkClips() {
968
- if (sharedClips.length === 0) return [];
969
-
970
- // Calculate scores for each clip
971
- sharedClips.forEach(clip => {
972
- // Composite score is weighted average of all metrics
973
- clip.compositeScore = (
974
- clip.engagementScore * 0.4 +
975
- clip.qualityScore * 0.3 +
976
- clip.consistencyScore * 0.3
977
- );
978
-
979
- // Add some randomness to simulate different algorithms
980
- clip.engagementScore = Math.min(5, clip.engagementScore + Math.random() * 0.5 - 0.25);
981
- clip.qualityScore = Math.min(5, clip.qualityScore + Math.random() * 0.5 - 0.25);
982
- clip.consistencyScore = Math.min(5, clip.consistencyScore + Math.random() * 0.5 - 0.25);
983
- });
984
-
985
- // Sort based on selected algorithm
986
- const algorithm = document.getElementById('ranking-algorithm').value;
987
- let sortedClips = [...sharedClips];
988
-
989
- switch (algorithm) {
990
- case 'engagement':
991
- sortedClips.sort((a, b) => b.engagementScore - a.engagementScore);
992
- break;
993
- case 'quality':
994
- sortedClips.sort((a, b) => b.qualityScore - a.qualityScore);
995
- break;
996
- case 'consistency':
997
- sortedClips.sort((a, b) => b.consistencyScore - a.consistencyScore);
998
- break;
999
- case 'composite':
1000
- default:
1001
- sortedClips.sort((a, b) => b.compositeScore - a.compositeScore);
1002
- break;
1003
- }
1004
- return sortedClips;
1005
- }
1006
-
1007
-
1008
- // Update rankings display
1009
- function updateRankings() {
1010
- const container = document.getElementById('rankings-container');
1011
- container.innerHTML = '';
1012
-
1013
- const rankedClips = benchmarkClips();
1014
-
1015
- if (rankedClips.length === 0) {
1016
- container.innerHTML = '<p class="text-gray-500 text-sm">Rankings will appear here when available...</p>';
1017
- return;
1018
- }