unified-video-framework 1.4.256 → 1.4.258

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 === true ? 1 : 0, // Native controls disabled by default for custom controls
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,280 @@ 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;
1397
+ private detectYouTubeLiveStatus(): void {
1398
+ if (!this.youtubePlayer || !this.youtubePlayerReady) return;
1410
1399
 
1411
1400
  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`;
1401
+ const videoData = this.youtubePlayer.getVideoData();
1402
+ this.isYouTubeLive = videoData?.isLive || false;
1403
+
1404
+ // Use custom controls by default, unless youtubeNativeControls is explicitly set to true
1405
+ this.useYouTubeNativeControls = this.config.youtubeNativeControls === true;
1406
+
1407
+ this.debugLog('YouTube Live status:', {
1408
+ isLive: this.isYouTubeLive,
1409
+ useNativeControls: this.useYouTubeNativeControls,
1410
+ videoId: videoData?.video_id
1411
+ });
1414
1412
 
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
1413
  } catch (error) {
1431
- this.debugWarn('Could not get YouTube title from oembed API:', error);
1414
+ this.debugWarn('Could not detect YouTube Live status:', error);
1415
+ // Fallback: check duration - Live videos typically have duration = 0
1416
+ try {
1417
+ const duration = this.youtubePlayer.getDuration();
1418
+ this.isYouTubeLive = !duration || duration === 0;
1419
+ this.useYouTubeNativeControls = this.config.youtubeNativeControls === true;
1420
+
1421
+ this.debugLog('YouTube Live detected via duration check:', {
1422
+ duration,
1423
+ isLive: this.isYouTubeLive,
1424
+ useNativeControls: this.useYouTubeNativeControls
1425
+ });
1426
+ } catch (e) {
1427
+ this.debugWarn('Could not check YouTube duration for Live detection:', e);
1428
+ }
1432
1429
  }
1433
1430
  }
1434
-
1435
- private youtubeTimeTrackingInterval: NodeJS.Timeout | null = null;
1436
- private youtubeAvailableQualities: any[] = [];
1437
- private youtubeCurrentQuality: any = null;
1438
-
1431
+
1439
1432
  /**
1440
- * Extract available YouTube video qualities with retry logic
1433
+ * Update controls visibility based on YouTube Live status and config
1441
1434
  */
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
- }
1435
+ private updateControlsVisibility(): void {
1436
+ const controlsContainer = document.getElementById('uvf-controls-container');
1437
+ if (!controlsContainer) return;
1447
1438
 
1448
- 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
- };
1439
+ if (this.youtubePlayer && this.useYouTubeNativeControls) {
1440
+ // Hide custom controls and show YouTube native controls
1441
+ controlsContainer.style.display = 'none';
1475
1442
 
1476
- this.youtubeAvailableQualities = [];
1477
-
1478
- // Always add auto quality option first
1479
- this.youtubeAvailableQualities.push({ label: 'Auto', value: 'auto', height: 0 });
1480
-
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
1443
+ // Re-create YouTube player with native controls if the current player doesn't have them
1444
+ // This happens when the player was initially created with controls disabled
1445
+ if (this.config.youtubeNativeControls === false) {
1446
+ this.recreateYouTubePlayerWithNativeControls();
1493
1447
  }
1494
1448
 
1495
- this.debugLog('✅ Successfully mapped YouTube qualities:', this.youtubeAvailableQualities);
1449
+ this.debugLog('✅ YouTube native controls enabled', {
1450
+ isLive: this.isYouTubeLive,
1451
+ reason: this.config.youtubeNativeControls === true ? 'Explicitly enabled in config' : 'Live stream detected'
1452
+ });
1453
+ } else {
1454
+ // Show custom controls and ensure YouTube native controls are disabled
1455
+ controlsContainer.style.display = 'flex';
1496
1456
 
1497
- // Get current quality
1498
- try {
1499
- const currentQuality = this.youtubePlayer.getPlaybackQuality();
1500
- this.youtubeCurrentQuality = qualityMap[currentQuality] || qualityMap['auto'];
1501
- this.debugLog('Current YouTube quality:', this.youtubeCurrentQuality);
1502
- } catch (e) {
1503
- this.debugWarn('Could not get current YouTube quality:', e);
1504
- this.youtubeCurrentQuality = qualityMap['auto'];
1457
+ // Re-create YouTube player without native controls if needed
1458
+ if (this.config.youtubeNativeControls === true) {
1459
+ this.recreateYouTubePlayerWithoutNativeControls();
1505
1460
  }
1506
1461
 
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);
1528
- }
1462
+ this.debugLog('✅ Custom controls enabled for YouTube video');
1529
1463
  }
1530
1464
  }
1531
1465
 
1532
1466
  /**
1533
- * Set YouTube video quality
1467
+ * Recreate YouTube player with native controls enabled
1534
1468
  */
1535
- private setYouTubeQuality(qualityLevel: string): void {
1536
- if (!this.youtubePlayer) return;
1469
+ private recreateYouTubePlayerWithNativeControls(): void {
1470
+ if (!this.source?.metadata?.videoId) return;
1537
1471
 
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
- };
1550
-
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
- }
1602
- }
1603
-
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');
1472
+ const videoId = this.source.metadata.videoId;
1473
+ const currentTime = this.youtubePlayer?.getCurrentTime() || 0;
1474
+
1475
+ // Find the container
1476
+ const container = this.playerWrapper || this.video?.parentElement;
1477
+ if (!container) return;
1478
+
1479
+ // Destroy current player
1480
+ if (this.youtubePlayer) {
1481
+ this.youtubePlayer.destroy();
1482
+ }
1483
+
1484
+ // Remove existing iframe container
1485
+ const existingContainer = container.querySelector(`#youtube-player-${videoId}`);
1486
+ if (existingContainer) {
1487
+ existingContainer.remove();
1609
1488
  }
1489
+
1490
+ // Create new iframe container
1491
+ const iframeContainer = document.createElement('div');
1492
+ iframeContainer.id = `youtube-player-${videoId}`;
1493
+ iframeContainer.style.cssText = `
1494
+ position: absolute;
1495
+ top: 0;
1496
+ left: 0;
1497
+ width: 100%;
1498
+ height: 100%;
1499
+ z-index: 1;
1500
+ `;
1501
+ container.appendChild(iframeContainer);
1502
+
1503
+ // Create new player with native controls
1504
+ this.youtubePlayer = new window.YT.Player(iframeContainer.id, {
1505
+ videoId: videoId,
1506
+ width: '100%',
1507
+ height: '100%',
1508
+ playerVars: {
1509
+ autoplay: this.config.autoPlay ? 1 : 0,
1510
+ controls: 1, // Enable native controls
1511
+ modestbranding: 1,
1512
+ rel: 0,
1513
+ showinfo: 0,
1514
+ iv_load_policy: 3,
1515
+ playsinline: 1,
1516
+ start: Math.floor(currentTime)
1517
+ },
1518
+ events: {
1519
+ onReady: () => {
1520
+ this.youtubePlayerReady = true;
1521
+ this.debugLog('YouTube player with native controls ready');
1522
+ this.emit('onReady');
1523
+ },
1524
+ onStateChange: (event: any) => this.onYouTubePlayerStateChange(event),
1525
+ onError: (event: any) => this.onYouTubePlayerError(event)
1526
+ }
1527
+ });
1610
1528
  }
1611
1529
 
1612
1530
  /**
1613
- * Verify YouTube quality change with retry logic
1531
+ * Recreate YouTube player without native controls enabled
1614
1532
  */
1615
- private verifyYouTubeQualityChange(requestedQuality: string, qualityMap: Record<string, any>, attempt: number): void {
1616
- const maxAttempts = 3;
1617
- const delays = [500, 1500, 3000]; // Progressive delays
1533
+ private recreateYouTubePlayerWithoutNativeControls(): void {
1534
+ if (!this.source?.metadata?.videoId) return;
1618
1535
 
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
- }
1536
+ const videoId = this.source.metadata.videoId;
1537
+ const currentTime = this.youtubePlayer?.getCurrentTime() || 0;
1538
+
1539
+ // Find the container
1540
+ const container = this.playerWrapper || this.video?.parentElement;
1541
+ if (!container) return;
1542
+
1543
+ // Destroy current player
1544
+ if (this.youtubePlayer) {
1545
+ this.youtubePlayer.destroy();
1546
+ }
1547
+
1548
+ // Remove existing iframe container
1549
+ const existingContainer = container.querySelector(`#youtube-player-${videoId}`);
1550
+ if (existingContainer) {
1551
+ existingContainer.remove();
1552
+ }
1553
+
1554
+ // Create new iframe container
1555
+ const iframeContainer = document.createElement('div');
1556
+ iframeContainer.id = `youtube-player-${videoId}`;
1557
+ iframeContainer.style.cssText = `
1558
+ position: absolute;
1559
+ top: 0;
1560
+ left: 0;
1561
+ width: 100%;
1562
+ height: 100%;
1563
+ z-index: 1;
1564
+ `;
1565
+ container.appendChild(iframeContainer);
1566
+
1567
+ // Create new player without native controls
1568
+ this.youtubePlayer = new window.YT.Player(iframeContainer.id, {
1569
+ videoId: videoId,
1570
+ width: '100%',
1571
+ height: '100%',
1572
+ playerVars: {
1573
+ autoplay: this.config.autoPlay ? 1 : 0,
1574
+ controls: 0, // Disable native controls
1575
+ disablekb: 0,
1576
+ fs: 0,
1577
+ iv_load_policy: 3,
1578
+ modestbranding: 1,
1579
+ rel: 0,
1580
+ showinfo: 0,
1581
+ playsinline: 1,
1582
+ start: Math.floor(currentTime)
1583
+ },
1584
+ events: {
1585
+ onReady: () => {
1586
+ this.youtubePlayerReady = true;
1587
+ this.debugLog('YouTube player without native controls ready');
1588
+ this.emit('onReady');
1589
+ },
1590
+ onStateChange: (event: any) => this.onYouTubePlayerStateChange(event),
1591
+ onError: (event: any) => this.onYouTubePlayerError(event)
1674
1592
  }
1675
- }, delays[attempt] || 1000);
1593
+ });
1676
1594
  }
1677
1595
 
1678
1596
  /**
1679
- * Retry YouTube quality change with alternative methods
1597
+ * Toggle between native and custom YouTube controls
1680
1598
  */
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);
1599
+ public toggleYouTubeControls(useNative: boolean = !this.useYouTubeNativeControls): void {
1600
+ if (!this.youtubePlayer) {
1601
+ this.debugWarn('Cannot toggle YouTube controls - no YouTube player active');
1602
+ return;
1709
1603
  }
1604
+
1605
+ this.useYouTubeNativeControls = useNative;
1606
+ this.config.youtubeNativeControls = useNative; // Update config
1607
+ this.updateControlsVisibility();
1608
+
1609
+ this.debugLog('YouTube controls toggled:', {
1610
+ useNative: this.useYouTubeNativeControls,
1611
+ isLive: this.isYouTubeLive
1612
+ });
1613
+
1614
+ // Show notification
1615
+ this.showNotification(
1616
+ `YouTube Controls: ${this.useYouTubeNativeControls ? 'Native' : 'Custom'}`
1617
+ );
1710
1618
  }
1711
1619
 
1712
1620
  /**
1713
- * Update quality badge with current quality
1621
+ * Fallback method to get title from YouTube oembed API
1714
1622
  */
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';
1623
+ private async getYouTubeVideoTitleFromOEmbed(): Promise<void> {
1624
+ if (!this.source?.metadata?.videoId) return;
1625
+
1626
+ try {
1627
+ const videoId = this.source.metadata.videoId;
1628
+ const oembedUrl = `https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${videoId}&format=json`;
1629
+
1630
+ const response = await fetch(oembedUrl);
1631
+ if (response.ok) {
1632
+ const data = await response.json();
1633
+ if (data.title) {
1634
+ this.debugLog('Got YouTube title from oembed API:', data.title);
1635
+
1636
+ // Update source metadata
1637
+ if (this.source && this.source.metadata) {
1638
+ this.source.metadata.title = data.title;
1639
+ }
1640
+
1641
+ // Update UI
1642
+ this.updateMetadataUI();
1643
+ }
1644
+ }
1645
+ } catch (error) {
1646
+ this.debugWarn('Could not get YouTube title from oembed API:', error);
1731
1647
  }
1732
1648
  }
1733
1649
 
1650
+ private youtubeTimeTrackingInterval: NodeJS.Timeout | null = null;
1651
+ private isYouTubeLive: boolean = false;
1652
+ private useYouTubeNativeControls: boolean = false;
1653
+
1734
1654
  private startYouTubeTimeTracking(): void {
1735
1655
  if (this.youtubeTimeTrackingInterval) {
1736
1656
  clearInterval(this.youtubeTimeTrackingInterval);
1737
1657
  }
1738
1658
 
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
- }
1659
+ this.youtubeTimeTrackingInterval = setInterval(() => {
1660
+ if (this.youtubePlayer && this.youtubePlayerReady) {
1661
+ try {
1662
+ const currentTime = this.youtubePlayer.getCurrentTime();
1663
+ const duration = this.youtubePlayer.getDuration();
1664
+ const buffered = this.youtubePlayer.getVideoLoadedFraction() * 100;
1665
+
1666
+ this.state.currentTime = currentTime || 0;
1667
+ this.state.duration = duration || 0;
1668
+ this.state.bufferedPercentage = buffered || 0;
1759
1669
 
1760
1670
  // Update UI progress bar
1761
1671
  this.updateYouTubeProgressBar(currentTime, duration, buffered);
@@ -9536,23 +9446,15 @@ export class WebPlayer extends BasePlayer {
9536
9446
  private setQualityByLabel(quality: string): void {
9537
9447
  // Handle YouTube player
9538
9448
  if (this.youtubePlayer && this.youtubePlayerReady) {
9539
- // Set YouTube quality directly
9540
- this.setYouTubeQuality(quality);
9541
-
9542
- // Update UI to reflect selection
9449
+ // YouTube quality is limited - show notification
9450
+ this.showShortcutIndicator('Quality control not available for YouTube');
9451
+ // Update UI to reflect current quality
9543
9452
  document.querySelectorAll('.quality-option').forEach(option => {
9544
9453
  option.classList.remove('active');
9545
- if ((option as HTMLElement).dataset.quality === quality) {
9454
+ if ((option as HTMLElement).dataset.quality === 'auto') {
9546
9455
  option.classList.add('active');
9547
9456
  }
9548
9457
  });
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
9458
  return;
9557
9459
  }
9558
9460
 
@@ -9853,17 +9755,9 @@ export class WebPlayer extends BasePlayer {
9853
9755
 
9854
9756
  // Quality Accordion Section (only if enabled in config and qualities detected)
9855
9757
  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);
9758
+ const currentQuality = this.availableQualities.find(q => q.value === this.currentQuality);
9863
9759
  const currentQualityLabel = currentQuality ? currentQuality.label : 'Auto';
9864
9760
 
9865
- this.debugLog('🎛️ Current quality for display:', qualityForDisplay, '(', currentQualityLabel, ')');
9866
-
9867
9761
  menuHTML += `
9868
9762
  <div class="uvf-accordion-item">
9869
9763
  <div class="uvf-accordion-header" data-section="quality">
@@ -9881,11 +9775,7 @@ export class WebPlayer extends BasePlayer {
9881
9775
  <div class="uvf-accordion-content" data-section="quality">`;
9882
9776
 
9883
9777
  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' : '';
9778
+ const isActive = quality.value === this.currentQuality ? 'active' : '';
9889
9779
  const isPremium = this.isQualityPremium(quality);
9890
9780
  const isLocked = isPremium && !this.isPremiumUser();
9891
9781
  const qualityHeight = (quality as any).height || 0;
@@ -9953,22 +9843,10 @@ export class WebPlayer extends BasePlayer {
9953
9843
  * Detect available video qualities from different sources
9954
9844
  */
9955
9845
  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 = [];
9846
+ this.availableQualities = [{ value: 'auto', label: 'Auto' }];
9959
9847
  let detectedQualities: Array<{ value: string; label: string; height?: number }> = [];
9960
9848
 
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) {
9849
+ if (this.hls && this.hls.levels) {
9972
9850
  // HLS qualities
9973
9851
  this.hls.levels.forEach((level: any, index: number) => {
9974
9852
  if (level.height) {
@@ -10340,13 +10218,6 @@ export class WebPlayer extends BasePlayer {
10340
10218
  private setQualityFromSettings(quality: string): void {
10341
10219
  this.currentQuality = quality;
10342
10220
 
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
10221
  if (quality === 'auto') {
10351
10222
  // Enable auto quality with filter consideration
10352
10223
  if (this.hls) {