unified-video-framework 1.4.250 → 1.4.252
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.
|
@@ -1122,6 +1122,11 @@ export class WebPlayer extends BasePlayer {
|
|
|
1122
1122
|
// Create YouTube iframe player with custom controls integration
|
|
1123
1123
|
await this.createYouTubePlayer(videoId);
|
|
1124
1124
|
|
|
1125
|
+
// Force another metadata update after player creation
|
|
1126
|
+
setTimeout(() => {
|
|
1127
|
+
this.updateMetadataUI();
|
|
1128
|
+
}, 1000);
|
|
1129
|
+
|
|
1125
1130
|
this.debugLog('✅ YouTube video loaded successfully');
|
|
1126
1131
|
this.debugLog('YouTube video title:', metadata.title);
|
|
1127
1132
|
} catch (error) {
|
|
@@ -1249,6 +1254,15 @@ export class WebPlayer extends BasePlayer {
|
|
|
1249
1254
|
|
|
1250
1255
|
// Extract available YouTube qualities
|
|
1251
1256
|
this.extractYouTubeAvailableQualities();
|
|
1257
|
+
|
|
1258
|
+
// Try to get video title from YouTube API
|
|
1259
|
+
this.getYouTubeVideoTitle();
|
|
1260
|
+
|
|
1261
|
+
// Update metadata UI and settings after player is ready
|
|
1262
|
+
setTimeout(() => {
|
|
1263
|
+
this.updateMetadataUI();
|
|
1264
|
+
this.updateSettingsMenu();
|
|
1265
|
+
}, 500);
|
|
1252
1266
|
}
|
|
1253
1267
|
|
|
1254
1268
|
// Start time tracking
|
|
@@ -1294,6 +1308,11 @@ export class WebPlayer extends BasePlayer {
|
|
|
1294
1308
|
case window.YT.PlayerState.CUED:
|
|
1295
1309
|
this.state.duration = this.youtubePlayer.getDuration();
|
|
1296
1310
|
this.updateYouTubeUI('cued');
|
|
1311
|
+
|
|
1312
|
+
// Try to get video title when video is cued
|
|
1313
|
+
setTimeout(() => {
|
|
1314
|
+
this.getYouTubeVideoTitle();
|
|
1315
|
+
}, 100);
|
|
1297
1316
|
break;
|
|
1298
1317
|
}
|
|
1299
1318
|
}
|
|
@@ -1343,6 +1362,64 @@ export class WebPlayer extends BasePlayer {
|
|
|
1343
1362
|
});
|
|
1344
1363
|
}
|
|
1345
1364
|
|
|
1365
|
+
/**
|
|
1366
|
+
* Get YouTube video title from the player API
|
|
1367
|
+
*/
|
|
1368
|
+
private getYouTubeVideoTitle(): void {
|
|
1369
|
+
if (!this.youtubePlayer || !this.youtubePlayerReady) return;
|
|
1370
|
+
|
|
1371
|
+
try {
|
|
1372
|
+
// Try to get video data from YouTube player
|
|
1373
|
+
const videoData = this.youtubePlayer.getVideoData();
|
|
1374
|
+
if (videoData && videoData.title) {
|
|
1375
|
+
this.debugLog('Got YouTube title from player API:', videoData.title);
|
|
1376
|
+
|
|
1377
|
+
// Update source metadata with the correct title
|
|
1378
|
+
if (this.source && this.source.metadata) {
|
|
1379
|
+
this.source.metadata.title = videoData.title;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// Update UI immediately
|
|
1383
|
+
this.updateMetadataUI();
|
|
1384
|
+
}
|
|
1385
|
+
} catch (error) {
|
|
1386
|
+
this.debugWarn('Could not get YouTube video title from API:', error);
|
|
1387
|
+
|
|
1388
|
+
// Fallback: Try to get from oembed API
|
|
1389
|
+
this.getYouTubeVideoTitleFromOEmbed();
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
/**
|
|
1394
|
+
* Fallback method to get title from YouTube oembed API
|
|
1395
|
+
*/
|
|
1396
|
+
private async getYouTubeVideoTitleFromOEmbed(): Promise<void> {
|
|
1397
|
+
if (!this.source?.metadata?.videoId) return;
|
|
1398
|
+
|
|
1399
|
+
try {
|
|
1400
|
+
const videoId = this.source.metadata.videoId;
|
|
1401
|
+
const oembedUrl = `https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${videoId}&format=json`;
|
|
1402
|
+
|
|
1403
|
+
const response = await fetch(oembedUrl);
|
|
1404
|
+
if (response.ok) {
|
|
1405
|
+
const data = await response.json();
|
|
1406
|
+
if (data.title) {
|
|
1407
|
+
this.debugLog('Got YouTube title from oembed API:', data.title);
|
|
1408
|
+
|
|
1409
|
+
// Update source metadata
|
|
1410
|
+
if (this.source && this.source.metadata) {
|
|
1411
|
+
this.source.metadata.title = data.title;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// Update UI
|
|
1415
|
+
this.updateMetadataUI();
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
} catch (error) {
|
|
1419
|
+
this.debugWarn('Could not get YouTube title from oembed API:', error);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1346
1423
|
private youtubeTimeTrackingInterval: NodeJS.Timeout | null = null;
|
|
1347
1424
|
private youtubeAvailableQualities: any[] = [];
|
|
1348
1425
|
private youtubeCurrentQuality: any = null;
|
|
@@ -1392,6 +1469,9 @@ export class WebPlayer extends BasePlayer {
|
|
|
1392
1469
|
this.youtubeCurrentQuality = qualityMap['auto'];
|
|
1393
1470
|
}
|
|
1394
1471
|
|
|
1472
|
+
// Update quality badge with current YouTube quality
|
|
1473
|
+
this.updateQualityBadge();
|
|
1474
|
+
|
|
1395
1475
|
// Update settings menu to show YouTube qualities
|
|
1396
1476
|
this.updateSettingsMenu();
|
|
1397
1477
|
|
|
@@ -1440,6 +1520,9 @@ export class WebPlayer extends BasePlayer {
|
|
|
1440
1520
|
this.debugLog('⚠️ Requested quality:', qualityLevel, '| Actual quality:', currentQuality, '(YouTube may have optimized)');
|
|
1441
1521
|
}
|
|
1442
1522
|
|
|
1523
|
+
// Update quality badge
|
|
1524
|
+
this.updateQualityBadge();
|
|
1525
|
+
|
|
1443
1526
|
// Update settings menu UI
|
|
1444
1527
|
this.updateSettingsMenu();
|
|
1445
1528
|
} catch (verifyError) {
|
|
@@ -1452,6 +1535,28 @@ export class WebPlayer extends BasePlayer {
|
|
|
1452
1535
|
this.showNotification('Quality control limited on YouTube');
|
|
1453
1536
|
}
|
|
1454
1537
|
}
|
|
1538
|
+
|
|
1539
|
+
/**
|
|
1540
|
+
* Update quality badge with current quality
|
|
1541
|
+
*/
|
|
1542
|
+
private updateQualityBadge(): void {
|
|
1543
|
+
const qualityBadge = document.getElementById('uvf-quality-badge');
|
|
1544
|
+
if (!qualityBadge) return;
|
|
1545
|
+
|
|
1546
|
+
// For YouTube, use YouTube current quality
|
|
1547
|
+
if (this.youtubePlayer && this.youtubePlayerReady && this.youtubeCurrentQuality) {
|
|
1548
|
+
qualityBadge.textContent = this.youtubeCurrentQuality.label || 'AUTO';
|
|
1549
|
+
this.debugLog('Updated quality badge for YouTube:', this.youtubeCurrentQuality.label);
|
|
1550
|
+
} else if (this.currentQualityIndex >= 0 && this.qualities[this.currentQualityIndex]) {
|
|
1551
|
+
// For other sources, use current quality
|
|
1552
|
+
const quality = this.qualities[this.currentQualityIndex];
|
|
1553
|
+
qualityBadge.textContent = quality.label || 'HD';
|
|
1554
|
+
this.debugLog('Updated quality badge for standard video:', quality.label);
|
|
1555
|
+
} else {
|
|
1556
|
+
// Default
|
|
1557
|
+
qualityBadge.textContent = 'AUTO';
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1455
1560
|
|
|
1456
1561
|
private startYouTubeTimeTracking(): void {
|
|
1457
1562
|
if (this.youtubeTimeTrackingInterval) {
|
|
@@ -1486,16 +1591,18 @@ export class WebPlayer extends BasePlayer {
|
|
|
1486
1591
|
|
|
1487
1592
|
const percent = (currentTime / duration) * 100;
|
|
1488
1593
|
|
|
1489
|
-
// Update progress filled
|
|
1594
|
+
// Update progress filled (only if not dragging)
|
|
1490
1595
|
const progressFilled = document.getElementById('uvf-progress-filled') as HTMLElement;
|
|
1491
1596
|
if (progressFilled && !this.isDragging) {
|
|
1492
1597
|
progressFilled.style.width = percent + '%';
|
|
1493
1598
|
}
|
|
1494
1599
|
|
|
1495
|
-
// Update progress handle
|
|
1600
|
+
// Update progress handle (only if not dragging)
|
|
1496
1601
|
const progressHandle = document.getElementById('uvf-progress-handle') as HTMLElement;
|
|
1497
1602
|
if (progressHandle && !this.isDragging) {
|
|
1498
1603
|
progressHandle.style.left = percent + '%';
|
|
1604
|
+
// Remove dragging class if it was set
|
|
1605
|
+
progressHandle.classList.remove('dragging');
|
|
1499
1606
|
}
|
|
1500
1607
|
|
|
1501
1608
|
// Update buffered progress
|
|
@@ -1504,8 +1611,13 @@ export class WebPlayer extends BasePlayer {
|
|
|
1504
1611
|
progressBuffered.style.width = buffered + '%';
|
|
1505
1612
|
}
|
|
1506
1613
|
|
|
1507
|
-
// Update time display
|
|
1508
|
-
|
|
1614
|
+
// Update time display with YouTube-specific times
|
|
1615
|
+
const timeDisplay = document.getElementById('uvf-time-display');
|
|
1616
|
+
if (timeDisplay) {
|
|
1617
|
+
const currentTimeStr = this.formatTime(currentTime);
|
|
1618
|
+
const durationStr = this.formatTime(duration);
|
|
1619
|
+
timeDisplay.textContent = `${currentTimeStr} / ${durationStr}`;
|
|
1620
|
+
}
|
|
1509
1621
|
}
|
|
1510
1622
|
|
|
1511
1623
|
|
|
@@ -1950,12 +2062,27 @@ export class WebPlayer extends BasePlayer {
|
|
|
1950
2062
|
private updateTimeTooltip(e: MouseEvent): void {
|
|
1951
2063
|
const progressBar = document.getElementById('uvf-progress-bar');
|
|
1952
2064
|
const tooltip = document.getElementById('uvf-time-tooltip');
|
|
1953
|
-
if (!progressBar || !tooltip
|
|
2065
|
+
if (!progressBar || !tooltip) return;
|
|
2066
|
+
|
|
2067
|
+
// Get duration from appropriate source
|
|
2068
|
+
let duration = 0;
|
|
2069
|
+
if (this.youtubePlayer && this.youtubePlayerReady) {
|
|
2070
|
+
try {
|
|
2071
|
+
duration = this.youtubePlayer.getDuration() || 0;
|
|
2072
|
+
} catch (error) {
|
|
2073
|
+
this.debugWarn('Error getting YouTube duration for tooltip:', error);
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
} else if (this.video) {
|
|
2077
|
+
duration = this.video.duration || 0;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
if (!duration || !isFinite(duration)) return;
|
|
1954
2081
|
|
|
1955
2082
|
const rect = progressBar.getBoundingClientRect();
|
|
1956
2083
|
const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
|
|
1957
2084
|
const percent = (x / rect.width);
|
|
1958
|
-
const time = percent *
|
|
2085
|
+
const time = percent * duration;
|
|
1959
2086
|
|
|
1960
2087
|
// Update tooltip content and position
|
|
1961
2088
|
tooltip.textContent = this.formatTime(time);
|
|
@@ -6771,7 +6898,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
6771
6898
|
const qualityBadge = document.createElement('div');
|
|
6772
6899
|
qualityBadge.className = 'uvf-quality-badge';
|
|
6773
6900
|
qualityBadge.id = 'uvf-quality-badge';
|
|
6774
|
-
qualityBadge.textContent = '
|
|
6901
|
+
qualityBadge.textContent = 'AUTO'; // Default to AUTO for better UX
|
|
6775
6902
|
rightControls.appendChild(qualityBadge);
|
|
6776
6903
|
|
|
6777
6904
|
// Settings button with menu (show only if enabled)
|
|
@@ -8128,12 +8255,27 @@ export class WebPlayer extends BasePlayer {
|
|
|
8128
8255
|
const progressBar = document.querySelector('.uvf-progress-bar') as HTMLElement;
|
|
8129
8256
|
const progressFilled = document.getElementById('uvf-progress-filled') as HTMLElement;
|
|
8130
8257
|
const progressHandle = document.getElementById('uvf-progress-handle') as HTMLElement;
|
|
8131
|
-
if (!progressBar
|
|
8258
|
+
if (!progressBar) return;
|
|
8259
|
+
|
|
8260
|
+
// Get duration from appropriate source
|
|
8261
|
+
let duration = 0;
|
|
8262
|
+
if (this.youtubePlayer && this.youtubePlayerReady) {
|
|
8263
|
+
try {
|
|
8264
|
+
duration = this.youtubePlayer.getDuration() || 0;
|
|
8265
|
+
} catch (error) {
|
|
8266
|
+
this.debugWarn('Error getting YouTube duration for seeking:', error);
|
|
8267
|
+
return;
|
|
8268
|
+
}
|
|
8269
|
+
} else if (this.video) {
|
|
8270
|
+
duration = this.video.duration;
|
|
8271
|
+
} else {
|
|
8272
|
+
this.debugWarn('No video source available for seeking');
|
|
8273
|
+
return;
|
|
8274
|
+
}
|
|
8132
8275
|
|
|
8133
|
-
const duration = this.video.duration;
|
|
8134
8276
|
// Validate duration before calculating seek time
|
|
8135
8277
|
if (!isFinite(duration) || isNaN(duration) || duration <= 0) {
|
|
8136
|
-
this.debugWarn('Invalid video duration, cannot seek via progress bar');
|
|
8278
|
+
this.debugWarn('Invalid video duration, cannot seek via progress bar:', duration);
|
|
8137
8279
|
return;
|
|
8138
8280
|
}
|
|
8139
8281
|
|
|
@@ -8148,21 +8290,24 @@ export class WebPlayer extends BasePlayer {
|
|
|
8148
8290
|
return;
|
|
8149
8291
|
}
|
|
8150
8292
|
|
|
8293
|
+
this.debugLog('Seeking to position:', time, 'seconds (', Math.round(percent), '%)');
|
|
8294
|
+
|
|
8151
8295
|
// Update UI immediately for responsive feedback
|
|
8152
|
-
if (progressFilled) {
|
|
8296
|
+
if (progressFilled && !this.isDragging) {
|
|
8153
8297
|
progressFilled.style.width = percent + '%';
|
|
8154
8298
|
}
|
|
8155
|
-
if (progressHandle) {
|
|
8299
|
+
if (progressHandle && !this.isDragging) {
|
|
8156
8300
|
progressHandle.style.left = percent + '%';
|
|
8157
|
-
|
|
8158
|
-
if (this.isDragging) {
|
|
8159
|
-
progressHandle.classList.add('dragging');
|
|
8160
|
-
} else {
|
|
8161
|
-
progressHandle.classList.remove('dragging');
|
|
8162
|
-
}
|
|
8301
|
+
progressHandle.classList.add('dragging');
|
|
8163
8302
|
}
|
|
8164
8303
|
|
|
8304
|
+
// Perform the actual seek
|
|
8165
8305
|
this.seek(time);
|
|
8306
|
+
|
|
8307
|
+
// For YouTube, provide immediate visual feedback since API might be delayed
|
|
8308
|
+
if (this.youtubePlayer && this.youtubePlayerReady) {
|
|
8309
|
+
this.emit('onSeeking');
|
|
8310
|
+
}
|
|
8166
8311
|
}
|
|
8167
8312
|
|
|
8168
8313
|
private formatTime(seconds: number): string {
|
|
@@ -9196,6 +9341,13 @@ export class WebPlayer extends BasePlayer {
|
|
|
9196
9341
|
option.classList.add('active');
|
|
9197
9342
|
}
|
|
9198
9343
|
});
|
|
9344
|
+
|
|
9345
|
+
// Update quality badge
|
|
9346
|
+
const qualityBadge = document.getElementById('uvf-quality-badge');
|
|
9347
|
+
if (qualityBadge) {
|
|
9348
|
+
const qualityOption = this.youtubeAvailableQualities.find(q => q.value === quality);
|
|
9349
|
+
qualityBadge.textContent = qualityOption ? qualityOption.label : 'AUTO';
|
|
9350
|
+
}
|
|
9199
9351
|
return;
|
|
9200
9352
|
}
|
|
9201
9353
|
|
|
@@ -9592,9 +9744,9 @@ export class WebPlayer extends BasePlayer {
|
|
|
9592
9744
|
* Detect available video qualities from different sources
|
|
9593
9745
|
*/
|
|
9594
9746
|
private detectAvailableQualities(): void {
|
|
9595
|
-
//
|
|
9747
|
+
// Check if we're using YouTube and have detected qualities
|
|
9596
9748
|
const isYouTube = this.youtubePlayer && this.youtubePlayerReady && this.youtubeAvailableQualities.length > 0;
|
|
9597
|
-
this.availableQualities =
|
|
9749
|
+
this.availableQualities = [];
|
|
9598
9750
|
let detectedQualities: Array<{ value: string; label: string; height?: number }> = [];
|
|
9599
9751
|
|
|
9600
9752
|
// YouTube qualities
|
|
@@ -9605,6 +9757,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
9605
9757
|
height: q.height
|
|
9606
9758
|
}));
|
|
9607
9759
|
this.debugLog('Using YouTube qualities:', detectedQualities);
|
|
9760
|
+
this.availableQualities = detectedQualities;
|
|
9761
|
+
return; // Early return for YouTube
|
|
9608
9762
|
} else if (this.hls && this.hls.levels) {
|
|
9609
9763
|
// HLS qualities
|
|
9610
9764
|
this.hls.levels.forEach((level: any, index: number) => {
|