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.
- 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 -11
- package/packages/web/dist/WebPlayer.d.ts.map +1 -1
- package/packages/web/dist/WebPlayer.js +47 -418
- 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 +77 -567
- package/packages/web/src/react/WebPlayerView.tsx +2 -5
|
@@ -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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
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
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
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
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
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
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
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
|
|
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
|
|
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
|
|
1856
|
-
|
|
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:
|
|
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 = '
|
|
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
|
|
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
|
|
8070
|
+
if (progressFilled) {
|
|
8560
8071
|
progressFilled.style.width = percent + '%';
|
|
8561
8072
|
}
|
|
8562
|
-
if (progressHandle
|
|
8073
|
+
if (progressHandle) {
|
|
8563
8074
|
progressHandle.style.left = percent + '%';
|
|
8564
|
-
|
|
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 {
|