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
- this.updateTimeDisplay();
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 || !this.video) return;
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 * this.video.duration;
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 = 'HD';
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 || !this.video) return;
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
- // Add dragging class for visual feedback
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
- // Don't add default 'Auto' for YouTube - it's already included in youtubeAvailableQualities
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 = isYouTube ? [] : [{ value: 'auto', label: 'Auto' }];
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) => {