unified-video-framework 1.4.262 → 1.4.264

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.
@@ -179,13 +179,14 @@ export class WebPlayer extends BasePlayer {
179
179
  console.log('WebPlayer.initialize called with config:', config);
180
180
 
181
181
  // Set useCustomControls based on config before calling parent initialize
182
- // Check both customControls (specific) and controls (standard) options
182
+ // Priority: customControls > controls (for backward compatibility)
183
183
  if (config && config.customControls !== undefined) {
184
184
  this.useCustomControls = config.customControls;
185
- console.log('Custom controls set via customControls to:', this.useCustomControls);
185
+ console.log('Custom controls set via customControls:', this.useCustomControls);
186
186
  } else if (config && config.controls !== undefined) {
187
+ // Use 'controls' prop if customControls is not defined
187
188
  this.useCustomControls = config.controls;
188
- console.log('Custom controls set via controls to:', this.useCustomControls);
189
+ console.log('Custom controls set via controls prop:', this.useCustomControls);
189
190
  }
190
191
 
191
192
  // Configure settings menu options
@@ -292,9 +293,18 @@ export class WebPlayer extends BasePlayer {
292
293
  // Create custom controls if enabled
293
294
  if (this.useCustomControls) {
294
295
  this.createCustomControls(videoContainer);
296
+ } else {
297
+ // Hide controls if disabled
298
+ this.debugLog('Controls disabled - not creating custom controls UI');
295
299
  }
296
300
 
297
301
 
302
+ // Apply controls-disabled class if controls are disabled
303
+ if (!this.useCustomControls) {
304
+ wrapper.classList.add('controls-disabled');
305
+ this.debugLog('🚫 controls-disabled class applied to player wrapper');
306
+ }
307
+
298
308
  // Assemble the player
299
309
  wrapper.appendChild(videoContainer);
300
310
 
@@ -1119,20 +1129,11 @@ export class WebPlayer extends BasePlayer {
1119
1129
  if (this.video && metadata.thumbnail) {
1120
1130
  this.video.poster = metadata.thumbnail;
1121
1131
  }
1122
-
1123
- // Update metadata UI immediately with YouTube title and thumbnail
1124
- this.updateMetadataUI();
1125
1132
 
1126
1133
  // Create YouTube iframe player with custom controls integration
1127
1134
  await this.createYouTubePlayer(videoId);
1128
1135
 
1129
- // Force another metadata update after player creation
1130
- setTimeout(() => {
1131
- this.updateMetadataUI();
1132
- }, 1000);
1133
-
1134
1136
  this.debugLog('✅ YouTube video loaded successfully');
1135
- this.debugLog('YouTube video title:', metadata.title);
1136
1137
  } catch (error) {
1137
1138
  this.debugError('Failed to load YouTube video:', error);
1138
1139
  throw new Error(`YouTube video loading failed: ${error}`);
@@ -1154,18 +1155,16 @@ export class WebPlayer extends BasePlayer {
1154
1155
  this.video.style.display = 'none';
1155
1156
  }
1156
1157
 
1157
- // Create iframe container with proper z-index based on controls configuration
1158
+ // Create iframe container
1158
1159
  const iframeContainer = document.createElement('div');
1159
1160
  iframeContainer.id = `youtube-player-${videoId}`;
1160
- const useNativeControls = this.config.youtubeNativeControls === true;
1161
1161
  iframeContainer.style.cssText = `
1162
1162
  position: absolute;
1163
1163
  top: 0;
1164
1164
  left: 0;
1165
1165
  width: 100%;
1166
1166
  height: 100%;
1167
- z-index: ${useNativeControls ? '1' : '0'};
1168
- pointer-events: ${useNativeControls ? 'auto' : 'none'};
1167
+ z-index: 1;
1169
1168
  `;
1170
1169
 
1171
1170
  // Remove existing YouTube player if any
@@ -1190,7 +1189,7 @@ export class WebPlayer extends BasePlayer {
1190
1189
  width: '100%',
1191
1190
  height: '100%',
1192
1191
  playerVars: {
1193
- controls: this.config.youtubeNativeControls === true ? 1 : 0, // Native controls disabled by default for custom controls
1192
+ controls: 0, // Hide YouTube controls
1194
1193
  disablekb: 0, // Allow keyboard controls
1195
1194
  fs: 0, // Hide fullscreen button
1196
1195
  iv_load_policy: 3, // Hide annotations
@@ -1208,10 +1207,7 @@ export class WebPlayer extends BasePlayer {
1208
1207
  }
1209
1208
  });
1210
1209
 
1211
- // Track the initial controls state
1212
- this.currentYouTubeControlsState = this.config.youtubeNativeControls === true;
1213
-
1214
- this.debugLog('YouTube player created with controls:', this.currentYouTubeControlsState);
1210
+ this.debugLog('YouTube player created');
1215
1211
  }
1216
1212
 
1217
1213
  private async loadYouTubeAPI(): Promise<void> {
@@ -1260,20 +1256,6 @@ export class WebPlayer extends BasePlayer {
1260
1256
  if (this.config.muted) {
1261
1257
  this.youtubePlayer.mute();
1262
1258
  }
1263
-
1264
- // Detect if YouTube video is Live and handle controls
1265
- this.detectYouTubeLiveStatus();
1266
-
1267
- // Try to get video title from YouTube API
1268
- this.getYouTubeVideoTitle();
1269
-
1270
- // Update metadata UI and controls visibility after player is ready
1271
- setTimeout(() => {
1272
- this.updateMetadataUI();
1273
- this.updateControlsVisibility();
1274
- // Ensure UI elements have correct z-index for YouTube integration
1275
- this.ensureUILayering();
1276
- }, 500);
1277
1259
  }
1278
1260
 
1279
1261
  // Start time tracking
@@ -1288,10 +1270,10 @@ export class WebPlayer extends BasePlayer {
1288
1270
  switch (state) {
1289
1271
  case window.YT.PlayerState.PLAYING:
1290
1272
  this.state.isPlaying = true;
1291
- this.state.isPaused = false;
1292
- this.state.isBuffering = false;
1293
- this.updateYouTubeUI('playing');
1294
- this.emit('onPlay');
1273
+ this.state.isPaused = false;
1274
+ this.state.isBuffering = false;
1275
+ this.updateYouTubeUI('playing');
1276
+ this.emit('onPlay');
1295
1277
  break;
1296
1278
 
1297
1279
  case window.YT.PlayerState.PAUSED:
@@ -1302,10 +1284,10 @@ export class WebPlayer extends BasePlayer {
1302
1284
  this.emit('onPause');
1303
1285
  break;
1304
1286
 
1305
- case window.YT.PlayerState.BUFFERING:
1306
- this.state.isBuffering = true;
1307
- this.updateYouTubeUI('buffering');
1308
- this.emit('onBuffering', true);
1287
+ case window.YT.PlayerState.BUFFERING:
1288
+ this.state.isBuffering = true;
1289
+ this.updateYouTubeUI('buffering');
1290
+ this.emit('onBuffering', true);
1309
1291
  break;
1310
1292
 
1311
1293
  case window.YT.PlayerState.ENDED:
@@ -1316,16 +1298,10 @@ export class WebPlayer extends BasePlayer {
1316
1298
  this.emit('onEnded');
1317
1299
  break;
1318
1300
 
1319
- case window.YT.PlayerState.CUED:
1320
- this.state.duration = this.youtubePlayer.getDuration();
1321
- this.updateYouTubeUI('cued');
1322
-
1323
- // Re-check Live status when video is cued
1324
- setTimeout(() => {
1325
- this.detectYouTubeLiveStatus();
1326
- this.updateControlsVisibility();
1327
- }, 500);
1328
- break;
1301
+ case window.YT.PlayerState.CUED:
1302
+ this.state.duration = this.youtubePlayer.getDuration();
1303
+ this.updateYouTubeUI('cued');
1304
+ break;
1329
1305
  }
1330
1306
  }
1331
1307
 
@@ -1374,446 +1350,23 @@ export class WebPlayer extends BasePlayer {
1374
1350
  });
1375
1351
  }
1376
1352
 
1377
- /**
1378
- * Get YouTube video title from the player API
1379
- */
1380
- private getYouTubeVideoTitle(): void {
1381
- if (!this.youtubePlayer || !this.youtubePlayerReady) return;
1382
-
1383
- try {
1384
- // Try to get video data from YouTube player
1385
- const videoData = this.youtubePlayer.getVideoData();
1386
- if (videoData && videoData.title) {
1387
- this.debugLog('Got YouTube title from player API:', videoData.title);
1388
-
1389
- // Update source metadata with the correct title
1390
- if (this.source && this.source.metadata) {
1391
- this.source.metadata.title = videoData.title;
1392
- }
1393
-
1394
- // Update UI immediately
1395
- this.updateMetadataUI();
1396
- }
1397
- } catch (error) {
1398
- this.debugWarn('Could not get YouTube video title from API:', error);
1399
-
1400
- // Fallback: Try to get from oembed API
1401
- this.getYouTubeVideoTitleFromOEmbed();
1402
- }
1403
- }
1404
-
1405
- /**
1406
- * Detect if YouTube video is Live
1407
- */
1408
- private detectYouTubeLiveStatus(): void {
1409
- if (!this.youtubePlayer || !this.youtubePlayerReady) return;
1410
-
1411
- try {
1412
- const videoData = this.youtubePlayer.getVideoData();
1413
- this.isYouTubeLive = videoData?.isLive || false;
1414
-
1415
- // Use custom controls by default, unless youtubeNativeControls is explicitly set to true
1416
- this.useYouTubeNativeControls = this.config.youtubeNativeControls === true;
1417
-
1418
- this.debugLog('YouTube Live status:', {
1419
- isLive: this.isYouTubeLive,
1420
- useNativeControls: this.useYouTubeNativeControls,
1421
- videoId: videoData?.video_id
1422
- });
1423
-
1424
- } catch (error) {
1425
- this.debugWarn('Could not detect YouTube Live status:', error);
1426
- // Fallback: check duration - Live videos typically have duration = 0
1427
- try {
1428
- const duration = this.youtubePlayer.getDuration();
1429
- this.isYouTubeLive = !duration || duration === 0;
1430
- this.useYouTubeNativeControls = this.config.youtubeNativeControls === true;
1431
-
1432
- this.debugLog('YouTube Live detected via duration check:', {
1433
- duration,
1434
- isLive: this.isYouTubeLive,
1435
- useNativeControls: this.useYouTubeNativeControls
1436
- });
1437
- } catch (e) {
1438
- this.debugWarn('Could not check YouTube duration for Live detection:', e);
1439
- }
1440
- }
1441
- }
1442
-
1443
- /**
1444
- * Ensure all UI elements have correct z-index for proper layering
1445
- */
1446
- private ensureUILayering(): void {
1447
- if (!this.youtubePlayer) return;
1448
-
1449
- const videoContainer = this.playerWrapper?.querySelector('.uvf-video-container') as HTMLElement;
1450
- if (!videoContainer) return;
1451
-
1452
- // Ensure all custom UI elements are above the YouTube iframe
1453
- const uiElements = videoContainer.querySelectorAll('.uvf-top-gradient, .uvf-controls-gradient, .uvf-top-bar, .uvf-center-play-container, .uvf-shortcut-indicator, .uvf-controls-bar');
1454
- uiElements.forEach(el => {
1455
- const element = el as HTMLElement;
1456
- element.style.zIndex = '2';
1457
- element.style.position = 'relative';
1458
- });
1459
-
1460
- this.debugLog('UI layering ensured for YouTube player');
1461
- }
1462
-
1463
- /**
1464
- * Update controls visibility based on YouTube Live status and config
1465
- */
1466
- private updateControlsVisibility(): void {
1467
- const controlsContainer = document.getElementById('uvf-controls');
1468
- if (!controlsContainer) {
1469
- this.debugWarn('Controls container not found, looking for .uvf-controls-bar');
1470
- const controlsBar = this.playerWrapper?.querySelector('.uvf-controls-bar') as HTMLElement;
1471
- if (!controlsBar) {
1472
- this.debugWarn('Controls bar not found either, cannot update controls visibility');
1473
- return;
1474
- }
1475
- // Use the controls bar as fallback
1476
- const controlsContainerFallback = controlsBar;
1477
-
1478
- if (this.youtubePlayer && this.useYouTubeNativeControls) {
1479
- // Hide custom controls
1480
- controlsContainerFallback.style.display = 'none';
1481
-
1482
- // Lower YouTube wrapper z-index and pointer events appropriately
1483
- const videoId = this.source?.metadata?.videoId;
1484
- if (videoId) {
1485
- const ytWrapper = this.playerWrapper?.querySelector(`#youtube-player-${videoId}`) as HTMLElement;
1486
- if (ytWrapper) {
1487
- ytWrapper.style.zIndex = '1';
1488
- ytWrapper.style.pointerEvents = 'auto';
1489
- ytWrapper.style.position = 'absolute';
1490
- }
1491
- }
1492
-
1493
- // Also hide any other control elements
1494
- const videoContainer = this.playerWrapper?.querySelector('.uvf-video-container') as HTMLElement;
1495
- if (videoContainer) {
1496
- const allControls = videoContainer.querySelectorAll('.uvf-top-gradient, .uvf-controls-gradient, .uvf-top-bar, .uvf-center-play-container, .uvf-shortcut-indicator');
1497
- allControls.forEach(el => (el as HTMLElement).style.display = 'none');
1498
- }
1499
-
1500
- // Only recreate YouTube player if it doesn't currently have native controls
1501
- if (this.currentYouTubeControlsState !== true) {
1502
- this.recreateYouTubePlayerWithNativeControls();
1503
- this.currentYouTubeControlsState = true;
1504
- }
1505
-
1506
- this.debugLog('✅ YouTube native controls enabled', {
1507
- isLive: this.isYouTubeLive,
1508
- reason: this.config.youtubeNativeControls === true ? 'Explicitly enabled in config' : 'Live stream detected'
1509
- });
1510
- } else {
1511
- // Show custom controls
1512
- controlsContainerFallback.style.display = 'flex';
1513
- controlsContainerFallback.style.zIndex = '2';
1514
-
1515
- // Raise YouTube wrapper below UI and disable pointer events so UI is clickable
1516
- const videoId = this.source?.metadata?.videoId;
1517
- if (videoId) {
1518
- const ytWrapper = this.playerWrapper?.querySelector(`#youtube-player-${videoId}`) as HTMLElement;
1519
- if (ytWrapper) {
1520
- ytWrapper.style.zIndex = '0';
1521
- ytWrapper.style.pointerEvents = 'none';
1522
- ytWrapper.style.position = 'absolute';
1523
- }
1524
- }
1525
-
1526
- // Also show other control elements and ensure they are above the iframe
1527
- const videoContainer = this.playerWrapper?.querySelector('.uvf-video-container') as HTMLElement;
1528
- if (videoContainer) {
1529
- const allControls = videoContainer.querySelectorAll('.uvf-top-gradient, .uvf-controls-gradient, .uvf-top-bar, .uvf-center-play-container, .uvf-shortcut-indicator');
1530
- allControls.forEach(el => {
1531
- const elh = el as HTMLElement;
1532
- elh.style.display = '';
1533
- elh.style.zIndex = '2';
1534
- elh.style.position = 'relative';
1535
- });
1536
- }
1537
-
1538
- // Only recreate YouTube player if it currently has native controls
1539
- if (this.currentYouTubeControlsState !== false) {
1540
- this.recreateYouTubePlayerWithoutNativeControls();
1541
- this.currentYouTubeControlsState = false;
1542
- }
1543
-
1544
- this.debugLog('✅ Custom controls enabled for YouTube video');
1545
- }
1546
- return;
1547
- }
1548
-
1549
- if (this.youtubePlayer && this.useYouTubeNativeControls) {
1550
- // Hide custom controls and show YouTube native controls
1551
- controlsContainer.style.display = 'none';
1552
-
1553
- // Lower YouTube wrapper z-index and enable pointer events
1554
- const videoId = this.source?.metadata?.videoId;
1555
- if (videoId) {
1556
- const ytWrapper = this.playerWrapper?.querySelector(`#youtube-player-${videoId}`) as HTMLElement;
1557
- if (ytWrapper) {
1558
- ytWrapper.style.zIndex = '1';
1559
- ytWrapper.style.pointerEvents = 'auto';
1560
- ytWrapper.style.position = 'absolute';
1561
- }
1562
- }
1563
-
1564
- // Only recreate YouTube player if it doesn't currently have native controls
1565
- if (this.currentYouTubeControlsState !== true) {
1566
- this.recreateYouTubePlayerWithNativeControls();
1567
- this.currentYouTubeControlsState = true;
1568
- }
1569
-
1570
- this.debugLog('✅ YouTube native controls enabled', {
1571
- isLive: this.isYouTubeLive,
1572
- reason: this.config.youtubeNativeControls === true ? 'Explicitly enabled in config' : 'Live stream detected'
1573
- });
1574
- } else {
1575
- // Show custom controls and ensure YouTube native controls are disabled
1576
- controlsContainer.style.display = 'flex';
1577
- controlsContainer.style.zIndex = '2';
1578
- controlsContainer.style.position = 'relative';
1579
-
1580
- // Ensure YouTube iframe is behind controls and disable pointer events
1581
- const videoId = this.source?.metadata?.videoId;
1582
- if (videoId) {
1583
- const ytWrapper = this.playerWrapper?.querySelector(`#youtube-player-${videoId}`) as HTMLElement;
1584
- if (ytWrapper) {
1585
- ytWrapper.style.zIndex = '0';
1586
- ytWrapper.style.pointerEvents = 'none';
1587
- ytWrapper.style.position = 'absolute';
1588
- }
1589
- }
1590
-
1591
- // Ensure other UI elements are also above the iframe
1592
- const videoContainer = this.playerWrapper?.querySelector('.uvf-video-container') as HTMLElement;
1593
- if (videoContainer) {
1594
- const allControls = videoContainer.querySelectorAll('.uvf-top-gradient, .uvf-controls-gradient, .uvf-top-bar, .uvf-center-play-container, .uvf-shortcut-indicator');
1595
- allControls.forEach(el => {
1596
- const elh = el as HTMLElement;
1597
- elh.style.zIndex = '2';
1598
- elh.style.position = 'relative';
1599
- });
1600
- }
1601
-
1602
- // Only recreate YouTube player if it currently has native controls
1603
- if (this.currentYouTubeControlsState !== false) {
1604
- this.recreateYouTubePlayerWithoutNativeControls();
1605
- this.currentYouTubeControlsState = false;
1606
- }
1607
-
1608
- this.debugLog('✅ Custom controls enabled for YouTube video');
1609
- }
1610
- }
1611
-
1612
- /**
1613
- * Recreate YouTube player with native controls enabled
1614
- */
1615
- private recreateYouTubePlayerWithNativeControls(): void {
1616
- if (!this.source?.metadata?.videoId) return;
1617
-
1618
- const videoId = this.source.metadata.videoId;
1619
- const currentTime = this.youtubePlayer?.getCurrentTime() || 0;
1620
-
1621
- // Find the container
1622
- const container = this.playerWrapper || this.video?.parentElement;
1623
- if (!container) return;
1624
-
1625
- // Destroy current player
1626
- if (this.youtubePlayer) {
1627
- this.youtubePlayer.destroy();
1628
- }
1629
-
1630
- // Remove existing iframe container
1631
- const existingContainer = container.querySelector(`#youtube-player-${videoId}`);
1632
- if (existingContainer) {
1633
- existingContainer.remove();
1634
- }
1635
-
1636
- // Create new iframe container
1637
- const iframeContainer = document.createElement('div');
1638
- iframeContainer.id = `youtube-player-${videoId}`;
1639
- iframeContainer.style.cssText = `
1640
- position: absolute;
1641
- top: 0;
1642
- left: 0;
1643
- width: 100%;
1644
- height: 100%;
1645
- z-index: 1;
1646
- `;
1647
- container.appendChild(iframeContainer);
1648
-
1649
- // Create new player with native controls
1650
- this.youtubePlayer = new window.YT.Player(iframeContainer.id, {
1651
- videoId: videoId,
1652
- width: '100%',
1653
- height: '100%',
1654
- playerVars: {
1655
- autoplay: this.config.autoPlay ? 1 : 0,
1656
- controls: 1, // Enable native controls
1657
- modestbranding: 1,
1658
- rel: 0,
1659
- showinfo: 0,
1660
- iv_load_policy: 3,
1661
- playsinline: 1,
1662
- start: Math.floor(currentTime)
1663
- },
1664
- events: {
1665
- onReady: () => {
1666
- this.youtubePlayerReady = true;
1667
- this.debugLog('YouTube player with native controls ready');
1668
- this.emit('onReady');
1669
- },
1670
- onStateChange: (event: any) => this.onYouTubePlayerStateChange(event),
1671
- onError: (event: any) => this.onYouTubePlayerError(event)
1672
- }
1673
- });
1674
- }
1675
-
1676
- /**
1677
- * Recreate YouTube player without native controls enabled
1678
- */
1679
- private recreateYouTubePlayerWithoutNativeControls(): void {
1680
- if (!this.source?.metadata?.videoId) return;
1681
-
1682
- const videoId = this.source.metadata.videoId;
1683
- const currentTime = this.youtubePlayer?.getCurrentTime() || 0;
1684
-
1685
- // Find the container
1686
- const container = this.playerWrapper || this.video?.parentElement;
1687
- if (!container) return;
1688
-
1689
- // Destroy current player
1690
- if (this.youtubePlayer) {
1691
- this.youtubePlayer.destroy();
1692
- }
1693
-
1694
- // Remove existing iframe container
1695
- const existingContainer = container.querySelector(`#youtube-player-${videoId}`);
1696
- if (existingContainer) {
1697
- existingContainer.remove();
1698
- }
1699
-
1700
- // Create new iframe container
1701
- const iframeContainer = document.createElement('div');
1702
- iframeContainer.id = `youtube-player-${videoId}`;
1703
- iframeContainer.style.cssText = `
1704
- position: absolute;
1705
- top: 0;
1706
- left: 0;
1707
- width: 100%;
1708
- height: 100%;
1709
- z-index: 0;
1710
- pointer-events: none;
1711
- `;
1712
- container.appendChild(iframeContainer);
1713
-
1714
- // Create new player without native controls
1715
- this.youtubePlayer = new window.YT.Player(iframeContainer.id, {
1716
- videoId: videoId,
1717
- width: '100%',
1718
- height: '100%',
1719
- playerVars: {
1720
- autoplay: this.config.autoPlay ? 1 : 0,
1721
- controls: 0, // Disable native controls
1722
- disablekb: 0,
1723
- fs: 0,
1724
- iv_load_policy: 3,
1725
- modestbranding: 1,
1726
- rel: 0,
1727
- showinfo: 0,
1728
- playsinline: 1,
1729
- start: Math.floor(currentTime)
1730
- },
1731
- events: {
1732
- onReady: () => {
1733
- this.youtubePlayerReady = true;
1734
- this.debugLog('YouTube player without native controls ready');
1735
- this.emit('onReady');
1736
- },
1737
- onStateChange: (event: any) => this.onYouTubePlayerStateChange(event),
1738
- onError: (event: any) => this.onYouTubePlayerError(event)
1739
- }
1740
- });
1741
- }
1742
-
1743
- /**
1744
- * Toggle between native and custom YouTube controls
1745
- */
1746
- public toggleYouTubeControls(useNative: boolean = !this.useYouTubeNativeControls): void {
1747
- if (!this.youtubePlayer) {
1748
- this.debugWarn('Cannot toggle YouTube controls - no YouTube player active');
1749
- return;
1750
- }
1751
-
1752
- this.useYouTubeNativeControls = useNative;
1753
- this.config.youtubeNativeControls = useNative; // Update config
1754
- this.updateControlsVisibility();
1755
-
1756
- this.debugLog('YouTube controls toggled:', {
1757
- useNative: this.useYouTubeNativeControls,
1758
- isLive: this.isYouTubeLive
1759
- });
1760
-
1761
- // Show notification
1762
- this.showNotification(
1763
- `YouTube Controls: ${this.useYouTubeNativeControls ? 'Native' : 'Custom'}`
1764
- );
1765
- }
1766
-
1767
- /**
1768
- * Fallback method to get title from YouTube oembed API
1769
- */
1770
- private async getYouTubeVideoTitleFromOEmbed(): Promise<void> {
1771
- if (!this.source?.metadata?.videoId) return;
1772
-
1773
- try {
1774
- const videoId = this.source.metadata.videoId;
1775
- const oembedUrl = `https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${videoId}&format=json`;
1776
-
1777
- const response = await fetch(oembedUrl);
1778
- if (response.ok) {
1779
- const data = await response.json();
1780
- if (data.title) {
1781
- this.debugLog('Got YouTube title from oembed API:', data.title);
1782
-
1783
- // Update source metadata
1784
- if (this.source && this.source.metadata) {
1785
- this.source.metadata.title = data.title;
1786
- }
1787
-
1788
- // Update UI
1789
- this.updateMetadataUI();
1790
- }
1791
- }
1792
- } catch (error) {
1793
- this.debugWarn('Could not get YouTube title from oembed API:', error);
1794
- }
1795
- }
1796
-
1797
1353
  private youtubeTimeTrackingInterval: NodeJS.Timeout | null = null;
1798
- private isYouTubeLive: boolean = false;
1799
- private useYouTubeNativeControls: boolean = false;
1800
- private currentYouTubeControlsState: boolean | null = null; // Track current YouTube player controls state
1801
1354
 
1802
1355
  private startYouTubeTimeTracking(): void {
1803
1356
  if (this.youtubeTimeTrackingInterval) {
1804
1357
  clearInterval(this.youtubeTimeTrackingInterval);
1805
1358
  }
1806
1359
 
1807
- this.youtubeTimeTrackingInterval = setInterval(() => {
1808
- if (this.youtubePlayer && this.youtubePlayerReady) {
1809
- try {
1810
- const currentTime = this.youtubePlayer.getCurrentTime();
1811
- const duration = this.youtubePlayer.getDuration();
1812
- const buffered = this.youtubePlayer.getVideoLoadedFraction() * 100;
1813
-
1814
- this.state.currentTime = currentTime || 0;
1815
- this.state.duration = duration || 0;
1816
- this.state.bufferedPercentage = buffered || 0;
1360
+ this.youtubeTimeTrackingInterval = setInterval(() => {
1361
+ if (this.youtubePlayer && this.youtubePlayerReady) {
1362
+ try {
1363
+ const currentTime = this.youtubePlayer.getCurrentTime();
1364
+ const duration = this.youtubePlayer.getDuration();
1365
+ const buffered = this.youtubePlayer.getVideoLoadedFraction() * 100;
1366
+
1367
+ this.state.currentTime = currentTime || 0;
1368
+ this.state.duration = duration || 0;
1369
+ this.state.bufferedPercentage = buffered || 0;
1817
1370
 
1818
1371
  // Update UI progress bar
1819
1372
  this.updateYouTubeProgressBar(currentTime, duration, buffered);
@@ -1832,18 +1385,16 @@ export class WebPlayer extends BasePlayer {
1832
1385
 
1833
1386
  const percent = (currentTime / duration) * 100;
1834
1387
 
1835
- // Update progress filled (only if not dragging)
1388
+ // Update progress filled
1836
1389
  const progressFilled = document.getElementById('uvf-progress-filled') as HTMLElement;
1837
1390
  if (progressFilled && !this.isDragging) {
1838
1391
  progressFilled.style.width = percent + '%';
1839
1392
  }
1840
1393
 
1841
- // Update progress handle (only if not dragging)
1394
+ // Update progress handle
1842
1395
  const progressHandle = document.getElementById('uvf-progress-handle') as HTMLElement;
1843
1396
  if (progressHandle && !this.isDragging) {
1844
1397
  progressHandle.style.left = percent + '%';
1845
- // Remove dragging class if it was set
1846
- progressHandle.classList.remove('dragging');
1847
1398
  }
1848
1399
 
1849
1400
  // Update buffered progress
@@ -1852,13 +1403,8 @@ export class WebPlayer extends BasePlayer {
1852
1403
  progressBuffered.style.width = buffered + '%';
1853
1404
  }
1854
1405
 
1855
- // Update time display with YouTube-specific times
1856
- const timeDisplay = document.getElementById('uvf-time-display');
1857
- if (timeDisplay) {
1858
- const currentTimeStr = this.formatTime(currentTime);
1859
- const durationStr = this.formatTime(duration);
1860
- timeDisplay.textContent = `${currentTimeStr} / ${durationStr}`;
1861
- }
1406
+ // Update time display
1407
+ this.updateTimeDisplay();
1862
1408
  }
1863
1409
 
1864
1410
 
@@ -2303,27 +1849,12 @@ export class WebPlayer extends BasePlayer {
2303
1849
  private updateTimeTooltip(e: MouseEvent): void {
2304
1850
  const progressBar = document.getElementById('uvf-progress-bar');
2305
1851
  const tooltip = document.getElementById('uvf-time-tooltip');
2306
- if (!progressBar || !tooltip) return;
2307
-
2308
- // Get duration from appropriate source
2309
- let duration = 0;
2310
- if (this.youtubePlayer && this.youtubePlayerReady) {
2311
- try {
2312
- duration = this.youtubePlayer.getDuration() || 0;
2313
- } catch (error) {
2314
- this.debugWarn('Error getting YouTube duration for tooltip:', error);
2315
- return;
2316
- }
2317
- } else if (this.video) {
2318
- duration = this.video.duration || 0;
2319
- }
2320
-
2321
- if (!duration || !isFinite(duration)) return;
1852
+ if (!progressBar || !tooltip || !this.video) return;
2322
1853
 
2323
1854
  const rect = progressBar.getBoundingClientRect();
2324
1855
  const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
2325
1856
  const percent = (x / rect.width);
2326
- const time = percent * duration;
1857
+ const time = percent * this.video.duration;
2327
1858
 
2328
1859
  // Update tooltip content and position
2329
1860
  tooltip.textContent = this.formatTime(time);
@@ -4978,29 +4509,7 @@ export class WebPlayer extends BasePlayer {
4978
4509
  }
4979
4510
 
4980
4511
  .uvf-accordion-item.expanded .uvf-accordion-content {
4981
- max-height: 350px;
4982
- overflow-y: auto;
4983
- -webkit-overflow-scrolling: touch;
4984
- }
4985
-
4986
- /* Special handling for quality accordion with many options */
4987
- .uvf-accordion-item.expanded .uvf-accordion-content[data-section="quality"] {
4988
- max-height: 400px;
4989
- }
4990
-
4991
- /* Scrollbar styling for accordion content */
4992
- .uvf-accordion-content::-webkit-scrollbar {
4993
- width: 4px;
4994
- }
4995
- .uvf-accordion-content::-webkit-scrollbar-track {
4996
- background: transparent;
4997
- }
4998
- .uvf-accordion-content::-webkit-scrollbar-thumb {
4999
- background: rgba(255,255,255,0.3);
5000
- border-radius: 2px;
5001
- }
5002
- .uvf-accordion-content::-webkit-scrollbar-thumb:hover {
5003
- background: rgba(255,255,255,0.5);
4512
+ max-height: 250px;
5004
4513
  }
5005
4514
 
5006
4515
  /* Settings options within accordion */
@@ -6728,6 +6237,25 @@ export class WebPlayer extends BasePlayer {
6728
6237
  }
6729
6238
  }
6730
6239
 
6240
+ /* Controls visibility */
6241
+ .uvf-controls-bar {
6242
+ display: flex !important;
6243
+ }
6244
+
6245
+ .uvf-top-bar {
6246
+ display: flex !important;
6247
+ }
6248
+
6249
+ /* Hide all controls if controls prop is false */
6250
+ .uvf-player-wrapper.controls-disabled .uvf-controls-bar,
6251
+ .uvf-player-wrapper.controls-disabled .uvf-top-bar,
6252
+ .uvf-player-wrapper.controls-disabled .uvf-top-gradient,
6253
+ .uvf-player-wrapper.controls-disabled .uvf-controls-gradient {
6254
+ display: none !important;
6255
+ visibility: hidden !important;
6256
+ pointer-events: none !important;
6257
+ }
6258
+
6731
6259
  /* Hide YouTube UI elements */
6732
6260
  iframe[src*="youtube.com"] {
6733
6261
  pointer-events: auto !important;
@@ -7161,7 +6689,7 @@ export class WebPlayer extends BasePlayer {
7161
6689
  const qualityBadge = document.createElement('div');
7162
6690
  qualityBadge.className = 'uvf-quality-badge';
7163
6691
  qualityBadge.id = 'uvf-quality-badge';
7164
- qualityBadge.textContent = 'AUTO'; // Default to AUTO for better UX
6692
+ qualityBadge.textContent = 'HD';
7165
6693
  rightControls.appendChild(qualityBadge);
7166
6694
 
7167
6695
  // Settings button with menu (show only if enabled)
@@ -8518,27 +8046,12 @@ export class WebPlayer extends BasePlayer {
8518
8046
  const progressBar = document.querySelector('.uvf-progress-bar') as HTMLElement;
8519
8047
  const progressFilled = document.getElementById('uvf-progress-filled') as HTMLElement;
8520
8048
  const progressHandle = document.getElementById('uvf-progress-handle') as HTMLElement;
8521
- if (!progressBar) return;
8522
-
8523
- // Get duration from appropriate source
8524
- let duration = 0;
8525
- if (this.youtubePlayer && this.youtubePlayerReady) {
8526
- try {
8527
- duration = this.youtubePlayer.getDuration() || 0;
8528
- } catch (error) {
8529
- this.debugWarn('Error getting YouTube duration for seeking:', error);
8530
- return;
8531
- }
8532
- } else if (this.video) {
8533
- duration = this.video.duration;
8534
- } else {
8535
- this.debugWarn('No video source available for seeking');
8536
- return;
8537
- }
8049
+ if (!progressBar || !this.video) return;
8538
8050
 
8051
+ const duration = this.video.duration;
8539
8052
  // Validate duration before calculating seek time
8540
8053
  if (!isFinite(duration) || isNaN(duration) || duration <= 0) {
8541
- this.debugWarn('Invalid video duration, cannot seek via progress bar:', duration);
8054
+ this.debugWarn('Invalid video duration, cannot seek via progress bar');
8542
8055
  return;
8543
8056
  }
8544
8057
 
@@ -8553,24 +8066,21 @@ export class WebPlayer extends BasePlayer {
8553
8066
  return;
8554
8067
  }
8555
8068
 
8556
- this.debugLog('Seeking to position:', time, 'seconds (', Math.round(percent), '%)');
8557
-
8558
8069
  // Update UI immediately for responsive feedback
8559
- if (progressFilled && !this.isDragging) {
8070
+ if (progressFilled) {
8560
8071
  progressFilled.style.width = percent + '%';
8561
8072
  }
8562
- if (progressHandle && !this.isDragging) {
8073
+ if (progressHandle) {
8563
8074
  progressHandle.style.left = percent + '%';
8564
- progressHandle.classList.add('dragging');
8075
+ // Add dragging class for visual feedback
8076
+ if (this.isDragging) {
8077
+ progressHandle.classList.add('dragging');
8078
+ } else {
8079
+ progressHandle.classList.remove('dragging');
8080
+ }
8565
8081
  }
8566
8082
 
8567
- // Perform the actual seek
8568
8083
  this.seek(time);
8569
-
8570
- // For YouTube, provide immediate visual feedback since API might be delayed
8571
- if (this.youtubePlayer && this.youtubePlayerReady) {
8572
- this.emit('onSeeking');
8573
- }
8574
8084
  }
8575
8085
 
8576
8086
  private formatTime(seconds: number): string {