unified-video-framework 1.4.261 → 1.4.263
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.
- package/package.json +1 -1
- package/packages/core/dist/interfaces/IVideoPlayer.d.ts +0 -1
- package/packages/core/dist/interfaces/IVideoPlayer.d.ts.map +1 -1
- package/packages/core/dist/interfaces.d.ts +0 -1
- package/packages/core/dist/interfaces.d.ts.map +1 -1
- package/packages/core/src/interfaces/IVideoPlayer.ts +0 -3
- package/packages/core/src/interfaces.ts +0 -1
- package/packages/web/dist/WebPlayer.d.ts +0 -10
- package/packages/web/dist/WebPlayer.d.ts.map +1 -1
- package/packages/web/dist/WebPlayer.js +19 -349
- package/packages/web/dist/WebPlayer.js.map +1 -1
- package/packages/web/dist/react/WebPlayerView.d.ts +1 -1
- package/packages/web/dist/react/WebPlayerView.d.ts.map +1 -1
- package/packages/web/dist/react/WebPlayerView.js +1 -2
- package/packages/web/dist/react/WebPlayerView.js.map +1 -1
- package/packages/web/src/WebPlayer.ts +44 -477
- package/packages/web/src/react/WebPlayerView.tsx +2 -5
|
@@ -179,13 +179,9 @@ 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
|
|
183
182
|
if (config && config.customControls !== undefined) {
|
|
184
183
|
this.useCustomControls = config.customControls;
|
|
185
|
-
console.log('Custom controls set
|
|
186
|
-
} else if (config && config.controls !== undefined) {
|
|
187
|
-
this.useCustomControls = config.controls;
|
|
188
|
-
console.log('Custom controls set via controls to:', this.useCustomControls);
|
|
184
|
+
console.log('Custom controls set to:', this.useCustomControls);
|
|
189
185
|
}
|
|
190
186
|
|
|
191
187
|
// Configure settings menu options
|
|
@@ -1119,20 +1115,11 @@ export class WebPlayer extends BasePlayer {
|
|
|
1119
1115
|
if (this.video && metadata.thumbnail) {
|
|
1120
1116
|
this.video.poster = metadata.thumbnail;
|
|
1121
1117
|
}
|
|
1122
|
-
|
|
1123
|
-
// Update metadata UI immediately with YouTube title and thumbnail
|
|
1124
|
-
this.updateMetadataUI();
|
|
1125
1118
|
|
|
1126
1119
|
// Create YouTube iframe player with custom controls integration
|
|
1127
1120
|
await this.createYouTubePlayer(videoId);
|
|
1128
1121
|
|
|
1129
|
-
// Force another metadata update after player creation
|
|
1130
|
-
setTimeout(() => {
|
|
1131
|
-
this.updateMetadataUI();
|
|
1132
|
-
}, 1000);
|
|
1133
|
-
|
|
1134
1122
|
this.debugLog('✅ YouTube video loaded successfully');
|
|
1135
|
-
this.debugLog('YouTube video title:', metadata.title);
|
|
1136
1123
|
} catch (error) {
|
|
1137
1124
|
this.debugError('Failed to load YouTube video:', error);
|
|
1138
1125
|
throw new Error(`YouTube video loading failed: ${error}`);
|
|
@@ -1188,7 +1175,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
1188
1175
|
width: '100%',
|
|
1189
1176
|
height: '100%',
|
|
1190
1177
|
playerVars: {
|
|
1191
|
-
controls:
|
|
1178
|
+
controls: 0, // Hide YouTube controls
|
|
1192
1179
|
disablekb: 0, // Allow keyboard controls
|
|
1193
1180
|
fs: 0, // Hide fullscreen button
|
|
1194
1181
|
iv_load_policy: 3, // Hide annotations
|
|
@@ -1206,10 +1193,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
1206
1193
|
}
|
|
1207
1194
|
});
|
|
1208
1195
|
|
|
1209
|
-
|
|
1210
|
-
this.currentYouTubeControlsState = this.config.youtubeNativeControls === true;
|
|
1211
|
-
|
|
1212
|
-
this.debugLog('YouTube player created with controls:', this.currentYouTubeControlsState);
|
|
1196
|
+
this.debugLog('YouTube player created');
|
|
1213
1197
|
}
|
|
1214
1198
|
|
|
1215
1199
|
private async loadYouTubeAPI(): Promise<void> {
|
|
@@ -1258,18 +1242,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
1258
1242
|
if (this.config.muted) {
|
|
1259
1243
|
this.youtubePlayer.mute();
|
|
1260
1244
|
}
|
|
1261
|
-
|
|
1262
|
-
// Detect if YouTube video is Live and handle controls
|
|
1263
|
-
this.detectYouTubeLiveStatus();
|
|
1264
|
-
|
|
1265
|
-
// Try to get video title from YouTube API
|
|
1266
|
-
this.getYouTubeVideoTitle();
|
|
1267
|
-
|
|
1268
|
-
// Update metadata UI and controls visibility after player is ready
|
|
1269
|
-
setTimeout(() => {
|
|
1270
|
-
this.updateMetadataUI();
|
|
1271
|
-
this.updateControlsVisibility();
|
|
1272
|
-
}, 500);
|
|
1273
1245
|
}
|
|
1274
1246
|
|
|
1275
1247
|
// Start time tracking
|
|
@@ -1284,10 +1256,10 @@ export class WebPlayer extends BasePlayer {
|
|
|
1284
1256
|
switch (state) {
|
|
1285
1257
|
case window.YT.PlayerState.PLAYING:
|
|
1286
1258
|
this.state.isPlaying = true;
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1259
|
+
this.state.isPaused = false;
|
|
1260
|
+
this.state.isBuffering = false;
|
|
1261
|
+
this.updateYouTubeUI('playing');
|
|
1262
|
+
this.emit('onPlay');
|
|
1291
1263
|
break;
|
|
1292
1264
|
|
|
1293
1265
|
case window.YT.PlayerState.PAUSED:
|
|
@@ -1298,10 +1270,10 @@ export class WebPlayer extends BasePlayer {
|
|
|
1298
1270
|
this.emit('onPause');
|
|
1299
1271
|
break;
|
|
1300
1272
|
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1273
|
+
case window.YT.PlayerState.BUFFERING:
|
|
1274
|
+
this.state.isBuffering = true;
|
|
1275
|
+
this.updateYouTubeUI('buffering');
|
|
1276
|
+
this.emit('onBuffering', true);
|
|
1305
1277
|
break;
|
|
1306
1278
|
|
|
1307
1279
|
case window.YT.PlayerState.ENDED:
|
|
@@ -1312,16 +1284,10 @@ export class WebPlayer extends BasePlayer {
|
|
|
1312
1284
|
this.emit('onEnded');
|
|
1313
1285
|
break;
|
|
1314
1286
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
// Re-check Live status when video is cued
|
|
1320
|
-
setTimeout(() => {
|
|
1321
|
-
this.detectYouTubeLiveStatus();
|
|
1322
|
-
this.updateControlsVisibility();
|
|
1323
|
-
}, 500);
|
|
1324
|
-
break;
|
|
1287
|
+
case window.YT.PlayerState.CUED:
|
|
1288
|
+
this.state.duration = this.youtubePlayer.getDuration();
|
|
1289
|
+
this.updateYouTubeUI('cued');
|
|
1290
|
+
break;
|
|
1325
1291
|
}
|
|
1326
1292
|
}
|
|
1327
1293
|
|
|
@@ -1370,360 +1336,23 @@ export class WebPlayer extends BasePlayer {
|
|
|
1370
1336
|
});
|
|
1371
1337
|
}
|
|
1372
1338
|
|
|
1373
|
-
/**
|
|
1374
|
-
* Get YouTube video title from the player API
|
|
1375
|
-
*/
|
|
1376
|
-
private getYouTubeVideoTitle(): void {
|
|
1377
|
-
if (!this.youtubePlayer || !this.youtubePlayerReady) return;
|
|
1378
|
-
|
|
1379
|
-
try {
|
|
1380
|
-
// Try to get video data from YouTube player
|
|
1381
|
-
const videoData = this.youtubePlayer.getVideoData();
|
|
1382
|
-
if (videoData && videoData.title) {
|
|
1383
|
-
this.debugLog('Got YouTube title from player API:', videoData.title);
|
|
1384
|
-
|
|
1385
|
-
// Update source metadata with the correct title
|
|
1386
|
-
if (this.source && this.source.metadata) {
|
|
1387
|
-
this.source.metadata.title = videoData.title;
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
// Update UI immediately
|
|
1391
|
-
this.updateMetadataUI();
|
|
1392
|
-
}
|
|
1393
|
-
} catch (error) {
|
|
1394
|
-
this.debugWarn('Could not get YouTube video title from API:', error);
|
|
1395
|
-
|
|
1396
|
-
// Fallback: Try to get from oembed API
|
|
1397
|
-
this.getYouTubeVideoTitleFromOEmbed();
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
|
-
/**
|
|
1402
|
-
* Detect if YouTube video is Live
|
|
1403
|
-
*/
|
|
1404
|
-
private detectYouTubeLiveStatus(): void {
|
|
1405
|
-
if (!this.youtubePlayer || !this.youtubePlayerReady) return;
|
|
1406
|
-
|
|
1407
|
-
try {
|
|
1408
|
-
const videoData = this.youtubePlayer.getVideoData();
|
|
1409
|
-
this.isYouTubeLive = videoData?.isLive || false;
|
|
1410
|
-
|
|
1411
|
-
// Use custom controls by default, unless youtubeNativeControls is explicitly set to true
|
|
1412
|
-
this.useYouTubeNativeControls = this.config.youtubeNativeControls === true;
|
|
1413
|
-
|
|
1414
|
-
this.debugLog('YouTube Live status:', {
|
|
1415
|
-
isLive: this.isYouTubeLive,
|
|
1416
|
-
useNativeControls: this.useYouTubeNativeControls,
|
|
1417
|
-
videoId: videoData?.video_id
|
|
1418
|
-
});
|
|
1419
|
-
|
|
1420
|
-
} catch (error) {
|
|
1421
|
-
this.debugWarn('Could not detect YouTube Live status:', error);
|
|
1422
|
-
// Fallback: check duration - Live videos typically have duration = 0
|
|
1423
|
-
try {
|
|
1424
|
-
const duration = this.youtubePlayer.getDuration();
|
|
1425
|
-
this.isYouTubeLive = !duration || duration === 0;
|
|
1426
|
-
this.useYouTubeNativeControls = this.config.youtubeNativeControls === true;
|
|
1427
|
-
|
|
1428
|
-
this.debugLog('YouTube Live detected via duration check:', {
|
|
1429
|
-
duration,
|
|
1430
|
-
isLive: this.isYouTubeLive,
|
|
1431
|
-
useNativeControls: this.useYouTubeNativeControls
|
|
1432
|
-
});
|
|
1433
|
-
} catch (e) {
|
|
1434
|
-
this.debugWarn('Could not check YouTube duration for Live detection:', e);
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
/**
|
|
1440
|
-
* Update controls visibility based on YouTube Live status and config
|
|
1441
|
-
*/
|
|
1442
|
-
private updateControlsVisibility(): void {
|
|
1443
|
-
const controlsContainer = document.getElementById('uvf-controls');
|
|
1444
|
-
if (!controlsContainer) {
|
|
1445
|
-
this.debugWarn('Controls container not found, looking for .uvf-controls-bar');
|
|
1446
|
-
const controlsBar = this.playerWrapper?.querySelector('.uvf-controls-bar') as HTMLElement;
|
|
1447
|
-
if (!controlsBar) {
|
|
1448
|
-
this.debugWarn('Controls bar not found either, cannot update controls visibility');
|
|
1449
|
-
return;
|
|
1450
|
-
}
|
|
1451
|
-
// Use the controls bar as fallback
|
|
1452
|
-
const controlsContainerFallback = controlsBar;
|
|
1453
|
-
|
|
1454
|
-
if (this.youtubePlayer && this.useYouTubeNativeControls) {
|
|
1455
|
-
// Hide custom controls
|
|
1456
|
-
controlsContainerFallback.style.display = 'none';
|
|
1457
|
-
// Also hide any other control elements
|
|
1458
|
-
const videoContainer = this.playerWrapper?.querySelector('.uvf-video-container') as HTMLElement;
|
|
1459
|
-
if (videoContainer) {
|
|
1460
|
-
const allControls = videoContainer.querySelectorAll('.uvf-top-gradient, .uvf-controls-gradient, .uvf-top-bar, .uvf-center-play-container, .uvf-shortcut-indicator');
|
|
1461
|
-
allControls.forEach(el => (el as HTMLElement).style.display = 'none');
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
// Only recreate YouTube player if it doesn't currently have native controls
|
|
1465
|
-
if (this.currentYouTubeControlsState !== true) {
|
|
1466
|
-
this.recreateYouTubePlayerWithNativeControls();
|
|
1467
|
-
this.currentYouTubeControlsState = true;
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
this.debugLog('✅ YouTube native controls enabled', {
|
|
1471
|
-
isLive: this.isYouTubeLive,
|
|
1472
|
-
reason: this.config.youtubeNativeControls === true ? 'Explicitly enabled in config' : 'Live stream detected'
|
|
1473
|
-
});
|
|
1474
|
-
} else {
|
|
1475
|
-
// Show custom controls
|
|
1476
|
-
controlsContainerFallback.style.display = 'flex';
|
|
1477
|
-
// Also show other control elements
|
|
1478
|
-
const videoContainer = this.playerWrapper?.querySelector('.uvf-video-container') as HTMLElement;
|
|
1479
|
-
if (videoContainer) {
|
|
1480
|
-
const allControls = videoContainer.querySelectorAll('.uvf-top-gradient, .uvf-controls-gradient, .uvf-top-bar, .uvf-center-play-container, .uvf-shortcut-indicator');
|
|
1481
|
-
allControls.forEach(el => (el as HTMLElement).style.display = '');
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
// Only recreate YouTube player if it currently has native controls
|
|
1485
|
-
if (this.currentYouTubeControlsState !== false) {
|
|
1486
|
-
this.recreateYouTubePlayerWithoutNativeControls();
|
|
1487
|
-
this.currentYouTubeControlsState = false;
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
this.debugLog('✅ Custom controls enabled for YouTube video');
|
|
1491
|
-
}
|
|
1492
|
-
return;
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
if (this.youtubePlayer && this.useYouTubeNativeControls) {
|
|
1496
|
-
// Hide custom controls and show YouTube native controls
|
|
1497
|
-
controlsContainer.style.display = 'none';
|
|
1498
|
-
|
|
1499
|
-
// Only recreate YouTube player if it doesn't currently have native controls
|
|
1500
|
-
if (this.currentYouTubeControlsState !== true) {
|
|
1501
|
-
this.recreateYouTubePlayerWithNativeControls();
|
|
1502
|
-
this.currentYouTubeControlsState = true;
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
this.debugLog('✅ YouTube native controls enabled', {
|
|
1506
|
-
isLive: this.isYouTubeLive,
|
|
1507
|
-
reason: this.config.youtubeNativeControls === true ? 'Explicitly enabled in config' : 'Live stream detected'
|
|
1508
|
-
});
|
|
1509
|
-
} else {
|
|
1510
|
-
// Show custom controls and ensure YouTube native controls are disabled
|
|
1511
|
-
controlsContainer.style.display = 'flex';
|
|
1512
|
-
|
|
1513
|
-
// Only recreate YouTube player if it currently has native controls
|
|
1514
|
-
if (this.currentYouTubeControlsState !== false) {
|
|
1515
|
-
this.recreateYouTubePlayerWithoutNativeControls();
|
|
1516
|
-
this.currentYouTubeControlsState = false;
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
this.debugLog('✅ Custom controls enabled for YouTube video');
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
/**
|
|
1524
|
-
* Recreate YouTube player with native controls enabled
|
|
1525
|
-
*/
|
|
1526
|
-
private recreateYouTubePlayerWithNativeControls(): void {
|
|
1527
|
-
if (!this.source?.metadata?.videoId) return;
|
|
1528
|
-
|
|
1529
|
-
const videoId = this.source.metadata.videoId;
|
|
1530
|
-
const currentTime = this.youtubePlayer?.getCurrentTime() || 0;
|
|
1531
|
-
|
|
1532
|
-
// Find the container
|
|
1533
|
-
const container = this.playerWrapper || this.video?.parentElement;
|
|
1534
|
-
if (!container) return;
|
|
1535
|
-
|
|
1536
|
-
// Destroy current player
|
|
1537
|
-
if (this.youtubePlayer) {
|
|
1538
|
-
this.youtubePlayer.destroy();
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
// Remove existing iframe container
|
|
1542
|
-
const existingContainer = container.querySelector(`#youtube-player-${videoId}`);
|
|
1543
|
-
if (existingContainer) {
|
|
1544
|
-
existingContainer.remove();
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
// Create new iframe container
|
|
1548
|
-
const iframeContainer = document.createElement('div');
|
|
1549
|
-
iframeContainer.id = `youtube-player-${videoId}`;
|
|
1550
|
-
iframeContainer.style.cssText = `
|
|
1551
|
-
position: absolute;
|
|
1552
|
-
top: 0;
|
|
1553
|
-
left: 0;
|
|
1554
|
-
width: 100%;
|
|
1555
|
-
height: 100%;
|
|
1556
|
-
z-index: 1;
|
|
1557
|
-
`;
|
|
1558
|
-
container.appendChild(iframeContainer);
|
|
1559
|
-
|
|
1560
|
-
// Create new player with native controls
|
|
1561
|
-
this.youtubePlayer = new window.YT.Player(iframeContainer.id, {
|
|
1562
|
-
videoId: videoId,
|
|
1563
|
-
width: '100%',
|
|
1564
|
-
height: '100%',
|
|
1565
|
-
playerVars: {
|
|
1566
|
-
autoplay: this.config.autoPlay ? 1 : 0,
|
|
1567
|
-
controls: 1, // Enable native controls
|
|
1568
|
-
modestbranding: 1,
|
|
1569
|
-
rel: 0,
|
|
1570
|
-
showinfo: 0,
|
|
1571
|
-
iv_load_policy: 3,
|
|
1572
|
-
playsinline: 1,
|
|
1573
|
-
start: Math.floor(currentTime)
|
|
1574
|
-
},
|
|
1575
|
-
events: {
|
|
1576
|
-
onReady: () => {
|
|
1577
|
-
this.youtubePlayerReady = true;
|
|
1578
|
-
this.debugLog('YouTube player with native controls ready');
|
|
1579
|
-
this.emit('onReady');
|
|
1580
|
-
},
|
|
1581
|
-
onStateChange: (event: any) => this.onYouTubePlayerStateChange(event),
|
|
1582
|
-
onError: (event: any) => this.onYouTubePlayerError(event)
|
|
1583
|
-
}
|
|
1584
|
-
});
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
/**
|
|
1588
|
-
* Recreate YouTube player without native controls enabled
|
|
1589
|
-
*/
|
|
1590
|
-
private recreateYouTubePlayerWithoutNativeControls(): void {
|
|
1591
|
-
if (!this.source?.metadata?.videoId) return;
|
|
1592
|
-
|
|
1593
|
-
const videoId = this.source.metadata.videoId;
|
|
1594
|
-
const currentTime = this.youtubePlayer?.getCurrentTime() || 0;
|
|
1595
|
-
|
|
1596
|
-
// Find the container
|
|
1597
|
-
const container = this.playerWrapper || this.video?.parentElement;
|
|
1598
|
-
if (!container) return;
|
|
1599
|
-
|
|
1600
|
-
// Destroy current player
|
|
1601
|
-
if (this.youtubePlayer) {
|
|
1602
|
-
this.youtubePlayer.destroy();
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
// Remove existing iframe container
|
|
1606
|
-
const existingContainer = container.querySelector(`#youtube-player-${videoId}`);
|
|
1607
|
-
if (existingContainer) {
|
|
1608
|
-
existingContainer.remove();
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
// Create new iframe container
|
|
1612
|
-
const iframeContainer = document.createElement('div');
|
|
1613
|
-
iframeContainer.id = `youtube-player-${videoId}`;
|
|
1614
|
-
iframeContainer.style.cssText = `
|
|
1615
|
-
position: absolute;
|
|
1616
|
-
top: 0;
|
|
1617
|
-
left: 0;
|
|
1618
|
-
width: 100%;
|
|
1619
|
-
height: 100%;
|
|
1620
|
-
z-index: 1;
|
|
1621
|
-
`;
|
|
1622
|
-
container.appendChild(iframeContainer);
|
|
1623
|
-
|
|
1624
|
-
// Create new player without native controls
|
|
1625
|
-
this.youtubePlayer = new window.YT.Player(iframeContainer.id, {
|
|
1626
|
-
videoId: videoId,
|
|
1627
|
-
width: '100%',
|
|
1628
|
-
height: '100%',
|
|
1629
|
-
playerVars: {
|
|
1630
|
-
autoplay: this.config.autoPlay ? 1 : 0,
|
|
1631
|
-
controls: 0, // Disable native controls
|
|
1632
|
-
disablekb: 0,
|
|
1633
|
-
fs: 0,
|
|
1634
|
-
iv_load_policy: 3,
|
|
1635
|
-
modestbranding: 1,
|
|
1636
|
-
rel: 0,
|
|
1637
|
-
showinfo: 0,
|
|
1638
|
-
playsinline: 1,
|
|
1639
|
-
start: Math.floor(currentTime)
|
|
1640
|
-
},
|
|
1641
|
-
events: {
|
|
1642
|
-
onReady: () => {
|
|
1643
|
-
this.youtubePlayerReady = true;
|
|
1644
|
-
this.debugLog('YouTube player without native controls ready');
|
|
1645
|
-
this.emit('onReady');
|
|
1646
|
-
},
|
|
1647
|
-
onStateChange: (event: any) => this.onYouTubePlayerStateChange(event),
|
|
1648
|
-
onError: (event: any) => this.onYouTubePlayerError(event)
|
|
1649
|
-
}
|
|
1650
|
-
});
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
/**
|
|
1654
|
-
* Toggle between native and custom YouTube controls
|
|
1655
|
-
*/
|
|
1656
|
-
public toggleYouTubeControls(useNative: boolean = !this.useYouTubeNativeControls): void {
|
|
1657
|
-
if (!this.youtubePlayer) {
|
|
1658
|
-
this.debugWarn('Cannot toggle YouTube controls - no YouTube player active');
|
|
1659
|
-
return;
|
|
1660
|
-
}
|
|
1661
|
-
|
|
1662
|
-
this.useYouTubeNativeControls = useNative;
|
|
1663
|
-
this.config.youtubeNativeControls = useNative; // Update config
|
|
1664
|
-
this.updateControlsVisibility();
|
|
1665
|
-
|
|
1666
|
-
this.debugLog('YouTube controls toggled:', {
|
|
1667
|
-
useNative: this.useYouTubeNativeControls,
|
|
1668
|
-
isLive: this.isYouTubeLive
|
|
1669
|
-
});
|
|
1670
|
-
|
|
1671
|
-
// Show notification
|
|
1672
|
-
this.showNotification(
|
|
1673
|
-
`YouTube Controls: ${this.useYouTubeNativeControls ? 'Native' : 'Custom'}`
|
|
1674
|
-
);
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
/**
|
|
1678
|
-
* Fallback method to get title from YouTube oembed API
|
|
1679
|
-
*/
|
|
1680
|
-
private async getYouTubeVideoTitleFromOEmbed(): Promise<void> {
|
|
1681
|
-
if (!this.source?.metadata?.videoId) return;
|
|
1682
|
-
|
|
1683
|
-
try {
|
|
1684
|
-
const videoId = this.source.metadata.videoId;
|
|
1685
|
-
const oembedUrl = `https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${videoId}&format=json`;
|
|
1686
|
-
|
|
1687
|
-
const response = await fetch(oembedUrl);
|
|
1688
|
-
if (response.ok) {
|
|
1689
|
-
const data = await response.json();
|
|
1690
|
-
if (data.title) {
|
|
1691
|
-
this.debugLog('Got YouTube title from oembed API:', data.title);
|
|
1692
|
-
|
|
1693
|
-
// Update source metadata
|
|
1694
|
-
if (this.source && this.source.metadata) {
|
|
1695
|
-
this.source.metadata.title = data.title;
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
// Update UI
|
|
1699
|
-
this.updateMetadataUI();
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
} catch (error) {
|
|
1703
|
-
this.debugWarn('Could not get YouTube title from oembed API:', error);
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
1339
|
private youtubeTimeTrackingInterval: NodeJS.Timeout | null = null;
|
|
1708
|
-
private isYouTubeLive: boolean = false;
|
|
1709
|
-
private useYouTubeNativeControls: boolean = false;
|
|
1710
|
-
private currentYouTubeControlsState: boolean | null = null; // Track current YouTube player controls state
|
|
1711
1340
|
|
|
1712
1341
|
private startYouTubeTimeTracking(): void {
|
|
1713
1342
|
if (this.youtubeTimeTrackingInterval) {
|
|
1714
1343
|
clearInterval(this.youtubeTimeTrackingInterval);
|
|
1715
1344
|
}
|
|
1716
1345
|
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1346
|
+
this.youtubeTimeTrackingInterval = setInterval(() => {
|
|
1347
|
+
if (this.youtubePlayer && this.youtubePlayerReady) {
|
|
1348
|
+
try {
|
|
1349
|
+
const currentTime = this.youtubePlayer.getCurrentTime();
|
|
1350
|
+
const duration = this.youtubePlayer.getDuration();
|
|
1351
|
+
const buffered = this.youtubePlayer.getVideoLoadedFraction() * 100;
|
|
1352
|
+
|
|
1353
|
+
this.state.currentTime = currentTime || 0;
|
|
1354
|
+
this.state.duration = duration || 0;
|
|
1355
|
+
this.state.bufferedPercentage = buffered || 0;
|
|
1727
1356
|
|
|
1728
1357
|
// Update UI progress bar
|
|
1729
1358
|
this.updateYouTubeProgressBar(currentTime, duration, buffered);
|
|
@@ -1742,18 +1371,16 @@ export class WebPlayer extends BasePlayer {
|
|
|
1742
1371
|
|
|
1743
1372
|
const percent = (currentTime / duration) * 100;
|
|
1744
1373
|
|
|
1745
|
-
// Update progress filled
|
|
1374
|
+
// Update progress filled
|
|
1746
1375
|
const progressFilled = document.getElementById('uvf-progress-filled') as HTMLElement;
|
|
1747
1376
|
if (progressFilled && !this.isDragging) {
|
|
1748
1377
|
progressFilled.style.width = percent + '%';
|
|
1749
1378
|
}
|
|
1750
1379
|
|
|
1751
|
-
// Update progress handle
|
|
1380
|
+
// Update progress handle
|
|
1752
1381
|
const progressHandle = document.getElementById('uvf-progress-handle') as HTMLElement;
|
|
1753
1382
|
if (progressHandle && !this.isDragging) {
|
|
1754
1383
|
progressHandle.style.left = percent + '%';
|
|
1755
|
-
// Remove dragging class if it was set
|
|
1756
|
-
progressHandle.classList.remove('dragging');
|
|
1757
1384
|
}
|
|
1758
1385
|
|
|
1759
1386
|
// Update buffered progress
|
|
@@ -1762,13 +1389,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
1762
1389
|
progressBuffered.style.width = buffered + '%';
|
|
1763
1390
|
}
|
|
1764
1391
|
|
|
1765
|
-
// Update time display
|
|
1766
|
-
|
|
1767
|
-
if (timeDisplay) {
|
|
1768
|
-
const currentTimeStr = this.formatTime(currentTime);
|
|
1769
|
-
const durationStr = this.formatTime(duration);
|
|
1770
|
-
timeDisplay.textContent = `${currentTimeStr} / ${durationStr}`;
|
|
1771
|
-
}
|
|
1392
|
+
// Update time display
|
|
1393
|
+
this.updateTimeDisplay();
|
|
1772
1394
|
}
|
|
1773
1395
|
|
|
1774
1396
|
|
|
@@ -2213,27 +1835,12 @@ export class WebPlayer extends BasePlayer {
|
|
|
2213
1835
|
private updateTimeTooltip(e: MouseEvent): void {
|
|
2214
1836
|
const progressBar = document.getElementById('uvf-progress-bar');
|
|
2215
1837
|
const tooltip = document.getElementById('uvf-time-tooltip');
|
|
2216
|
-
if (!progressBar || !tooltip) return;
|
|
2217
|
-
|
|
2218
|
-
// Get duration from appropriate source
|
|
2219
|
-
let duration = 0;
|
|
2220
|
-
if (this.youtubePlayer && this.youtubePlayerReady) {
|
|
2221
|
-
try {
|
|
2222
|
-
duration = this.youtubePlayer.getDuration() || 0;
|
|
2223
|
-
} catch (error) {
|
|
2224
|
-
this.debugWarn('Error getting YouTube duration for tooltip:', error);
|
|
2225
|
-
return;
|
|
2226
|
-
}
|
|
2227
|
-
} else if (this.video) {
|
|
2228
|
-
duration = this.video.duration || 0;
|
|
2229
|
-
}
|
|
2230
|
-
|
|
2231
|
-
if (!duration || !isFinite(duration)) return;
|
|
1838
|
+
if (!progressBar || !tooltip || !this.video) return;
|
|
2232
1839
|
|
|
2233
1840
|
const rect = progressBar.getBoundingClientRect();
|
|
2234
1841
|
const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
|
|
2235
1842
|
const percent = (x / rect.width);
|
|
2236
|
-
const time = percent * duration;
|
|
1843
|
+
const time = percent * this.video.duration;
|
|
2237
1844
|
|
|
2238
1845
|
// Update tooltip content and position
|
|
2239
1846
|
tooltip.textContent = this.formatTime(time);
|
|
@@ -4888,29 +4495,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
4888
4495
|
}
|
|
4889
4496
|
|
|
4890
4497
|
.uvf-accordion-item.expanded .uvf-accordion-content {
|
|
4891
|
-
max-height:
|
|
4892
|
-
overflow-y: auto;
|
|
4893
|
-
-webkit-overflow-scrolling: touch;
|
|
4894
|
-
}
|
|
4895
|
-
|
|
4896
|
-
/* Special handling for quality accordion with many options */
|
|
4897
|
-
.uvf-accordion-item.expanded .uvf-accordion-content[data-section="quality"] {
|
|
4898
|
-
max-height: 400px;
|
|
4899
|
-
}
|
|
4900
|
-
|
|
4901
|
-
/* Scrollbar styling for accordion content */
|
|
4902
|
-
.uvf-accordion-content::-webkit-scrollbar {
|
|
4903
|
-
width: 4px;
|
|
4904
|
-
}
|
|
4905
|
-
.uvf-accordion-content::-webkit-scrollbar-track {
|
|
4906
|
-
background: transparent;
|
|
4907
|
-
}
|
|
4908
|
-
.uvf-accordion-content::-webkit-scrollbar-thumb {
|
|
4909
|
-
background: rgba(255,255,255,0.3);
|
|
4910
|
-
border-radius: 2px;
|
|
4911
|
-
}
|
|
4912
|
-
.uvf-accordion-content::-webkit-scrollbar-thumb:hover {
|
|
4913
|
-
background: rgba(255,255,255,0.5);
|
|
4498
|
+
max-height: 250px;
|
|
4914
4499
|
}
|
|
4915
4500
|
|
|
4916
4501
|
/* Settings options within accordion */
|
|
@@ -7071,7 +6656,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
7071
6656
|
const qualityBadge = document.createElement('div');
|
|
7072
6657
|
qualityBadge.className = 'uvf-quality-badge';
|
|
7073
6658
|
qualityBadge.id = 'uvf-quality-badge';
|
|
7074
|
-
qualityBadge.textContent = '
|
|
6659
|
+
qualityBadge.textContent = 'HD';
|
|
7075
6660
|
rightControls.appendChild(qualityBadge);
|
|
7076
6661
|
|
|
7077
6662
|
// Settings button with menu (show only if enabled)
|
|
@@ -8428,27 +8013,12 @@ export class WebPlayer extends BasePlayer {
|
|
|
8428
8013
|
const progressBar = document.querySelector('.uvf-progress-bar') as HTMLElement;
|
|
8429
8014
|
const progressFilled = document.getElementById('uvf-progress-filled') as HTMLElement;
|
|
8430
8015
|
const progressHandle = document.getElementById('uvf-progress-handle') as HTMLElement;
|
|
8431
|
-
if (!progressBar) return;
|
|
8432
|
-
|
|
8433
|
-
// Get duration from appropriate source
|
|
8434
|
-
let duration = 0;
|
|
8435
|
-
if (this.youtubePlayer && this.youtubePlayerReady) {
|
|
8436
|
-
try {
|
|
8437
|
-
duration = this.youtubePlayer.getDuration() || 0;
|
|
8438
|
-
} catch (error) {
|
|
8439
|
-
this.debugWarn('Error getting YouTube duration for seeking:', error);
|
|
8440
|
-
return;
|
|
8441
|
-
}
|
|
8442
|
-
} else if (this.video) {
|
|
8443
|
-
duration = this.video.duration;
|
|
8444
|
-
} else {
|
|
8445
|
-
this.debugWarn('No video source available for seeking');
|
|
8446
|
-
return;
|
|
8447
|
-
}
|
|
8016
|
+
if (!progressBar || !this.video) return;
|
|
8448
8017
|
|
|
8018
|
+
const duration = this.video.duration;
|
|
8449
8019
|
// Validate duration before calculating seek time
|
|
8450
8020
|
if (!isFinite(duration) || isNaN(duration) || duration <= 0) {
|
|
8451
|
-
this.debugWarn('Invalid video duration, cannot seek via progress bar
|
|
8021
|
+
this.debugWarn('Invalid video duration, cannot seek via progress bar');
|
|
8452
8022
|
return;
|
|
8453
8023
|
}
|
|
8454
8024
|
|
|
@@ -8463,24 +8033,21 @@ export class WebPlayer extends BasePlayer {
|
|
|
8463
8033
|
return;
|
|
8464
8034
|
}
|
|
8465
8035
|
|
|
8466
|
-
this.debugLog('Seeking to position:', time, 'seconds (', Math.round(percent), '%)');
|
|
8467
|
-
|
|
8468
8036
|
// Update UI immediately for responsive feedback
|
|
8469
|
-
if (progressFilled
|
|
8037
|
+
if (progressFilled) {
|
|
8470
8038
|
progressFilled.style.width = percent + '%';
|
|
8471
8039
|
}
|
|
8472
|
-
if (progressHandle
|
|
8040
|
+
if (progressHandle) {
|
|
8473
8041
|
progressHandle.style.left = percent + '%';
|
|
8474
|
-
|
|
8042
|
+
// Add dragging class for visual feedback
|
|
8043
|
+
if (this.isDragging) {
|
|
8044
|
+
progressHandle.classList.add('dragging');
|
|
8045
|
+
} else {
|
|
8046
|
+
progressHandle.classList.remove('dragging');
|
|
8047
|
+
}
|
|
8475
8048
|
}
|
|
8476
8049
|
|
|
8477
|
-
// Perform the actual seek
|
|
8478
8050
|
this.seek(time);
|
|
8479
|
-
|
|
8480
|
-
// For YouTube, provide immediate visual feedback since API might be delayed
|
|
8481
|
-
if (this.youtubePlayer && this.youtubePlayerReady) {
|
|
8482
|
-
this.emit('onSeeking');
|
|
8483
|
-
}
|
|
8484
8051
|
}
|
|
8485
8052
|
|
|
8486
8053
|
private formatTime(seconds: number): string {
|