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) return;
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 available quality levels:', availableQualityLevels);
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 as the first item (exactly once)
1392
- this.youtubeAvailableQualities.unshift({ label: 'Auto', value: 'auto', height: 0 });
1476
+ // Always add auto quality option first
1477
+ this.youtubeAvailableQualities.push({ label: 'Auto', value: 'auto', height: 0 });
1393
1478
 
1394
- this.debugLog('Mapped YouTube qualities:', this.youtubeAvailableQualities);
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 settings menu to show YouTube qualities
1407
- this.updateSettingsMenu();
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 || !this.video) return;
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 * this.video.duration;
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 = 'HD';
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">