unified-video-framework 1.4.251 → 1.4.253
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.
|
@@ -1255,6 +1255,9 @@ export class WebPlayer extends BasePlayer {
|
|
|
1255
1255
|
// Extract available YouTube qualities
|
|
1256
1256
|
this.extractYouTubeAvailableQualities();
|
|
1257
1257
|
|
|
1258
|
+
// Try to get video title from YouTube API
|
|
1259
|
+
this.getYouTubeVideoTitle();
|
|
1260
|
+
|
|
1258
1261
|
// Update metadata UI and settings after player is ready
|
|
1259
1262
|
setTimeout(() => {
|
|
1260
1263
|
this.updateMetadataUI();
|
|
@@ -1277,6 +1280,12 @@ export class WebPlayer extends BasePlayer {
|
|
|
1277
1280
|
this.state.isPaused = false;
|
|
1278
1281
|
this.state.isBuffering = false;
|
|
1279
1282
|
this.updateYouTubeUI('playing');
|
|
1283
|
+
|
|
1284
|
+
// Extract qualities when video starts playing
|
|
1285
|
+
setTimeout(() => {
|
|
1286
|
+
this.extractYouTubeAvailableQualities();
|
|
1287
|
+
}, 1000);
|
|
1288
|
+
|
|
1280
1289
|
this.emit('onPlay');
|
|
1281
1290
|
break;
|
|
1282
1291
|
|
|
@@ -1291,6 +1300,12 @@ export class WebPlayer extends BasePlayer {
|
|
|
1291
1300
|
case window.YT.PlayerState.BUFFERING:
|
|
1292
1301
|
this.state.isBuffering = true;
|
|
1293
1302
|
this.updateYouTubeUI('buffering');
|
|
1303
|
+
|
|
1304
|
+
// Try to extract qualities during buffering as well
|
|
1305
|
+
setTimeout(() => {
|
|
1306
|
+
this.extractYouTubeAvailableQualities();
|
|
1307
|
+
}, 500);
|
|
1308
|
+
|
|
1294
1309
|
this.emit('onBuffering', true);
|
|
1295
1310
|
break;
|
|
1296
1311
|
|
|
@@ -1305,6 +1320,11 @@ export class WebPlayer extends BasePlayer {
|
|
|
1305
1320
|
case window.YT.PlayerState.CUED:
|
|
1306
1321
|
this.state.duration = this.youtubePlayer.getDuration();
|
|
1307
1322
|
this.updateYouTubeUI('cued');
|
|
1323
|
+
|
|
1324
|
+
// Try to get video title when video is cued
|
|
1325
|
+
setTimeout(() => {
|
|
1326
|
+
this.getYouTubeVideoTitle();
|
|
1327
|
+
}, 100);
|
|
1308
1328
|
break;
|
|
1309
1329
|
}
|
|
1310
1330
|
}
|
|
@@ -1354,19 +1374,91 @@ export class WebPlayer extends BasePlayer {
|
|
|
1354
1374
|
});
|
|
1355
1375
|
}
|
|
1356
1376
|
|
|
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
|
+
* Fallback method to get title from YouTube oembed API
|
|
1407
|
+
*/
|
|
1408
|
+
private async getYouTubeVideoTitleFromOEmbed(): Promise<void> {
|
|
1409
|
+
if (!this.source?.metadata?.videoId) return;
|
|
1410
|
+
|
|
1411
|
+
try {
|
|
1412
|
+
const videoId = this.source.metadata.videoId;
|
|
1413
|
+
const oembedUrl = `https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${videoId}&format=json`;
|
|
1414
|
+
|
|
1415
|
+
const response = await fetch(oembedUrl);
|
|
1416
|
+
if (response.ok) {
|
|
1417
|
+
const data = await response.json();
|
|
1418
|
+
if (data.title) {
|
|
1419
|
+
this.debugLog('Got YouTube title from oembed API:', data.title);
|
|
1420
|
+
|
|
1421
|
+
// Update source metadata
|
|
1422
|
+
if (this.source && this.source.metadata) {
|
|
1423
|
+
this.source.metadata.title = data.title;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// Update UI
|
|
1427
|
+
this.updateMetadataUI();
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
} catch (error) {
|
|
1431
|
+
this.debugWarn('Could not get YouTube title from oembed API:', error);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1357
1435
|
private youtubeTimeTrackingInterval: NodeJS.Timeout | null = null;
|
|
1358
1436
|
private youtubeAvailableQualities: any[] = [];
|
|
1359
1437
|
private youtubeCurrentQuality: any = null;
|
|
1360
1438
|
|
|
1361
1439
|
/**
|
|
1362
|
-
* Extract available YouTube video qualities
|
|
1440
|
+
* Extract available YouTube video qualities with retry logic
|
|
1363
1441
|
*/
|
|
1364
|
-
private extractYouTubeAvailableQualities(): void {
|
|
1365
|
-
if (!this.youtubePlayer)
|
|
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
|
+
}
|
|
1366
1447
|
|
|
1367
1448
|
try {
|
|
1368
1449
|
const availableQualityLevels = this.youtubePlayer.getAvailableQualityLevels();
|
|
1369
|
-
this.debugLog('YouTube
|
|
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
|
+
}
|
|
1370
1462
|
|
|
1371
1463
|
// Map YouTube quality levels to standard labels
|
|
1372
1464
|
const qualityMap: Record<string, any> = {
|
|
@@ -1380,18 +1472,25 @@ export class WebPlayer extends BasePlayer {
|
|
|
1380
1472
|
};
|
|
1381
1473
|
|
|
1382
1474
|
this.youtubeAvailableQualities = [];
|
|
1383
|
-
availableQualityLevels.forEach((qualityLevel: string) => {
|
|
1384
|
-
// Skip 'auto' from YouTube API - we'll add it explicitly
|
|
1385
|
-
if (qualityLevel === 'auto') return;
|
|
1386
|
-
if (qualityMap[qualityLevel]) {
|
|
1387
|
-
this.youtubeAvailableQualities.push(qualityMap[qualityLevel]);
|
|
1388
|
-
}
|
|
1389
|
-
});
|
|
1390
1475
|
|
|
1391
|
-
// Always add auto quality option
|
|
1392
|
-
this.youtubeAvailableQualities.
|
|
1476
|
+
// Always add auto quality option first
|
|
1477
|
+
this.youtubeAvailableQualities.push({ label: 'Auto', value: 'auto', height: 0 });
|
|
1393
1478
|
|
|
1394
|
-
|
|
1479
|
+
// Add detected qualities, filtering out 'auto' since we already added it
|
|
1480
|
+
if (availableQualityLevels && availableQualityLevels.length > 0) {
|
|
1481
|
+
availableQualityLevels.forEach((qualityLevel: string) => {
|
|
1482
|
+
if (qualityLevel !== 'auto' && qualityMap[qualityLevel]) {
|
|
1483
|
+
this.youtubeAvailableQualities.push(qualityMap[qualityLevel]);
|
|
1484
|
+
}
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
// Sort qualities by height (descending, excluding auto)
|
|
1488
|
+
const autoQuality = this.youtubeAvailableQualities.shift(); // Remove auto temporarily
|
|
1489
|
+
this.youtubeAvailableQualities.sort((a, b) => (b.height || 0) - (a.height || 0));
|
|
1490
|
+
if (autoQuality) this.youtubeAvailableQualities.unshift(autoQuality); // Add auto back at beginning
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
this.debugLog('✅ Successfully mapped YouTube qualities:', this.youtubeAvailableQualities);
|
|
1395
1494
|
|
|
1396
1495
|
// Get current quality
|
|
1397
1496
|
try {
|
|
@@ -1403,11 +1502,28 @@ export class WebPlayer extends BasePlayer {
|
|
|
1403
1502
|
this.youtubeCurrentQuality = qualityMap['auto'];
|
|
1404
1503
|
}
|
|
1405
1504
|
|
|
1406
|
-
// Update
|
|
1407
|
-
this.
|
|
1505
|
+
// Update detected qualities to include YouTube qualities
|
|
1506
|
+
this.detectAvailableQualities();
|
|
1507
|
+
|
|
1508
|
+
this.debugLog('🚀 YouTube qualities successfully detected! Available qualities now:', this.availableQualities.length);
|
|
1509
|
+
|
|
1510
|
+
// Update quality badge with current YouTube quality
|
|
1511
|
+
this.updateQualityBadge();
|
|
1512
|
+
|
|
1513
|
+
// Force settings menu refresh with new qualities
|
|
1514
|
+
setTimeout(() => {
|
|
1515
|
+
this.debugLog('🔄 Force refreshing settings menu with YouTube qualities');
|
|
1516
|
+
this.updateSettingsMenu();
|
|
1517
|
+
}, 100);
|
|
1408
1518
|
|
|
1409
1519
|
} catch (error) {
|
|
1410
1520
|
this.debugWarn('Could not extract YouTube qualities:', error);
|
|
1521
|
+
// Retry if we haven't tried too many times
|
|
1522
|
+
if (retryCount < 3) {
|
|
1523
|
+
setTimeout(() => {
|
|
1524
|
+
this.extractYouTubeAvailableQualities(retryCount + 1);
|
|
1525
|
+
}, 2000);
|
|
1526
|
+
}
|
|
1411
1527
|
}
|
|
1412
1528
|
}
|
|
1413
1529
|
|
|
@@ -1451,6 +1567,9 @@ export class WebPlayer extends BasePlayer {
|
|
|
1451
1567
|
this.debugLog('⚠️ Requested quality:', qualityLevel, '| Actual quality:', currentQuality, '(YouTube may have optimized)');
|
|
1452
1568
|
}
|
|
1453
1569
|
|
|
1570
|
+
// Update quality badge
|
|
1571
|
+
this.updateQualityBadge();
|
|
1572
|
+
|
|
1454
1573
|
// Update settings menu UI
|
|
1455
1574
|
this.updateSettingsMenu();
|
|
1456
1575
|
} catch (verifyError) {
|
|
@@ -1463,12 +1582,36 @@ export class WebPlayer extends BasePlayer {
|
|
|
1463
1582
|
this.showNotification('Quality control limited on YouTube');
|
|
1464
1583
|
}
|
|
1465
1584
|
}
|
|
1585
|
+
|
|
1586
|
+
/**
|
|
1587
|
+
* Update quality badge with current quality
|
|
1588
|
+
*/
|
|
1589
|
+
private updateQualityBadge(): void {
|
|
1590
|
+
const qualityBadge = document.getElementById('uvf-quality-badge');
|
|
1591
|
+
if (!qualityBadge) return;
|
|
1592
|
+
|
|
1593
|
+
// For YouTube, use YouTube current quality
|
|
1594
|
+
if (this.youtubePlayer && this.youtubePlayerReady && this.youtubeCurrentQuality) {
|
|
1595
|
+
qualityBadge.textContent = this.youtubeCurrentQuality.label || 'AUTO';
|
|
1596
|
+
this.debugLog('Updated quality badge for YouTube:', this.youtubeCurrentQuality.label);
|
|
1597
|
+
} else if (this.currentQualityIndex >= 0 && this.qualities[this.currentQualityIndex]) {
|
|
1598
|
+
// For other sources, use current quality
|
|
1599
|
+
const quality = this.qualities[this.currentQualityIndex];
|
|
1600
|
+
qualityBadge.textContent = quality.label || 'HD';
|
|
1601
|
+
this.debugLog('Updated quality badge for standard video:', quality.label);
|
|
1602
|
+
} else {
|
|
1603
|
+
// Default
|
|
1604
|
+
qualityBadge.textContent = 'AUTO';
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1466
1607
|
|
|
1467
1608
|
private startYouTubeTimeTracking(): void {
|
|
1468
1609
|
if (this.youtubeTimeTrackingInterval) {
|
|
1469
1610
|
clearInterval(this.youtubeTimeTrackingInterval);
|
|
1470
1611
|
}
|
|
1471
1612
|
|
|
1613
|
+
let qualityCheckCounter = 0;
|
|
1614
|
+
|
|
1472
1615
|
this.youtubeTimeTrackingInterval = setInterval(() => {
|
|
1473
1616
|
if (this.youtubePlayer && this.youtubePlayerReady) {
|
|
1474
1617
|
try {
|
|
@@ -1480,6 +1623,14 @@ export class WebPlayer extends BasePlayer {
|
|
|
1480
1623
|
this.state.duration = duration || 0;
|
|
1481
1624
|
this.state.bufferedPercentage = buffered || 0;
|
|
1482
1625
|
|
|
1626
|
+
// Check for qualities periodically (every 10 seconds) if we don't have them yet
|
|
1627
|
+
qualityCheckCounter++;
|
|
1628
|
+
if (qualityCheckCounter >= 40 && this.youtubeAvailableQualities.length <= 1) { // 40 * 250ms = 10s
|
|
1629
|
+
this.debugLog('Periodic YouTube quality check triggered');
|
|
1630
|
+
this.extractYouTubeAvailableQualities();
|
|
1631
|
+
qualityCheckCounter = 0; // Reset counter
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1483
1634
|
// Update UI progress bar
|
|
1484
1635
|
this.updateYouTubeProgressBar(currentTime, duration, buffered);
|
|
1485
1636
|
|
|
@@ -1968,12 +2119,27 @@ export class WebPlayer extends BasePlayer {
|
|
|
1968
2119
|
private updateTimeTooltip(e: MouseEvent): void {
|
|
1969
2120
|
const progressBar = document.getElementById('uvf-progress-bar');
|
|
1970
2121
|
const tooltip = document.getElementById('uvf-time-tooltip');
|
|
1971
|
-
if (!progressBar || !tooltip
|
|
2122
|
+
if (!progressBar || !tooltip) return;
|
|
2123
|
+
|
|
2124
|
+
// Get duration from appropriate source
|
|
2125
|
+
let duration = 0;
|
|
2126
|
+
if (this.youtubePlayer && this.youtubePlayerReady) {
|
|
2127
|
+
try {
|
|
2128
|
+
duration = this.youtubePlayer.getDuration() || 0;
|
|
2129
|
+
} catch (error) {
|
|
2130
|
+
this.debugWarn('Error getting YouTube duration for tooltip:', error);
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
} else if (this.video) {
|
|
2134
|
+
duration = this.video.duration || 0;
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
if (!duration || !isFinite(duration)) return;
|
|
1972
2138
|
|
|
1973
2139
|
const rect = progressBar.getBoundingClientRect();
|
|
1974
2140
|
const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
|
|
1975
2141
|
const percent = (x / rect.width);
|
|
1976
|
-
const time = percent *
|
|
2142
|
+
const time = percent * duration;
|
|
1977
2143
|
|
|
1978
2144
|
// Update tooltip content and position
|
|
1979
2145
|
tooltip.textContent = this.formatTime(time);
|
|
@@ -6789,7 +6955,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
6789
6955
|
const qualityBadge = document.createElement('div');
|
|
6790
6956
|
qualityBadge.className = 'uvf-quality-badge';
|
|
6791
6957
|
qualityBadge.id = 'uvf-quality-badge';
|
|
6792
|
-
qualityBadge.textContent = '
|
|
6958
|
+
qualityBadge.textContent = 'AUTO'; // Default to AUTO for better UX
|
|
6793
6959
|
rightControls.appendChild(qualityBadge);
|
|
6794
6960
|
|
|
6795
6961
|
// Settings button with menu (show only if enabled)
|
|
@@ -9539,6 +9705,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
9539
9705
|
|
|
9540
9706
|
// Quality Accordion Section (only if enabled in config and qualities detected)
|
|
9541
9707
|
if (this.settingsConfig.quality && this.availableQualities.length > 0) {
|
|
9708
|
+
this.debugLog('🎛️ Building quality menu with', this.availableQualities.length, 'qualities:', this.availableQualities.map(q => q.label));
|
|
9709
|
+
|
|
9542
9710
|
// For YouTube, use youtubeCurrentQuality; otherwise use currentQuality
|
|
9543
9711
|
const qualityForDisplay = this.youtubePlayer && this.youtubePlayerReady && this.youtubeCurrentQuality
|
|
9544
9712
|
? this.youtubeCurrentQuality.value
|
|
@@ -9546,6 +9714,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
9546
9714
|
const currentQuality = this.availableQualities.find(q => q.value === qualityForDisplay);
|
|
9547
9715
|
const currentQualityLabel = currentQuality ? currentQuality.label : 'Auto';
|
|
9548
9716
|
|
|
9717
|
+
this.debugLog('🎛️ Current quality for display:', qualityForDisplay, '(', currentQualityLabel, ')');
|
|
9718
|
+
|
|
9549
9719
|
menuHTML += `
|
|
9550
9720
|
<div class="uvf-accordion-item">
|
|
9551
9721
|
<div class="uvf-accordion-header" data-section="quality">
|