unified-video-framework 1.4.256 → 1.4.257

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1184,7 +1184,7 @@ export class WebPlayer extends BasePlayer {
1184
1184
  width: '100%',
1185
1185
  height: '100%',
1186
1186
  playerVars: {
1187
- controls: 0, // Hide YouTube controls
1187
+ controls: this.config.youtubeNativeControls !== false ? 1 : 0, // Native controls enabled by default
1188
1188
  disablekb: 0, // Allow keyboard controls
1189
1189
  fs: 0, // Hide fullscreen button
1190
1190
  iv_load_policy: 3, // Hide annotations
@@ -1252,16 +1252,16 @@ export class WebPlayer extends BasePlayer {
1252
1252
  this.youtubePlayer.mute();
1253
1253
  }
1254
1254
 
1255
- // Extract available YouTube qualities
1256
- this.extractYouTubeAvailableQualities();
1255
+ // Detect if YouTube video is Live and handle controls
1256
+ this.detectYouTubeLiveStatus();
1257
1257
 
1258
1258
  // Try to get video title from YouTube API
1259
1259
  this.getYouTubeVideoTitle();
1260
1260
 
1261
- // Update metadata UI and settings after player is ready
1261
+ // Update metadata UI and controls visibility after player is ready
1262
1262
  setTimeout(() => {
1263
1263
  this.updateMetadataUI();
1264
- this.updateSettingsMenu();
1264
+ this.updateControlsVisibility();
1265
1265
  }, 500);
1266
1266
  }
1267
1267
 
@@ -1277,16 +1277,10 @@ export class WebPlayer extends BasePlayer {
1277
1277
  switch (state) {
1278
1278
  case window.YT.PlayerState.PLAYING:
1279
1279
  this.state.isPlaying = true;
1280
- this.state.isPaused = false;
1281
- this.state.isBuffering = false;
1282
- this.updateYouTubeUI('playing');
1283
-
1284
- // Extract qualities when video starts playing
1285
- setTimeout(() => {
1286
- this.extractYouTubeAvailableQualities();
1287
- }, 1000);
1288
-
1289
- this.emit('onPlay');
1280
+ this.state.isPaused = false;
1281
+ this.state.isBuffering = false;
1282
+ this.updateYouTubeUI('playing');
1283
+ this.emit('onPlay');
1290
1284
  break;
1291
1285
 
1292
1286
  case window.YT.PlayerState.PAUSED:
@@ -1297,16 +1291,10 @@ export class WebPlayer extends BasePlayer {
1297
1291
  this.emit('onPause');
1298
1292
  break;
1299
1293
 
1300
- case window.YT.PlayerState.BUFFERING:
1301
- this.state.isBuffering = true;
1302
- this.updateYouTubeUI('buffering');
1303
-
1304
- // Try to extract qualities during buffering as well
1305
- setTimeout(() => {
1306
- this.extractYouTubeAvailableQualities();
1307
- }, 500);
1308
-
1309
- this.emit('onBuffering', true);
1294
+ case window.YT.PlayerState.BUFFERING:
1295
+ this.state.isBuffering = true;
1296
+ this.updateYouTubeUI('buffering');
1297
+ this.emit('onBuffering', true);
1310
1298
  break;
1311
1299
 
1312
1300
  case window.YT.PlayerState.ENDED:
@@ -1317,15 +1305,16 @@ export class WebPlayer extends BasePlayer {
1317
1305
  this.emit('onEnded');
1318
1306
  break;
1319
1307
 
1320
- case window.YT.PlayerState.CUED:
1321
- this.state.duration = this.youtubePlayer.getDuration();
1322
- this.updateYouTubeUI('cued');
1323
-
1324
- // Try to get video title when video is cued
1325
- setTimeout(() => {
1326
- this.getYouTubeVideoTitle();
1327
- }, 100);
1328
- break;
1308
+ case window.YT.PlayerState.CUED:
1309
+ this.state.duration = this.youtubePlayer.getDuration();
1310
+ this.updateYouTubeUI('cued');
1311
+
1312
+ // Re-check Live status when video is cued
1313
+ setTimeout(() => {
1314
+ this.detectYouTubeLiveStatus();
1315
+ this.updateControlsVisibility();
1316
+ }, 500);
1317
+ break;
1329
1318
  }
1330
1319
  }
1331
1320
 
@@ -1403,359 +1392,207 @@ export class WebPlayer extends BasePlayer {
1403
1392
  }
1404
1393
 
1405
1394
  /**
1406
- * Fallback method to get title from YouTube oembed API
1395
+ * Detect if YouTube video is Live
1407
1396
  */
1408
- private async getYouTubeVideoTitleFromOEmbed(): Promise<void> {
1409
- if (!this.source?.metadata?.videoId) return;
1410
-
1411
- try {
1412
- const videoId = this.source.metadata.videoId;
1413
- const oembedUrl = `https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${videoId}&format=json`;
1414
-
1415
- const response = await fetch(oembedUrl);
1416
- if (response.ok) {
1417
- const data = await response.json();
1418
- if (data.title) {
1419
- this.debugLog('Got YouTube title from oembed API:', data.title);
1420
-
1421
- // Update source metadata
1422
- if (this.source && this.source.metadata) {
1423
- this.source.metadata.title = data.title;
1424
- }
1425
-
1426
- // Update UI
1427
- this.updateMetadataUI();
1428
- }
1429
- }
1430
- } catch (error) {
1431
- this.debugWarn('Could not get YouTube title from oembed API:', error);
1432
- }
1433
- }
1434
-
1435
- private youtubeTimeTrackingInterval: NodeJS.Timeout | null = null;
1436
- private youtubeAvailableQualities: any[] = [];
1437
- private youtubeCurrentQuality: any = null;
1438
-
1439
- /**
1440
- * Extract available YouTube video qualities with retry logic
1441
- */
1442
- private extractYouTubeAvailableQualities(retryCount: number = 0): void {
1443
- if (!this.youtubePlayer || !this.youtubePlayerReady) {
1444
- this.debugLog('YouTube player not ready for quality extraction');
1445
- return;
1446
- }
1397
+ private detectYouTubeLiveStatus(): void {
1398
+ if (!this.youtubePlayer || !this.youtubePlayerReady) return;
1447
1399
 
1448
1400
  try {
1449
- const availableQualityLevels = this.youtubePlayer.getAvailableQualityLevels();
1450
- this.debugLog('🔍 YouTube quality detection attempt', retryCount + 1, '- Found levels:', availableQualityLevels);
1451
- this.debugLog('🔍 YouTube player state:', this.youtubePlayer.getPlayerState());
1452
- this.debugLog('🔍 Current YouTube quality:', this.youtubePlayer.getPlaybackQuality());
1453
-
1454
- // If no qualities detected and we haven't retried too much, retry after a delay
1455
- if ((!availableQualityLevels || availableQualityLevels.length === 0 || (availableQualityLevels.length === 1 && availableQualityLevels[0] === 'auto')) && retryCount < 5) {
1456
- this.debugLog('No meaningful qualities detected, retrying in', (retryCount + 1) * 1000, 'ms...');
1457
- setTimeout(() => {
1458
- this.extractYouTubeAvailableQualities(retryCount + 1);
1459
- }, (retryCount + 1) * 1000);
1460
- return;
1461
- }
1462
-
1463
- // Map YouTube quality levels to standard labels
1464
- const qualityMap: Record<string, any> = {
1465
- 'hd2160': { label: '4K', value: 'hd2160', height: 2160 },
1466
- 'hd1440': { label: '1440p', value: 'hd1440', height: 1440 },
1467
- 'hd1080': { label: '1080p', value: 'hd1080', height: 1080 },
1468
- 'hd720': { label: '720p', value: 'hd720', height: 720 },
1469
- 'large': { label: '480p', value: 'large', height: 480 },
1470
- 'medium': { label: '360p', value: 'medium', height: 360 },
1471
- 'small': { label: '240p', value: 'small', height: 240 },
1472
- 'tiny': { label: '144p', value: 'tiny', height: 144 },
1473
- 'auto': { label: 'Auto', value: 'auto', height: 0 }
1474
- };
1475
-
1476
- this.youtubeAvailableQualities = [];
1401
+ const videoData = this.youtubePlayer.getVideoData();
1402
+ this.isYouTubeLive = videoData?.isLive || false;
1477
1403
 
1478
- // Always add auto quality option first
1479
- this.youtubeAvailableQualities.push({ label: 'Auto', value: 'auto', height: 0 });
1404
+ // Auto-enable native controls for all YouTube content by default, unless explicitly disabled
1405
+ this.useYouTubeNativeControls = this.config.youtubeNativeControls !== false;
1480
1406
 
1481
- // Add detected qualities, filtering out 'auto' since we already added it
1482
- if (availableQualityLevels && availableQualityLevels.length > 0) {
1483
- availableQualityLevels.forEach((qualityLevel: string) => {
1484
- if (qualityLevel !== 'auto' && qualityMap[qualityLevel]) {
1485
- this.youtubeAvailableQualities.push(qualityMap[qualityLevel]);
1486
- }
1487
- });
1488
-
1489
- // Sort qualities by height (descending, excluding auto)
1490
- const autoQuality = this.youtubeAvailableQualities.shift(); // Remove auto temporarily
1491
- this.youtubeAvailableQualities.sort((a, b) => (b.height || 0) - (a.height || 0));
1492
- if (autoQuality) this.youtubeAvailableQualities.unshift(autoQuality); // Add auto back at beginning
1493
- }
1494
-
1495
- this.debugLog('✅ Successfully mapped YouTube qualities:', this.youtubeAvailableQualities);
1407
+ this.debugLog('YouTube Live status:', {
1408
+ isLive: this.isYouTubeLive,
1409
+ useNativeControls: this.useYouTubeNativeControls,
1410
+ videoId: videoData?.video_id
1411
+ });
1496
1412
 
1497
- // Get current quality
1413
+ } catch (error) {
1414
+ this.debugWarn('Could not detect YouTube Live status:', error);
1415
+ // Fallback: check duration - Live videos typically have duration = 0
1498
1416
  try {
1499
- const currentQuality = this.youtubePlayer.getPlaybackQuality();
1500
- this.youtubeCurrentQuality = qualityMap[currentQuality] || qualityMap['auto'];
1501
- this.debugLog('Current YouTube quality:', this.youtubeCurrentQuality);
1417
+ const duration = this.youtubePlayer.getDuration();
1418
+ this.isYouTubeLive = !duration || duration === 0;
1419
+ this.useYouTubeNativeControls = this.config.youtubeNativeControls !== false;
1420
+
1421
+ this.debugLog('YouTube Live detected via duration check:', {
1422
+ duration,
1423
+ isLive: this.isYouTubeLive,
1424
+ useNativeControls: this.useYouTubeNativeControls
1425
+ });
1502
1426
  } catch (e) {
1503
- this.debugWarn('Could not get current YouTube quality:', e);
1504
- this.youtubeCurrentQuality = qualityMap['auto'];
1505
- }
1506
-
1507
- // Update detected qualities to include YouTube qualities
1508
- this.detectAvailableQualities();
1509
-
1510
- this.debugLog('🚀 YouTube qualities successfully detected! Available qualities now:', this.availableQualities.length);
1511
-
1512
- // Update quality badge with current YouTube quality
1513
- this.updateQualityBadge();
1514
-
1515
- // Force settings menu refresh with new qualities
1516
- setTimeout(() => {
1517
- this.debugLog('🔄 Force refreshing settings menu with YouTube qualities');
1518
- this.updateSettingsMenu();
1519
- }, 100);
1520
-
1521
- } catch (error) {
1522
- this.debugWarn('Could not extract YouTube qualities:', error);
1523
- // Retry if we haven't tried too many times
1524
- if (retryCount < 3) {
1525
- setTimeout(() => {
1526
- this.extractYouTubeAvailableQualities(retryCount + 1);
1527
- }, 2000);
1427
+ this.debugWarn('Could not check YouTube duration for Live detection:', e);
1528
1428
  }
1529
1429
  }
1530
1430
  }
1531
1431
 
1532
1432
  /**
1533
- * Set YouTube video quality
1433
+ * Update controls visibility based on YouTube Live status and config
1534
1434
  */
1535
- private setYouTubeQuality(qualityLevel: string): void {
1536
- if (!this.youtubePlayer) return;
1435
+ private updateControlsVisibility(): void {
1436
+ const controlsContainer = document.getElementById('uvf-controls-container');
1437
+ if (!controlsContainer) return;
1537
1438
 
1538
- try {
1539
- const qualityMap: Record<string, any> = {
1540
- 'hd2160': { label: '4K', value: 'hd2160', height: 2160 },
1541
- 'hd1440': { label: '1440p', value: 'hd1440', height: 1440 },
1542
- 'hd1080': { label: '1080p', value: 'hd1080', height: 1080 },
1543
- 'hd720': { label: '720p', value: 'hd720', height: 720 },
1544
- 'large': { label: '480p', value: 'large', height: 480 },
1545
- 'medium': { label: '360p', value: 'medium', height: 360 },
1546
- 'small': { label: '240p', value: 'small', height: 240 },
1547
- 'tiny': { label: '144p', value: 'tiny', height: 144 },
1548
- 'auto': { label: 'Auto', value: 'auto', height: 0 }
1549
- };
1439
+ if (this.youtubePlayer && this.useYouTubeNativeControls) {
1440
+ // Hide custom controls and show YouTube native controls
1441
+ controlsContainer.style.display = 'none';
1550
1442
 
1551
- this.debugLog('📊 Attempting to set YouTube quality to:', qualityLevel);
1552
-
1553
- // Multiple approaches to force YouTube quality change
1554
- if (qualityLevel === 'auto') {
1555
- // For auto, just use setPlaybackQuality
1556
- this.youtubePlayer.setPlaybackQuality(qualityLevel);
1557
- } else {
1558
- // For specific qualities, try multiple methods
1559
- try {
1560
- // Method 1: Set quality range (most effective for forcing specific quality)
1561
- if (typeof this.youtubePlayer.setPlaybackQualityRange === 'function') {
1562
- this.youtubePlayer.setPlaybackQualityRange(qualityLevel, qualityLevel);
1563
- this.debugLog('✅ setPlaybackQualityRange called for:', qualityLevel);
1564
- }
1565
- } catch (e) {
1566
- this.debugWarn('setPlaybackQualityRange failed:', e);
1567
- }
1568
-
1569
- try {
1570
- // Method 2: Standard quality setting
1571
- this.youtubePlayer.setPlaybackQuality(qualityLevel);
1572
- this.debugLog('✅ setPlaybackQuality called for:', qualityLevel);
1573
- } catch (e) {
1574
- this.debugWarn('setPlaybackQuality failed:', e);
1575
- }
1576
-
1577
- // Method 3: Force quality with seekTo trick (most aggressive)
1578
- try {
1579
- const currentTime = this.youtubePlayer.getCurrentTime();
1580
- const wasPlaying = this.youtubePlayer.getPlayerState() === window.YT.PlayerState.PLAYING;
1581
-
1582
- // Seek to current time + 0.1 seconds to force reload with new quality
1583
- this.youtubePlayer.seekTo(currentTime + 0.1, true);
1584
-
1585
- // Set quality immediately after seek
1586
- setTimeout(() => {
1587
- this.youtubePlayer.setPlaybackQuality(qualityLevel);
1588
-
1589
- // Seek back to original time
1590
- setTimeout(() => {
1591
- this.youtubePlayer.seekTo(currentTime, true);
1592
- if (!wasPlaying) {
1593
- this.youtubePlayer.pauseVideo();
1594
- }
1595
- }, 100);
1596
- }, 50);
1597
-
1598
- this.debugLog('🔄 Applied seekTo trick for quality change');
1599
- } catch (e) {
1600
- this.debugWarn('Force quality change with seekTo trick failed:', e);
1601
- }
1443
+ // Re-create YouTube player with native controls if needed
1444
+ if (!this.config.youtubeNativeControls) {
1445
+ this.recreateYouTubePlayerWithNativeControls();
1602
1446
  }
1603
1447
 
1604
- // Verify quality was changed after multiple delays with retry logic
1605
- this.verifyYouTubeQualityChange(qualityLevel, qualityMap, 0);
1606
- } catch (error) {
1607
- this.debugWarn('❌ Could not set YouTube quality:', error);
1608
- this.showNotification('Quality control limited on YouTube');
1448
+ this.debugLog('✅ YouTube native controls enabled', {
1449
+ isLive: this.isYouTubeLive,
1450
+ reason: this.config.youtubeNativeControls !== false ? 'Default behavior (all YouTube content)' : 'Live stream detected'
1451
+ });
1452
+ } else {
1453
+ // Show custom controls
1454
+ controlsContainer.style.display = 'flex';
1455
+ this.debugLog('✅ Custom controls enabled for YouTube video');
1609
1456
  }
1610
1457
  }
1611
1458
 
1612
1459
  /**
1613
- * Verify YouTube quality change with retry logic
1460
+ * Recreate YouTube player with native controls enabled
1614
1461
  */
1615
- private verifyYouTubeQualityChange(requestedQuality: string, qualityMap: Record<string, any>, attempt: number): void {
1616
- const maxAttempts = 3;
1617
- const delays = [500, 1500, 3000]; // Progressive delays
1462
+ private recreateYouTubePlayerWithNativeControls(): void {
1463
+ if (!this.source?.metadata?.videoId) return;
1618
1464
 
1619
- setTimeout(() => {
1620
- try {
1621
- const currentQuality = this.youtubePlayer.getPlaybackQuality();
1622
- this.youtubeCurrentQuality = qualityMap[currentQuality] || qualityMap['auto'];
1623
-
1624
- this.debugLog('🔍 Quality verification attempt', attempt + 1, '- Requested:', requestedQuality, '| Current:', currentQuality);
1625
-
1626
- if (currentQuality === requestedQuality || attempt >= maxAttempts - 1) {
1627
- // Success or final attempt
1628
- if (currentQuality === requestedQuality) {
1629
- this.showNotification(`Quality: ${qualityMap[currentQuality]?.label || 'requested quality'}`);
1630
- this.debugLog('✅ Quality successfully changed to:', requestedQuality);
1631
- } else {
1632
- // Try one more aggressive method on final attempt
1633
- if (attempt === maxAttempts - 1) {
1634
- this.debugLog('🔥 Final attempt: Trying loadVideoById method');
1635
- try {
1636
- const videoData = this.youtubePlayer.getVideoData();
1637
- const currentTime = this.youtubePlayer.getCurrentTime();
1638
-
1639
- // Reload video at current time with suggested quality
1640
- this.youtubePlayer.loadVideoById({
1641
- videoId: videoData.video_id,
1642
- startSeconds: currentTime,
1643
- suggestedQuality: requestedQuality
1644
- });
1645
-
1646
- this.debugLog('🔄 Video reloaded with suggested quality:', requestedQuality);
1647
- } catch (e) {
1648
- this.debugWarn('loadVideoById method failed:', e);
1649
- }
1650
- }
1651
-
1652
- this.showNotification(`Quality: ${qualityMap[currentQuality]?.label || currentQuality}`);
1653
- this.debugLog('⚠️ Quality change may not have worked. Requested:', requestedQuality, '| Current:', currentQuality);
1654
- }
1655
-
1656
- // Update UI regardless
1657
- this.updateQualityBadge();
1658
- this.updateSettingsMenu();
1659
- } else {
1660
- // Retry with more aggressive method
1661
- this.debugLog('🔁 Quality not changed, retrying with method', attempt + 2);
1662
- this.retryYouTubeQualityChange(requestedQuality, qualityMap, attempt + 1);
1663
-
1664
- // Still verify after delay
1665
- this.verifyYouTubeQualityChange(requestedQuality, qualityMap, attempt + 1);
1666
- }
1667
- } catch (verifyError) {
1668
- this.debugWarn('Could not verify YouTube quality:', verifyError);
1669
- if (attempt === maxAttempts - 1) {
1670
- this.showNotification('Quality change attempted');
1671
- this.updateQualityBadge();
1672
- this.updateSettingsMenu();
1673
- }
1465
+ const videoId = this.source.metadata.videoId;
1466
+ const currentTime = this.youtubePlayer?.getCurrentTime() || 0;
1467
+
1468
+ // Find the container
1469
+ const container = this.playerWrapper || this.video?.parentElement;
1470
+ if (!container) return;
1471
+
1472
+ // Destroy current player
1473
+ if (this.youtubePlayer) {
1474
+ this.youtubePlayer.destroy();
1475
+ }
1476
+
1477
+ // Remove existing iframe container
1478
+ const existingContainer = container.querySelector(`#youtube-player-${videoId}`);
1479
+ if (existingContainer) {
1480
+ existingContainer.remove();
1481
+ }
1482
+
1483
+ // Create new iframe container
1484
+ const iframeContainer = document.createElement('div');
1485
+ iframeContainer.id = `youtube-player-${videoId}`;
1486
+ iframeContainer.style.cssText = `
1487
+ position: absolute;
1488
+ top: 0;
1489
+ left: 0;
1490
+ width: 100%;
1491
+ height: 100%;
1492
+ z-index: 1;
1493
+ `;
1494
+ container.appendChild(iframeContainer);
1495
+
1496
+ // Create new player with native controls
1497
+ this.youtubePlayer = new window.YT.Player(iframeContainer.id, {
1498
+ videoId: videoId,
1499
+ width: '100%',
1500
+ height: '100%',
1501
+ playerVars: {
1502
+ autoplay: this.config.autoPlay ? 1 : 0,
1503
+ controls: 1, // Enable native controls
1504
+ modestbranding: 1,
1505
+ rel: 0,
1506
+ showinfo: 0,
1507
+ iv_load_policy: 3,
1508
+ playsinline: 1,
1509
+ start: Math.floor(currentTime)
1510
+ },
1511
+ events: {
1512
+ onReady: () => {
1513
+ this.youtubePlayerReady = true;
1514
+ this.debugLog('YouTube player with native controls ready');
1515
+ this.emit('onReady');
1516
+ },
1517
+ onStateChange: (event: any) => this.onYouTubePlayerStateChange(event),
1518
+ onError: (event: any) => this.onYouTubePlayerError(event)
1674
1519
  }
1675
- }, delays[attempt] || 1000);
1520
+ });
1676
1521
  }
1677
1522
 
1678
1523
  /**
1679
- * Retry YouTube quality change with alternative methods
1524
+ * Toggle between native and custom YouTube controls
1680
1525
  */
1681
- private retryYouTubeQualityChange(qualityLevel: string, qualityMap: Record<string, any>, attempt: number): void {
1682
- try {
1683
- if (attempt === 1) {
1684
- // Second attempt: Try with video restart
1685
- this.debugLog('🔄 Retry attempt 1: Using cueVideoById');
1686
- const videoData = this.youtubePlayer.getVideoData();
1687
- const currentTime = this.youtubePlayer.getCurrentTime();
1688
-
1689
- this.youtubePlayer.cueVideoById({
1690
- videoId: videoData.video_id,
1691
- startSeconds: currentTime,
1692
- suggestedQuality: qualityLevel
1693
- });
1694
-
1695
- setTimeout(() => this.youtubePlayer.playVideo(), 100);
1696
-
1697
- } else if (attempt === 2) {
1698
- // Third attempt: Multiple sequential calls
1699
- this.debugLog('🔄 Retry attempt 2: Multiple sequential calls');
1700
-
1701
- for (let i = 0; i < 3; i++) {
1702
- setTimeout(() => {
1703
- this.youtubePlayer.setPlaybackQuality(qualityLevel);
1704
- }, i * 100);
1705
- }
1706
- }
1707
- } catch (e) {
1708
- this.debugWarn('Retry attempt', attempt, 'failed:', e);
1526
+ public toggleYouTubeControls(useNative: boolean = !this.useYouTubeNativeControls): void {
1527
+ if (!this.youtubePlayer) {
1528
+ this.debugWarn('Cannot toggle YouTube controls - no YouTube player active');
1529
+ return;
1709
1530
  }
1531
+
1532
+ this.useYouTubeNativeControls = useNative;
1533
+ this.config.youtubeNativeControls = useNative; // Update config
1534
+ this.updateControlsVisibility();
1535
+
1536
+ this.debugLog('YouTube controls toggled:', {
1537
+ useNative: this.useYouTubeNativeControls,
1538
+ isLive: this.isYouTubeLive
1539
+ });
1540
+
1541
+ // Show notification
1542
+ this.showNotification(
1543
+ `YouTube Controls: ${this.useYouTubeNativeControls ? 'Native' : 'Custom'}`
1544
+ );
1710
1545
  }
1711
1546
 
1712
1547
  /**
1713
- * Update quality badge with current quality
1548
+ * Fallback method to get title from YouTube oembed API
1714
1549
  */
1715
- private updateQualityBadge(): void {
1716
- const qualityBadge = document.getElementById('uvf-quality-badge');
1717
- if (!qualityBadge) return;
1718
-
1719
- // For YouTube, use YouTube current quality
1720
- if (this.youtubePlayer && this.youtubePlayerReady && this.youtubeCurrentQuality) {
1721
- qualityBadge.textContent = this.youtubeCurrentQuality.label || 'AUTO';
1722
- this.debugLog('Updated quality badge for YouTube:', this.youtubeCurrentQuality.label);
1723
- } else if (this.currentQualityIndex >= 0 && this.qualities[this.currentQualityIndex]) {
1724
- // For other sources, use current quality
1725
- const quality = this.qualities[this.currentQualityIndex];
1726
- qualityBadge.textContent = quality.label || 'HD';
1727
- this.debugLog('Updated quality badge for standard video:', quality.label);
1728
- } else {
1729
- // Default
1730
- qualityBadge.textContent = 'AUTO';
1550
+ private async getYouTubeVideoTitleFromOEmbed(): Promise<void> {
1551
+ if (!this.source?.metadata?.videoId) return;
1552
+
1553
+ try {
1554
+ const videoId = this.source.metadata.videoId;
1555
+ const oembedUrl = `https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${videoId}&format=json`;
1556
+
1557
+ const response = await fetch(oembedUrl);
1558
+ if (response.ok) {
1559
+ const data = await response.json();
1560
+ if (data.title) {
1561
+ this.debugLog('Got YouTube title from oembed API:', data.title);
1562
+
1563
+ // Update source metadata
1564
+ if (this.source && this.source.metadata) {
1565
+ this.source.metadata.title = data.title;
1566
+ }
1567
+
1568
+ // Update UI
1569
+ this.updateMetadataUI();
1570
+ }
1571
+ }
1572
+ } catch (error) {
1573
+ this.debugWarn('Could not get YouTube title from oembed API:', error);
1731
1574
  }
1732
1575
  }
1733
1576
 
1577
+ private youtubeTimeTrackingInterval: NodeJS.Timeout | null = null;
1578
+ private isYouTubeLive: boolean = false;
1579
+ private useYouTubeNativeControls: boolean = false;
1580
+
1734
1581
  private startYouTubeTimeTracking(): void {
1735
1582
  if (this.youtubeTimeTrackingInterval) {
1736
1583
  clearInterval(this.youtubeTimeTrackingInterval);
1737
1584
  }
1738
1585
 
1739
- let qualityCheckCounter = 0;
1740
-
1741
- this.youtubeTimeTrackingInterval = setInterval(() => {
1742
- if (this.youtubePlayer && this.youtubePlayerReady) {
1743
- try {
1744
- const currentTime = this.youtubePlayer.getCurrentTime();
1745
- const duration = this.youtubePlayer.getDuration();
1746
- const buffered = this.youtubePlayer.getVideoLoadedFraction() * 100;
1747
-
1748
- this.state.currentTime = currentTime || 0;
1749
- this.state.duration = duration || 0;
1750
- this.state.bufferedPercentage = buffered || 0;
1751
-
1752
- // Check for qualities periodically (every 10 seconds) if we don't have them yet
1753
- qualityCheckCounter++;
1754
- if (qualityCheckCounter >= 40 && this.youtubeAvailableQualities.length <= 1) { // 40 * 250ms = 10s
1755
- this.debugLog('Periodic YouTube quality check triggered');
1756
- this.extractYouTubeAvailableQualities();
1757
- qualityCheckCounter = 0; // Reset counter
1758
- }
1586
+ this.youtubeTimeTrackingInterval = setInterval(() => {
1587
+ if (this.youtubePlayer && this.youtubePlayerReady) {
1588
+ try {
1589
+ const currentTime = this.youtubePlayer.getCurrentTime();
1590
+ const duration = this.youtubePlayer.getDuration();
1591
+ const buffered = this.youtubePlayer.getVideoLoadedFraction() * 100;
1592
+
1593
+ this.state.currentTime = currentTime || 0;
1594
+ this.state.duration = duration || 0;
1595
+ this.state.bufferedPercentage = buffered || 0;
1759
1596
 
1760
1597
  // Update UI progress bar
1761
1598
  this.updateYouTubeProgressBar(currentTime, duration, buffered);
@@ -9536,23 +9373,15 @@ export class WebPlayer extends BasePlayer {
9536
9373
  private setQualityByLabel(quality: string): void {
9537
9374
  // Handle YouTube player
9538
9375
  if (this.youtubePlayer && this.youtubePlayerReady) {
9539
- // Set YouTube quality directly
9540
- this.setYouTubeQuality(quality);
9541
-
9542
- // Update UI to reflect selection
9376
+ // YouTube quality is limited - show notification
9377
+ this.showShortcutIndicator('Quality control not available for YouTube');
9378
+ // Update UI to reflect current quality
9543
9379
  document.querySelectorAll('.quality-option').forEach(option => {
9544
9380
  option.classList.remove('active');
9545
- if ((option as HTMLElement).dataset.quality === quality) {
9381
+ if ((option as HTMLElement).dataset.quality === 'auto') {
9546
9382
  option.classList.add('active');
9547
9383
  }
9548
9384
  });
9549
-
9550
- // Update quality badge
9551
- const qualityBadge = document.getElementById('uvf-quality-badge');
9552
- if (qualityBadge) {
9553
- const qualityOption = this.youtubeAvailableQualities.find(q => q.value === quality);
9554
- qualityBadge.textContent = qualityOption ? qualityOption.label : 'AUTO';
9555
- }
9556
9385
  return;
9557
9386
  }
9558
9387
 
@@ -9853,17 +9682,9 @@ export class WebPlayer extends BasePlayer {
9853
9682
 
9854
9683
  // Quality Accordion Section (only if enabled in config and qualities detected)
9855
9684
  if (this.settingsConfig.quality && this.availableQualities.length > 0) {
9856
- this.debugLog('🎛️ Building quality menu with', this.availableQualities.length, 'qualities:', this.availableQualities.map(q => q.label));
9857
-
9858
- // For YouTube, use youtubeCurrentQuality; otherwise use currentQuality
9859
- const qualityForDisplay = this.youtubePlayer && this.youtubePlayerReady && this.youtubeCurrentQuality
9860
- ? this.youtubeCurrentQuality.value
9861
- : this.currentQuality;
9862
- const currentQuality = this.availableQualities.find(q => q.value === qualityForDisplay);
9685
+ const currentQuality = this.availableQualities.find(q => q.value === this.currentQuality);
9863
9686
  const currentQualityLabel = currentQuality ? currentQuality.label : 'Auto';
9864
9687
 
9865
- this.debugLog('🎛️ Current quality for display:', qualityForDisplay, '(', currentQualityLabel, ')');
9866
-
9867
9688
  menuHTML += `
9868
9689
  <div class="uvf-accordion-item">
9869
9690
  <div class="uvf-accordion-header" data-section="quality">
@@ -9881,11 +9702,7 @@ export class WebPlayer extends BasePlayer {
9881
9702
  <div class="uvf-accordion-content" data-section="quality">`;
9882
9703
 
9883
9704
  this.availableQualities.forEach(quality => {
9884
- // For YouTube, compare against youtubeCurrentQuality
9885
- const qualityValue = this.youtubePlayer && this.youtubePlayerReady && this.youtubeCurrentQuality
9886
- ? this.youtubeCurrentQuality.value
9887
- : this.currentQuality;
9888
- const isActive = quality.value === qualityValue ? 'active' : '';
9705
+ const isActive = quality.value === this.currentQuality ? 'active' : '';
9889
9706
  const isPremium = this.isQualityPremium(quality);
9890
9707
  const isLocked = isPremium && !this.isPremiumUser();
9891
9708
  const qualityHeight = (quality as any).height || 0;
@@ -9953,22 +9770,10 @@ export class WebPlayer extends BasePlayer {
9953
9770
  * Detect available video qualities from different sources
9954
9771
  */
9955
9772
  private detectAvailableQualities(): void {
9956
- // Check if we're using YouTube and have detected qualities
9957
- const isYouTube = this.youtubePlayer && this.youtubePlayerReady && this.youtubeAvailableQualities.length > 0;
9958
- this.availableQualities = [];
9773
+ this.availableQualities = [{ value: 'auto', label: 'Auto' }];
9959
9774
  let detectedQualities: Array<{ value: string; label: string; height?: number }> = [];
9960
9775
 
9961
- // YouTube qualities
9962
- if (isYouTube) {
9963
- detectedQualities = this.youtubeAvailableQualities.map(q => ({
9964
- value: q.value,
9965
- label: q.label,
9966
- height: q.height
9967
- }));
9968
- this.debugLog('Using YouTube qualities:', detectedQualities);
9969
- this.availableQualities = detectedQualities;
9970
- return; // Early return for YouTube
9971
- } else if (this.hls && this.hls.levels) {
9776
+ if (this.hls && this.hls.levels) {
9972
9777
  // HLS qualities
9973
9778
  this.hls.levels.forEach((level: any, index: number) => {
9974
9779
  if (level.height) {
@@ -10340,13 +10145,6 @@ export class WebPlayer extends BasePlayer {
10340
10145
  private setQualityFromSettings(quality: string): void {
10341
10146
  this.currentQuality = quality;
10342
10147
 
10343
- // Handle YouTube quality
10344
- if (this.youtubePlayer && this.youtubePlayerReady) {
10345
- this.setYouTubeQuality(quality);
10346
- this.debugLog(`YouTube quality set to ${quality}`);
10347
- return;
10348
- }
10349
-
10350
10148
  if (quality === 'auto') {
10351
10149
  // Enable auto quality with filter consideration
10352
10150
  if (this.hls) {