unified-video-framework 1.4.255 → 1.4.257
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/packages/core/dist/interfaces/IVideoPlayer.d.ts +1 -0
- package/packages/core/dist/interfaces/IVideoPlayer.d.ts.map +1 -1
- package/packages/core/dist/interfaces.d.ts +1 -0
- package/packages/core/dist/interfaces.d.ts.map +1 -1
- package/packages/core/src/interfaces/IVideoPlayer.ts +3 -0
- package/packages/core/src/interfaces.ts +1 -0
- package/packages/web/dist/WebPlayer.d.ts +6 -5
- package/packages/web/dist/WebPlayer.d.ts.map +1 -1
- package/packages/web/dist/WebPlayer.js +128 -216
- package/packages/web/dist/WebPlayer.js.map +1 -1
- package/packages/web/src/WebPlayer.ts +196 -312
|
@@ -1184,7 +1184,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
1184
1184
|
width: '100%',
|
|
1185
1185
|
height: '100%',
|
|
1186
1186
|
playerVars: {
|
|
1187
|
-
controls: 0,
|
|
1187
|
+
controls: this.config.youtubeNativeControls !== false ? 1 : 0, // Native controls enabled by default
|
|
1188
1188
|
disablekb: 0, // Allow keyboard controls
|
|
1189
1189
|
fs: 0, // Hide fullscreen button
|
|
1190
1190
|
iv_load_policy: 3, // Hide annotations
|
|
@@ -1252,16 +1252,16 @@ export class WebPlayer extends BasePlayer {
|
|
|
1252
1252
|
this.youtubePlayer.mute();
|
|
1253
1253
|
}
|
|
1254
1254
|
|
|
1255
|
-
//
|
|
1256
|
-
this.
|
|
1255
|
+
// Detect if YouTube video is Live and handle controls
|
|
1256
|
+
this.detectYouTubeLiveStatus();
|
|
1257
1257
|
|
|
1258
1258
|
// Try to get video title from YouTube API
|
|
1259
1259
|
this.getYouTubeVideoTitle();
|
|
1260
1260
|
|
|
1261
|
-
// Update metadata UI and
|
|
1261
|
+
// Update metadata UI and controls visibility after player is ready
|
|
1262
1262
|
setTimeout(() => {
|
|
1263
1263
|
this.updateMetadataUI();
|
|
1264
|
-
this.
|
|
1264
|
+
this.updateControlsVisibility();
|
|
1265
1265
|
}, 500);
|
|
1266
1266
|
}
|
|
1267
1267
|
|
|
@@ -1277,16 +1277,10 @@ export class WebPlayer extends BasePlayer {
|
|
|
1277
1277
|
switch (state) {
|
|
1278
1278
|
case window.YT.PlayerState.PLAYING:
|
|
1279
1279
|
this.state.isPlaying = true;
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
// Extract qualities when video starts playing
|
|
1285
|
-
setTimeout(() => {
|
|
1286
|
-
this.extractYouTubeAvailableQualities();
|
|
1287
|
-
}, 1000);
|
|
1288
|
-
|
|
1289
|
-
this.emit('onPlay');
|
|
1280
|
+
this.state.isPaused = false;
|
|
1281
|
+
this.state.isBuffering = false;
|
|
1282
|
+
this.updateYouTubeUI('playing');
|
|
1283
|
+
this.emit('onPlay');
|
|
1290
1284
|
break;
|
|
1291
1285
|
|
|
1292
1286
|
case window.YT.PlayerState.PAUSED:
|
|
@@ -1297,16 +1291,10 @@ export class WebPlayer extends BasePlayer {
|
|
|
1297
1291
|
this.emit('onPause');
|
|
1298
1292
|
break;
|
|
1299
1293
|
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
// Try to extract qualities during buffering as well
|
|
1305
|
-
setTimeout(() => {
|
|
1306
|
-
this.extractYouTubeAvailableQualities();
|
|
1307
|
-
}, 500);
|
|
1308
|
-
|
|
1309
|
-
this.emit('onBuffering', true);
|
|
1294
|
+
case window.YT.PlayerState.BUFFERING:
|
|
1295
|
+
this.state.isBuffering = true;
|
|
1296
|
+
this.updateYouTubeUI('buffering');
|
|
1297
|
+
this.emit('onBuffering', true);
|
|
1310
1298
|
break;
|
|
1311
1299
|
|
|
1312
1300
|
case window.YT.PlayerState.ENDED:
|
|
@@ -1317,15 +1305,16 @@ export class WebPlayer extends BasePlayer {
|
|
|
1317
1305
|
this.emit('onEnded');
|
|
1318
1306
|
break;
|
|
1319
1307
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1308
|
+
case window.YT.PlayerState.CUED:
|
|
1309
|
+
this.state.duration = this.youtubePlayer.getDuration();
|
|
1310
|
+
this.updateYouTubeUI('cued');
|
|
1311
|
+
|
|
1312
|
+
// Re-check Live status when video is cued
|
|
1313
|
+
setTimeout(() => {
|
|
1314
|
+
this.detectYouTubeLiveStatus();
|
|
1315
|
+
this.updateControlsVisibility();
|
|
1316
|
+
}, 500);
|
|
1317
|
+
break;
|
|
1329
1318
|
}
|
|
1330
1319
|
}
|
|
1331
1320
|
|
|
@@ -1402,6 +1391,159 @@ export class WebPlayer extends BasePlayer {
|
|
|
1402
1391
|
}
|
|
1403
1392
|
}
|
|
1404
1393
|
|
|
1394
|
+
/**
|
|
1395
|
+
* Detect if YouTube video is Live
|
|
1396
|
+
*/
|
|
1397
|
+
private detectYouTubeLiveStatus(): void {
|
|
1398
|
+
if (!this.youtubePlayer || !this.youtubePlayerReady) return;
|
|
1399
|
+
|
|
1400
|
+
try {
|
|
1401
|
+
const videoData = this.youtubePlayer.getVideoData();
|
|
1402
|
+
this.isYouTubeLive = videoData?.isLive || false;
|
|
1403
|
+
|
|
1404
|
+
// Auto-enable native controls for all YouTube content by default, unless explicitly disabled
|
|
1405
|
+
this.useYouTubeNativeControls = this.config.youtubeNativeControls !== false;
|
|
1406
|
+
|
|
1407
|
+
this.debugLog('YouTube Live status:', {
|
|
1408
|
+
isLive: this.isYouTubeLive,
|
|
1409
|
+
useNativeControls: this.useYouTubeNativeControls,
|
|
1410
|
+
videoId: videoData?.video_id
|
|
1411
|
+
});
|
|
1412
|
+
|
|
1413
|
+
} catch (error) {
|
|
1414
|
+
this.debugWarn('Could not detect YouTube Live status:', error);
|
|
1415
|
+
// Fallback: check duration - Live videos typically have duration = 0
|
|
1416
|
+
try {
|
|
1417
|
+
const duration = this.youtubePlayer.getDuration();
|
|
1418
|
+
this.isYouTubeLive = !duration || duration === 0;
|
|
1419
|
+
this.useYouTubeNativeControls = this.config.youtubeNativeControls !== false;
|
|
1420
|
+
|
|
1421
|
+
this.debugLog('YouTube Live detected via duration check:', {
|
|
1422
|
+
duration,
|
|
1423
|
+
isLive: this.isYouTubeLive,
|
|
1424
|
+
useNativeControls: this.useYouTubeNativeControls
|
|
1425
|
+
});
|
|
1426
|
+
} catch (e) {
|
|
1427
|
+
this.debugWarn('Could not check YouTube duration for Live detection:', e);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
/**
|
|
1433
|
+
* Update controls visibility based on YouTube Live status and config
|
|
1434
|
+
*/
|
|
1435
|
+
private updateControlsVisibility(): void {
|
|
1436
|
+
const controlsContainer = document.getElementById('uvf-controls-container');
|
|
1437
|
+
if (!controlsContainer) return;
|
|
1438
|
+
|
|
1439
|
+
if (this.youtubePlayer && this.useYouTubeNativeControls) {
|
|
1440
|
+
// Hide custom controls and show YouTube native controls
|
|
1441
|
+
controlsContainer.style.display = 'none';
|
|
1442
|
+
|
|
1443
|
+
// Re-create YouTube player with native controls if needed
|
|
1444
|
+
if (!this.config.youtubeNativeControls) {
|
|
1445
|
+
this.recreateYouTubePlayerWithNativeControls();
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
this.debugLog('✅ YouTube native controls enabled', {
|
|
1449
|
+
isLive: this.isYouTubeLive,
|
|
1450
|
+
reason: this.config.youtubeNativeControls !== false ? 'Default behavior (all YouTube content)' : 'Live stream detected'
|
|
1451
|
+
});
|
|
1452
|
+
} else {
|
|
1453
|
+
// Show custom controls
|
|
1454
|
+
controlsContainer.style.display = 'flex';
|
|
1455
|
+
this.debugLog('✅ Custom controls enabled for YouTube video');
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
/**
|
|
1460
|
+
* Recreate YouTube player with native controls enabled
|
|
1461
|
+
*/
|
|
1462
|
+
private recreateYouTubePlayerWithNativeControls(): void {
|
|
1463
|
+
if (!this.source?.metadata?.videoId) return;
|
|
1464
|
+
|
|
1465
|
+
const videoId = this.source.metadata.videoId;
|
|
1466
|
+
const currentTime = this.youtubePlayer?.getCurrentTime() || 0;
|
|
1467
|
+
|
|
1468
|
+
// Find the container
|
|
1469
|
+
const container = this.playerWrapper || this.video?.parentElement;
|
|
1470
|
+
if (!container) return;
|
|
1471
|
+
|
|
1472
|
+
// Destroy current player
|
|
1473
|
+
if (this.youtubePlayer) {
|
|
1474
|
+
this.youtubePlayer.destroy();
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
// Remove existing iframe container
|
|
1478
|
+
const existingContainer = container.querySelector(`#youtube-player-${videoId}`);
|
|
1479
|
+
if (existingContainer) {
|
|
1480
|
+
existingContainer.remove();
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// Create new iframe container
|
|
1484
|
+
const iframeContainer = document.createElement('div');
|
|
1485
|
+
iframeContainer.id = `youtube-player-${videoId}`;
|
|
1486
|
+
iframeContainer.style.cssText = `
|
|
1487
|
+
position: absolute;
|
|
1488
|
+
top: 0;
|
|
1489
|
+
left: 0;
|
|
1490
|
+
width: 100%;
|
|
1491
|
+
height: 100%;
|
|
1492
|
+
z-index: 1;
|
|
1493
|
+
`;
|
|
1494
|
+
container.appendChild(iframeContainer);
|
|
1495
|
+
|
|
1496
|
+
// Create new player with native controls
|
|
1497
|
+
this.youtubePlayer = new window.YT.Player(iframeContainer.id, {
|
|
1498
|
+
videoId: videoId,
|
|
1499
|
+
width: '100%',
|
|
1500
|
+
height: '100%',
|
|
1501
|
+
playerVars: {
|
|
1502
|
+
autoplay: this.config.autoPlay ? 1 : 0,
|
|
1503
|
+
controls: 1, // Enable native controls
|
|
1504
|
+
modestbranding: 1,
|
|
1505
|
+
rel: 0,
|
|
1506
|
+
showinfo: 0,
|
|
1507
|
+
iv_load_policy: 3,
|
|
1508
|
+
playsinline: 1,
|
|
1509
|
+
start: Math.floor(currentTime)
|
|
1510
|
+
},
|
|
1511
|
+
events: {
|
|
1512
|
+
onReady: () => {
|
|
1513
|
+
this.youtubePlayerReady = true;
|
|
1514
|
+
this.debugLog('YouTube player with native controls ready');
|
|
1515
|
+
this.emit('onReady');
|
|
1516
|
+
},
|
|
1517
|
+
onStateChange: (event: any) => this.onYouTubePlayerStateChange(event),
|
|
1518
|
+
onError: (event: any) => this.onYouTubePlayerError(event)
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
/**
|
|
1524
|
+
* Toggle between native and custom YouTube controls
|
|
1525
|
+
*/
|
|
1526
|
+
public toggleYouTubeControls(useNative: boolean = !this.useYouTubeNativeControls): void {
|
|
1527
|
+
if (!this.youtubePlayer) {
|
|
1528
|
+
this.debugWarn('Cannot toggle YouTube controls - no YouTube player active');
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
this.useYouTubeNativeControls = useNative;
|
|
1533
|
+
this.config.youtubeNativeControls = useNative; // Update config
|
|
1534
|
+
this.updateControlsVisibility();
|
|
1535
|
+
|
|
1536
|
+
this.debugLog('YouTube controls toggled:', {
|
|
1537
|
+
useNative: this.useYouTubeNativeControls,
|
|
1538
|
+
isLive: this.isYouTubeLive
|
|
1539
|
+
});
|
|
1540
|
+
|
|
1541
|
+
// Show notification
|
|
1542
|
+
this.showNotification(
|
|
1543
|
+
`YouTube Controls: ${this.useYouTubeNativeControls ? 'Native' : 'Custom'}`
|
|
1544
|
+
);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1405
1547
|
/**
|
|
1406
1548
|
* Fallback method to get title from YouTube oembed API
|
|
1407
1549
|
*/
|
|
@@ -1433,243 +1575,24 @@ export class WebPlayer extends BasePlayer {
|
|
|
1433
1575
|
}
|
|
1434
1576
|
|
|
1435
1577
|
private youtubeTimeTrackingInterval: NodeJS.Timeout | null = null;
|
|
1436
|
-
private
|
|
1437
|
-
private
|
|
1438
|
-
|
|
1439
|
-
/**
|
|
1440
|
-
* Extract available YouTube video qualities with retry logic
|
|
1441
|
-
*/
|
|
1442
|
-
private extractYouTubeAvailableQualities(retryCount: number = 0): void {
|
|
1443
|
-
if (!this.youtubePlayer || !this.youtubePlayerReady) {
|
|
1444
|
-
this.debugLog('YouTube player not ready for quality extraction');
|
|
1445
|
-
return;
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
try {
|
|
1449
|
-
const availableQualityLevels = this.youtubePlayer.getAvailableQualityLevels();
|
|
1450
|
-
this.debugLog('🔍 YouTube quality detection attempt', retryCount + 1, '- Found levels:', availableQualityLevels);
|
|
1451
|
-
this.debugLog('🔍 YouTube player state:', this.youtubePlayer.getPlayerState());
|
|
1452
|
-
this.debugLog('🔍 Current YouTube quality:', this.youtubePlayer.getPlaybackQuality());
|
|
1453
|
-
|
|
1454
|
-
// If no qualities detected and we haven't retried too much, retry after a delay
|
|
1455
|
-
if ((!availableQualityLevels || availableQualityLevels.length === 0 || (availableQualityLevels.length === 1 && availableQualityLevels[0] === 'auto')) && retryCount < 5) {
|
|
1456
|
-
this.debugLog('No meaningful qualities detected, retrying in', (retryCount + 1) * 1000, 'ms...');
|
|
1457
|
-
setTimeout(() => {
|
|
1458
|
-
this.extractYouTubeAvailableQualities(retryCount + 1);
|
|
1459
|
-
}, (retryCount + 1) * 1000);
|
|
1460
|
-
return;
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
// Map YouTube quality levels to standard labels
|
|
1464
|
-
const qualityMap: Record<string, any> = {
|
|
1465
|
-
'hd2160': { label: '4K', value: 'hd2160', height: 2160 },
|
|
1466
|
-
'hd1440': { label: '1440p', value: 'hd1440', height: 1440 },
|
|
1467
|
-
'hd1080': { label: '1080p', value: 'hd1080', height: 1080 },
|
|
1468
|
-
'hd720': { label: '720p', value: 'hd720', height: 720 },
|
|
1469
|
-
'large': { label: '480p', value: 'large', height: 480 },
|
|
1470
|
-
'medium': { label: '360p', value: 'medium', height: 360 },
|
|
1471
|
-
'small': { label: '240p', value: 'small', height: 240 },
|
|
1472
|
-
'tiny': { label: '144p', value: 'tiny', height: 144 },
|
|
1473
|
-
'auto': { label: 'Auto', value: 'auto', height: 0 }
|
|
1474
|
-
};
|
|
1475
|
-
|
|
1476
|
-
this.youtubeAvailableQualities = [];
|
|
1477
|
-
|
|
1478
|
-
// Always add auto quality option first
|
|
1479
|
-
this.youtubeAvailableQualities.push({ label: 'Auto', value: 'auto', height: 0 });
|
|
1480
|
-
|
|
1481
|
-
// Add detected qualities, filtering out 'auto' since we already added it
|
|
1482
|
-
if (availableQualityLevels && availableQualityLevels.length > 0) {
|
|
1483
|
-
availableQualityLevels.forEach((qualityLevel: string) => {
|
|
1484
|
-
if (qualityLevel !== 'auto' && qualityMap[qualityLevel]) {
|
|
1485
|
-
this.youtubeAvailableQualities.push(qualityMap[qualityLevel]);
|
|
1486
|
-
}
|
|
1487
|
-
});
|
|
1488
|
-
|
|
1489
|
-
// Sort qualities by height (descending, excluding auto)
|
|
1490
|
-
const autoQuality = this.youtubeAvailableQualities.shift(); // Remove auto temporarily
|
|
1491
|
-
this.youtubeAvailableQualities.sort((a, b) => (b.height || 0) - (a.height || 0));
|
|
1492
|
-
if (autoQuality) this.youtubeAvailableQualities.unshift(autoQuality); // Add auto back at beginning
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
this.debugLog('✅ Successfully mapped YouTube qualities:', this.youtubeAvailableQualities);
|
|
1496
|
-
|
|
1497
|
-
// Get current quality
|
|
1498
|
-
try {
|
|
1499
|
-
const currentQuality = this.youtubePlayer.getPlaybackQuality();
|
|
1500
|
-
this.youtubeCurrentQuality = qualityMap[currentQuality] || qualityMap['auto'];
|
|
1501
|
-
this.debugLog('Current YouTube quality:', this.youtubeCurrentQuality);
|
|
1502
|
-
} catch (e) {
|
|
1503
|
-
this.debugWarn('Could not get current YouTube quality:', e);
|
|
1504
|
-
this.youtubeCurrentQuality = qualityMap['auto'];
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
// Update detected qualities to include YouTube qualities
|
|
1508
|
-
this.detectAvailableQualities();
|
|
1509
|
-
|
|
1510
|
-
this.debugLog('🚀 YouTube qualities successfully detected! Available qualities now:', this.availableQualities.length);
|
|
1511
|
-
|
|
1512
|
-
// Update quality badge with current YouTube quality
|
|
1513
|
-
this.updateQualityBadge();
|
|
1514
|
-
|
|
1515
|
-
// Force settings menu refresh with new qualities
|
|
1516
|
-
setTimeout(() => {
|
|
1517
|
-
this.debugLog('🔄 Force refreshing settings menu with YouTube qualities');
|
|
1518
|
-
this.updateSettingsMenu();
|
|
1519
|
-
}, 100);
|
|
1520
|
-
|
|
1521
|
-
} catch (error) {
|
|
1522
|
-
this.debugWarn('Could not extract YouTube qualities:', error);
|
|
1523
|
-
// Retry if we haven't tried too many times
|
|
1524
|
-
if (retryCount < 3) {
|
|
1525
|
-
setTimeout(() => {
|
|
1526
|
-
this.extractYouTubeAvailableQualities(retryCount + 1);
|
|
1527
|
-
}, 2000);
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
/**
|
|
1533
|
-
* Set YouTube video quality
|
|
1534
|
-
*/
|
|
1535
|
-
private setYouTubeQuality(qualityLevel: string): void {
|
|
1536
|
-
if (!this.youtubePlayer) return;
|
|
1537
|
-
|
|
1538
|
-
try {
|
|
1539
|
-
const qualityMap: Record<string, any> = {
|
|
1540
|
-
'hd2160': { label: '4K', value: 'hd2160', height: 2160 },
|
|
1541
|
-
'hd1440': { label: '1440p', value: 'hd1440', height: 1440 },
|
|
1542
|
-
'hd1080': { label: '1080p', value: 'hd1080', height: 1080 },
|
|
1543
|
-
'hd720': { label: '720p', value: 'hd720', height: 720 },
|
|
1544
|
-
'large': { label: '480p', value: 'large', height: 480 },
|
|
1545
|
-
'medium': { label: '360p', value: 'medium', height: 360 },
|
|
1546
|
-
'small': { label: '240p', value: 'small', height: 240 },
|
|
1547
|
-
'tiny': { label: '144p', value: 'tiny', height: 144 },
|
|
1548
|
-
'auto': { label: 'Auto', value: 'auto', height: 0 }
|
|
1549
|
-
};
|
|
1550
|
-
|
|
1551
|
-
this.debugLog('📊 Attempting to set YouTube quality to:', qualityLevel);
|
|
1552
|
-
|
|
1553
|
-
// Multiple approaches to force YouTube quality change
|
|
1554
|
-
if (qualityLevel === 'auto') {
|
|
1555
|
-
// For auto, just use setPlaybackQuality
|
|
1556
|
-
this.youtubePlayer.setPlaybackQuality(qualityLevel);
|
|
1557
|
-
} else {
|
|
1558
|
-
// For specific qualities, try multiple methods
|
|
1559
|
-
try {
|
|
1560
|
-
// Method 1: Set quality range (most effective for forcing specific quality)
|
|
1561
|
-
if (typeof this.youtubePlayer.setPlaybackQualityRange === 'function') {
|
|
1562
|
-
this.youtubePlayer.setPlaybackQualityRange(qualityLevel, qualityLevel);
|
|
1563
|
-
this.debugLog('✅ setPlaybackQualityRange called for:', qualityLevel);
|
|
1564
|
-
}
|
|
1565
|
-
} catch (e) {
|
|
1566
|
-
this.debugWarn('setPlaybackQualityRange failed:', e);
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
try {
|
|
1570
|
-
// Method 2: Standard quality setting
|
|
1571
|
-
this.youtubePlayer.setPlaybackQuality(qualityLevel);
|
|
1572
|
-
this.debugLog('✅ setPlaybackQuality called for:', qualityLevel);
|
|
1573
|
-
} catch (e) {
|
|
1574
|
-
this.debugWarn('setPlaybackQuality failed:', e);
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
// Method 3: Force quality by pausing and resuming (more aggressive)
|
|
1578
|
-
try {
|
|
1579
|
-
const wasPlaying = this.youtubePlayer.getPlayerState() === window.YT.PlayerState.PLAYING;
|
|
1580
|
-
if (wasPlaying) {
|
|
1581
|
-
this.youtubePlayer.pauseVideo();
|
|
1582
|
-
setTimeout(() => {
|
|
1583
|
-
this.youtubePlayer.setPlaybackQuality(qualityLevel);
|
|
1584
|
-
this.youtubePlayer.playVideo();
|
|
1585
|
-
}, 100);
|
|
1586
|
-
}
|
|
1587
|
-
} catch (e) {
|
|
1588
|
-
this.debugWarn('Force quality change with pause/play failed:', e);
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
// Verify quality was changed after a delay
|
|
1593
|
-
setTimeout(() => {
|
|
1594
|
-
try {
|
|
1595
|
-
const currentQuality = this.youtubePlayer.getPlaybackQuality();
|
|
1596
|
-
this.youtubeCurrentQuality = qualityMap[currentQuality] || qualityMap['auto'];
|
|
1597
|
-
|
|
1598
|
-
this.debugLog('📊 Current quality after set:', currentQuality, '(', this.youtubeCurrentQuality?.label, ')');
|
|
1599
|
-
|
|
1600
|
-
// Show notification with result - be more user-friendly
|
|
1601
|
-
if (currentQuality === qualityLevel) {
|
|
1602
|
-
this.showNotification(`Quality: ${qualityMap[currentQuality]?.label || 'requested quality'}`);
|
|
1603
|
-
this.debugLog('✅ Quality successfully changed to:', qualityLevel);
|
|
1604
|
-
} else {
|
|
1605
|
-
// Don't show "YouTube optimized" message to users - it's confusing
|
|
1606
|
-
this.showNotification(`Quality: ${qualityMap[currentQuality]?.label || currentQuality}`);
|
|
1607
|
-
this.debugLog('⚠️ Requested quality:', qualityLevel, '| Actual quality:', currentQuality, '(YouTube may have optimized)');
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
// Update quality badge
|
|
1611
|
-
this.updateQualityBadge();
|
|
1612
|
-
|
|
1613
|
-
// Update settings menu UI
|
|
1614
|
-
this.updateSettingsMenu();
|
|
1615
|
-
} catch (verifyError) {
|
|
1616
|
-
this.debugWarn('Could not verify YouTube quality:', verifyError);
|
|
1617
|
-
}
|
|
1618
|
-
}, 1000);
|
|
1619
|
-
|
|
1620
|
-
} catch (error) {
|
|
1621
|
-
this.debugWarn('❌ Could not set YouTube quality:', error);
|
|
1622
|
-
this.showNotification('Quality control limited on YouTube');
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
/**
|
|
1627
|
-
* Update quality badge with current quality
|
|
1628
|
-
*/
|
|
1629
|
-
private updateQualityBadge(): void {
|
|
1630
|
-
const qualityBadge = document.getElementById('uvf-quality-badge');
|
|
1631
|
-
if (!qualityBadge) return;
|
|
1632
|
-
|
|
1633
|
-
// For YouTube, use YouTube current quality
|
|
1634
|
-
if (this.youtubePlayer && this.youtubePlayerReady && this.youtubeCurrentQuality) {
|
|
1635
|
-
qualityBadge.textContent = this.youtubeCurrentQuality.label || 'AUTO';
|
|
1636
|
-
this.debugLog('Updated quality badge for YouTube:', this.youtubeCurrentQuality.label);
|
|
1637
|
-
} else if (this.currentQualityIndex >= 0 && this.qualities[this.currentQualityIndex]) {
|
|
1638
|
-
// For other sources, use current quality
|
|
1639
|
-
const quality = this.qualities[this.currentQualityIndex];
|
|
1640
|
-
qualityBadge.textContent = quality.label || 'HD';
|
|
1641
|
-
this.debugLog('Updated quality badge for standard video:', quality.label);
|
|
1642
|
-
} else {
|
|
1643
|
-
// Default
|
|
1644
|
-
qualityBadge.textContent = 'AUTO';
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1578
|
+
private isYouTubeLive: boolean = false;
|
|
1579
|
+
private useYouTubeNativeControls: boolean = false;
|
|
1647
1580
|
|
|
1648
1581
|
private startYouTubeTimeTracking(): void {
|
|
1649
1582
|
if (this.youtubeTimeTrackingInterval) {
|
|
1650
1583
|
clearInterval(this.youtubeTimeTrackingInterval);
|
|
1651
1584
|
}
|
|
1652
1585
|
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
this.state.duration = duration || 0;
|
|
1664
|
-
this.state.bufferedPercentage = buffered || 0;
|
|
1665
|
-
|
|
1666
|
-
// Check for qualities periodically (every 10 seconds) if we don't have them yet
|
|
1667
|
-
qualityCheckCounter++;
|
|
1668
|
-
if (qualityCheckCounter >= 40 && this.youtubeAvailableQualities.length <= 1) { // 40 * 250ms = 10s
|
|
1669
|
-
this.debugLog('Periodic YouTube quality check triggered');
|
|
1670
|
-
this.extractYouTubeAvailableQualities();
|
|
1671
|
-
qualityCheckCounter = 0; // Reset counter
|
|
1672
|
-
}
|
|
1586
|
+
this.youtubeTimeTrackingInterval = setInterval(() => {
|
|
1587
|
+
if (this.youtubePlayer && this.youtubePlayerReady) {
|
|
1588
|
+
try {
|
|
1589
|
+
const currentTime = this.youtubePlayer.getCurrentTime();
|
|
1590
|
+
const duration = this.youtubePlayer.getDuration();
|
|
1591
|
+
const buffered = this.youtubePlayer.getVideoLoadedFraction() * 100;
|
|
1592
|
+
|
|
1593
|
+
this.state.currentTime = currentTime || 0;
|
|
1594
|
+
this.state.duration = duration || 0;
|
|
1595
|
+
this.state.bufferedPercentage = buffered || 0;
|
|
1673
1596
|
|
|
1674
1597
|
// Update UI progress bar
|
|
1675
1598
|
this.updateYouTubeProgressBar(currentTime, duration, buffered);
|
|
@@ -9450,23 +9373,15 @@ export class WebPlayer extends BasePlayer {
|
|
|
9450
9373
|
private setQualityByLabel(quality: string): void {
|
|
9451
9374
|
// Handle YouTube player
|
|
9452
9375
|
if (this.youtubePlayer && this.youtubePlayerReady) {
|
|
9453
|
-
//
|
|
9454
|
-
this.
|
|
9455
|
-
|
|
9456
|
-
// Update UI to reflect selection
|
|
9376
|
+
// YouTube quality is limited - show notification
|
|
9377
|
+
this.showShortcutIndicator('Quality control not available for YouTube');
|
|
9378
|
+
// Update UI to reflect current quality
|
|
9457
9379
|
document.querySelectorAll('.quality-option').forEach(option => {
|
|
9458
9380
|
option.classList.remove('active');
|
|
9459
|
-
if ((option as HTMLElement).dataset.quality ===
|
|
9381
|
+
if ((option as HTMLElement).dataset.quality === 'auto') {
|
|
9460
9382
|
option.classList.add('active');
|
|
9461
9383
|
}
|
|
9462
9384
|
});
|
|
9463
|
-
|
|
9464
|
-
// Update quality badge
|
|
9465
|
-
const qualityBadge = document.getElementById('uvf-quality-badge');
|
|
9466
|
-
if (qualityBadge) {
|
|
9467
|
-
const qualityOption = this.youtubeAvailableQualities.find(q => q.value === quality);
|
|
9468
|
-
qualityBadge.textContent = qualityOption ? qualityOption.label : 'AUTO';
|
|
9469
|
-
}
|
|
9470
9385
|
return;
|
|
9471
9386
|
}
|
|
9472
9387
|
|
|
@@ -9767,17 +9682,9 @@ export class WebPlayer extends BasePlayer {
|
|
|
9767
9682
|
|
|
9768
9683
|
// Quality Accordion Section (only if enabled in config and qualities detected)
|
|
9769
9684
|
if (this.settingsConfig.quality && this.availableQualities.length > 0) {
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
// For YouTube, use youtubeCurrentQuality; otherwise use currentQuality
|
|
9773
|
-
const qualityForDisplay = this.youtubePlayer && this.youtubePlayerReady && this.youtubeCurrentQuality
|
|
9774
|
-
? this.youtubeCurrentQuality.value
|
|
9775
|
-
: this.currentQuality;
|
|
9776
|
-
const currentQuality = this.availableQualities.find(q => q.value === qualityForDisplay);
|
|
9685
|
+
const currentQuality = this.availableQualities.find(q => q.value === this.currentQuality);
|
|
9777
9686
|
const currentQualityLabel = currentQuality ? currentQuality.label : 'Auto';
|
|
9778
9687
|
|
|
9779
|
-
this.debugLog('🎛️ Current quality for display:', qualityForDisplay, '(', currentQualityLabel, ')');
|
|
9780
|
-
|
|
9781
9688
|
menuHTML += `
|
|
9782
9689
|
<div class="uvf-accordion-item">
|
|
9783
9690
|
<div class="uvf-accordion-header" data-section="quality">
|
|
@@ -9795,11 +9702,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
9795
9702
|
<div class="uvf-accordion-content" data-section="quality">`;
|
|
9796
9703
|
|
|
9797
9704
|
this.availableQualities.forEach(quality => {
|
|
9798
|
-
|
|
9799
|
-
const qualityValue = this.youtubePlayer && this.youtubePlayerReady && this.youtubeCurrentQuality
|
|
9800
|
-
? this.youtubeCurrentQuality.value
|
|
9801
|
-
: this.currentQuality;
|
|
9802
|
-
const isActive = quality.value === qualityValue ? 'active' : '';
|
|
9705
|
+
const isActive = quality.value === this.currentQuality ? 'active' : '';
|
|
9803
9706
|
const isPremium = this.isQualityPremium(quality);
|
|
9804
9707
|
const isLocked = isPremium && !this.isPremiumUser();
|
|
9805
9708
|
const qualityHeight = (quality as any).height || 0;
|
|
@@ -9867,22 +9770,10 @@ export class WebPlayer extends BasePlayer {
|
|
|
9867
9770
|
* Detect available video qualities from different sources
|
|
9868
9771
|
*/
|
|
9869
9772
|
private detectAvailableQualities(): void {
|
|
9870
|
-
|
|
9871
|
-
const isYouTube = this.youtubePlayer && this.youtubePlayerReady && this.youtubeAvailableQualities.length > 0;
|
|
9872
|
-
this.availableQualities = [];
|
|
9773
|
+
this.availableQualities = [{ value: 'auto', label: 'Auto' }];
|
|
9873
9774
|
let detectedQualities: Array<{ value: string; label: string; height?: number }> = [];
|
|
9874
9775
|
|
|
9875
|
-
|
|
9876
|
-
if (isYouTube) {
|
|
9877
|
-
detectedQualities = this.youtubeAvailableQualities.map(q => ({
|
|
9878
|
-
value: q.value,
|
|
9879
|
-
label: q.label,
|
|
9880
|
-
height: q.height
|
|
9881
|
-
}));
|
|
9882
|
-
this.debugLog('Using YouTube qualities:', detectedQualities);
|
|
9883
|
-
this.availableQualities = detectedQualities;
|
|
9884
|
-
return; // Early return for YouTube
|
|
9885
|
-
} else if (this.hls && this.hls.levels) {
|
|
9776
|
+
if (this.hls && this.hls.levels) {
|
|
9886
9777
|
// HLS qualities
|
|
9887
9778
|
this.hls.levels.forEach((level: any, index: number) => {
|
|
9888
9779
|
if (level.height) {
|
|
@@ -10254,13 +10145,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
10254
10145
|
private setQualityFromSettings(quality: string): void {
|
|
10255
10146
|
this.currentQuality = quality;
|
|
10256
10147
|
|
|
10257
|
-
// Handle YouTube quality
|
|
10258
|
-
if (this.youtubePlayer && this.youtubePlayerReady) {
|
|
10259
|
-
this.setYouTubeQuality(quality);
|
|
10260
|
-
this.debugLog(`YouTube quality set to ${quality}`);
|
|
10261
|
-
return;
|
|
10262
|
-
}
|
|
10263
|
-
|
|
10264
10148
|
if (quality === 'auto') {
|
|
10265
10149
|
// Enable auto quality with filter consideration
|
|
10266
10150
|
if (this.hls) {
|