unified-video-framework 1.4.247 → 1.4.249

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.
@@ -1129,10 +1129,6 @@ export class WebPlayer extends BasePlayer {
1129
1129
  private youtubePlayer: any = null;
1130
1130
  private youtubePlayerReady: boolean = false;
1131
1131
  private youtubeIframe: HTMLIFrameElement | null = null;
1132
- private isYoutubePiPActive: boolean = false;
1133
- private youtubePiPContainer: HTMLDivElement | null = null;
1134
- private youtubePiPOriginalParent: HTMLElement | null = null;
1135
- private youtubePiPOriginalPosition: string = '';
1136
1132
 
1137
1133
  private async createYouTubePlayer(videoId: string): Promise<void> {
1138
1134
  const container = this.playerWrapper || this.video?.parentElement;
@@ -1246,6 +1242,9 @@ export class WebPlayer extends BasePlayer {
1246
1242
  if (this.config.muted) {
1247
1243
  this.youtubePlayer.mute();
1248
1244
  }
1245
+
1246
+ // Extract available YouTube qualities
1247
+ this.extractYouTubeAvailableQualities();
1249
1248
  }
1250
1249
 
1251
1250
  // Start time tracking
@@ -1341,6 +1340,114 @@ export class WebPlayer extends BasePlayer {
1341
1340
  }
1342
1341
 
1343
1342
  private youtubeTimeTrackingInterval: NodeJS.Timeout | null = null;
1343
+ private youtubeAvailableQualities: any[] = [];
1344
+ private youtubeCurrentQuality: any = null;
1345
+
1346
+ /**
1347
+ * Extract available YouTube video qualities
1348
+ */
1349
+ private extractYouTubeAvailableQualities(): void {
1350
+ if (!this.youtubePlayer) return;
1351
+
1352
+ try {
1353
+ const availableQualityLevels = this.youtubePlayer.getAvailableQualityLevels();
1354
+ this.debugLog('YouTube available quality levels:', availableQualityLevels);
1355
+
1356
+ // Map YouTube quality levels to standard labels
1357
+ const qualityMap: Record<string, any> = {
1358
+ 'hd1080': { label: '1080p', value: 'hd1080', height: 1080 },
1359
+ 'hd720': { label: '720p', value: 'hd720', height: 720 },
1360
+ 'large': { label: '480p', value: 'large', height: 480 },
1361
+ 'medium': { label: '360p', value: 'medium', height: 360 },
1362
+ 'small': { label: '240p', value: 'small', height: 240 },
1363
+ 'tiny': { label: '144p', value: 'tiny', height: 144 },
1364
+ 'auto': { label: 'Auto', value: 'auto', height: 0 }
1365
+ };
1366
+
1367
+ this.youtubeAvailableQualities = [];
1368
+ availableQualityLevels.forEach((qualityLevel: string) => {
1369
+ if (qualityMap[qualityLevel]) {
1370
+ this.youtubeAvailableQualities.push(qualityMap[qualityLevel]);
1371
+ }
1372
+ });
1373
+
1374
+ // Add auto quality option if not present
1375
+ if (!this.youtubeAvailableQualities.find(q => q.value === 'auto')) {
1376
+ this.youtubeAvailableQualities.unshift({ label: 'Auto', value: 'auto', height: 0 });
1377
+ }
1378
+
1379
+ this.debugLog('Mapped YouTube qualities:', this.youtubeAvailableQualities);
1380
+
1381
+ // Get current quality
1382
+ try {
1383
+ const currentQuality = this.youtubePlayer.getPlaybackQuality();
1384
+ this.youtubeCurrentQuality = qualityMap[currentQuality] || qualityMap['auto'];
1385
+ this.debugLog('Current YouTube quality:', this.youtubeCurrentQuality);
1386
+ } catch (e) {
1387
+ this.debugWarn('Could not get current YouTube quality:', e);
1388
+ this.youtubeCurrentQuality = qualityMap['auto'];
1389
+ }
1390
+
1391
+ // Update settings menu to show YouTube qualities
1392
+ this.updateSettingsMenu();
1393
+
1394
+ } catch (error) {
1395
+ this.debugWarn('Could not extract YouTube qualities:', error);
1396
+ }
1397
+ }
1398
+
1399
+ /**
1400
+ * Set YouTube video quality
1401
+ */
1402
+ private setYouTubeQuality(qualityLevel: string): void {
1403
+ if (!this.youtubePlayer) return;
1404
+
1405
+ try {
1406
+ const qualityMap: Record<string, any> = {
1407
+ 'hd1080': { label: '1080p', value: 'hd1080', height: 1080 },
1408
+ 'hd720': { label: '720p', value: 'hd720', height: 720 },
1409
+ 'large': { label: '480p', value: 'large', height: 480 },
1410
+ 'medium': { label: '360p', value: 'medium', height: 360 },
1411
+ 'small': { label: '240p', value: 'small', height: 240 },
1412
+ 'tiny': { label: '144p', value: 'tiny', height: 144 },
1413
+ 'auto': { label: 'Auto', value: 'auto', height: 0 }
1414
+ };
1415
+
1416
+ this.debugLog('📊 Attempting to set YouTube quality to:', qualityLevel);
1417
+
1418
+ // Try to set the quality
1419
+ this.youtubePlayer.setPlaybackQuality(qualityLevel);
1420
+ this.debugLog('✅ setPlaybackQuality called for:', qualityLevel);
1421
+
1422
+ // Verify quality was changed after a delay
1423
+ setTimeout(() => {
1424
+ try {
1425
+ const currentQuality = this.youtubePlayer.getPlaybackQuality();
1426
+ this.youtubeCurrentQuality = qualityMap[currentQuality] || qualityMap['auto'];
1427
+
1428
+ this.debugLog('📊 Current quality after set:', currentQuality, '(', this.youtubeCurrentQuality?.label, ')');
1429
+
1430
+ // Show notification with result
1431
+ if (currentQuality === qualityLevel) {
1432
+ this.showNotification(`Quality changed to ${qualityMap[currentQuality]?.label || 'requested quality'}`);
1433
+ this.debugLog('✅ Quality successfully changed to:', qualityLevel);
1434
+ } else {
1435
+ this.showNotification(`Quality set to ${qualityMap[currentQuality]?.label || currentQuality} (YouTube optimized)`);
1436
+ this.debugLog('⚠️ Requested quality:', qualityLevel, '| Actual quality:', currentQuality, '(YouTube may have optimized)');
1437
+ }
1438
+
1439
+ // Update settings menu UI
1440
+ this.updateSettingsMenu();
1441
+ } catch (verifyError) {
1442
+ this.debugWarn('Could not verify YouTube quality:', verifyError);
1443
+ }
1444
+ }, 1000);
1445
+
1446
+ } catch (error) {
1447
+ this.debugWarn('❌ Could not set YouTube quality:', error);
1448
+ this.showNotification('Quality control limited on YouTube');
1449
+ }
1450
+ }
1344
1451
 
1345
1452
  private startYouTubeTimeTracking(): void {
1346
1453
  if (this.youtubeTimeTrackingInterval) {
@@ -9075,12 +9182,13 @@ export class WebPlayer extends BasePlayer {
9075
9182
  private setQualityByLabel(quality: string): void {
9076
9183
  // Handle YouTube player
9077
9184
  if (this.youtubePlayer && this.youtubePlayerReady) {
9078
- // YouTube quality is limited - show notification
9079
- this.showShortcutIndicator('Quality control not available for YouTube');
9080
- // Update UI to reflect current quality
9185
+ // Set YouTube quality directly
9186
+ this.setYouTubeQuality(quality);
9187
+
9188
+ // Update UI to reflect selection
9081
9189
  document.querySelectorAll('.quality-option').forEach(option => {
9082
9190
  option.classList.remove('active');
9083
- if ((option as HTMLElement).dataset.quality === 'auto') {
9191
+ if ((option as HTMLElement).dataset.quality === quality) {
9084
9192
  option.classList.add('active');
9085
9193
  }
9086
9194
  });
@@ -9120,17 +9228,6 @@ export class WebPlayer extends BasePlayer {
9120
9228
 
9121
9229
  private async togglePiP(): Promise<void> {
9122
9230
  try {
9123
- // Handle YouTube player with custom PiP
9124
- if (this.youtubePlayer && this.youtubePlayerReady) {
9125
- if (this.isYoutubePiPActive) {
9126
- await this.exitYoutubePiP();
9127
- } else {
9128
- await this.enterYoutubePiP();
9129
- }
9130
- return;
9131
- }
9132
-
9133
- // Native PiP for regular video
9134
9231
  if ((document as any).pictureInPictureElement) {
9135
9232
  await this.exitPictureInPicture();
9136
9233
  } else {
@@ -9141,137 +9238,6 @@ export class WebPlayer extends BasePlayer {
9141
9238
  }
9142
9239
  }
9143
9240
 
9144
- private async enterYoutubePiP(): Promise<void> {
9145
- try {
9146
- const playerWrapper = this.playerWrapper || this.container?.querySelector('.uvf-player-wrapper');
9147
- if (!playerWrapper) {
9148
- this.showNotification('Player container not found');
9149
- return;
9150
- }
9151
-
9152
- // Store original position
9153
- this.youtubePiPOriginalParent = playerWrapper.parentElement;
9154
- this.youtubePiPOriginalPosition = playerWrapper.getAttribute('style') || '';
9155
-
9156
- // Create PiP container
9157
- this.youtubePiPContainer = document.createElement('div');
9158
- this.youtubePiPContainer.id = 'youtube-pip-container';
9159
- this.youtubePiPContainer.style.cssText = `
9160
- position: fixed;
9161
- bottom: 20px;
9162
- right: 20px;
9163
- width: 400px;
9164
- height: 225px;
9165
- z-index: 9999;
9166
- background: #000;
9167
- border: 2px solid rgba(255, 255, 255, 0.3);
9168
- border-radius: 8px;
9169
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
9170
- cursor: grab;
9171
- overflow: hidden;
9172
- `;
9173
-
9174
- // Add drag functionality
9175
- let isDragging = false;
9176
- let offset = { x: 0, y: 0 };
9177
-
9178
- this.youtubePiPContainer.addEventListener('mousedown', (e) => {
9179
- if ((e.target as HTMLElement).closest('.youtube-pip-controls')) return;
9180
- isDragging = true;
9181
- offset.x = e.clientX - this.youtubePiPContainer!.getBoundingClientRect().left;
9182
- offset.y = e.clientY - this.youtubePiPContainer!.getBoundingClientRect().top;
9183
- this.youtubePiPContainer!.style.cursor = 'grabbing';
9184
- });
9185
-
9186
- document.addEventListener('mousemove', (e) => {
9187
- if (!isDragging || !this.youtubePiPContainer) return;
9188
- this.youtubePiPContainer.style.left = (e.clientX - offset.x) + 'px';
9189
- this.youtubePiPContainer.style.top = (e.clientY - offset.y) + 'px';
9190
- });
9191
-
9192
- document.addEventListener('mouseup', () => {
9193
- isDragging = false;
9194
- if (this.youtubePiPContainer) this.youtubePiPContainer.style.cursor = 'grab';
9195
- });
9196
-
9197
- // Add close button
9198
- const closeBtn = document.createElement('button');
9199
- closeBtn.className = 'youtube-pip-controls';
9200
- closeBtn.innerHTML = '✕';
9201
- closeBtn.style.cssText = `
9202
- position: absolute;
9203
- top: 8px;
9204
- right: 8px;
9205
- width: 32px;
9206
- height: 32px;
9207
- background: rgba(255, 255, 255, 0.2);
9208
- border: none;
9209
- color: white;
9210
- font-size: 18px;
9211
- border-radius: 4px;
9212
- cursor: pointer;
9213
- z-index: 10000;
9214
- transition: background 0.2s;
9215
- `;
9216
-
9217
- closeBtn.addEventListener('hover', () => {
9218
- closeBtn.style.background = 'rgba(255, 255, 255, 0.4)';
9219
- });
9220
-
9221
- closeBtn.addEventListener('click', async () => {
9222
- await this.exitYoutubePiP();
9223
- });
9224
-
9225
- // Move player to PiP container
9226
- this.youtubePiPContainer.appendChild(playerWrapper as HTMLElement);
9227
- this.youtubePiPContainer.appendChild(closeBtn);
9228
- document.body.appendChild(this.youtubePiPContainer);
9229
-
9230
- // Style player for PiP
9231
- (playerWrapper as HTMLElement).style.width = '100%';
9232
- (playerWrapper as HTMLElement).style.height = '100%';
9233
-
9234
- this.isYoutubePiPActive = true;
9235
- this.showNotification('PiP mode active (drag to move)');
9236
- this.debugLog('YouTube PiP mode enabled');
9237
- } catch (error) {
9238
- this.debugError('Failed to enter YouTube PiP:', error);
9239
- this.showNotification('PiP mode failed');
9240
- }
9241
- }
9242
-
9243
- private async exitYoutubePiP(): Promise<void> {
9244
- try {
9245
- if (!this.youtubePiPContainer || !this.youtubePiPOriginalParent) {
9246
- return;
9247
- }
9248
-
9249
- const playerWrapper = this.playerWrapper || this.container?.querySelector('.uvf-player-wrapper');
9250
- if (playerWrapper) {
9251
- // Restore original styling
9252
- if (this.youtubePiPOriginalPosition) {
9253
- playerWrapper.setAttribute('style', this.youtubePiPOriginalPosition);
9254
- } else {
9255
- playerWrapper.removeAttribute('style');
9256
- }
9257
-
9258
- // Move back to original parent
9259
- this.youtubePiPOriginalParent.appendChild(playerWrapper);
9260
- }
9261
-
9262
- // Remove PiP container
9263
- this.youtubePiPContainer.remove();
9264
- this.youtubePiPContainer = null;
9265
- this.youtubePiPOriginalParent = null;
9266
-
9267
- this.isYoutubePiPActive = false;
9268
- this.showNotification('PiP mode closed');
9269
- this.debugLog('YouTube PiP mode disabled');
9270
- } catch (error) {
9271
- this.debugError('Failed to exit YouTube PiP:', error);
9272
- }
9273
- }
9274
-
9275
9241
  private setupCastContextSafe(): void {
9276
9242
  try {
9277
9243
  const castNs = (window as any).cast;
@@ -9526,7 +9492,11 @@ export class WebPlayer extends BasePlayer {
9526
9492
 
9527
9493
  // Quality Accordion Section (only if enabled in config and qualities detected)
9528
9494
  if (this.settingsConfig.quality && this.availableQualities.length > 0) {
9529
- const currentQuality = this.availableQualities.find(q => q.value === this.currentQuality);
9495
+ // For YouTube, use youtubeCurrentQuality; otherwise use currentQuality
9496
+ const qualityForDisplay = this.youtubePlayer && this.youtubePlayerReady && this.youtubeCurrentQuality
9497
+ ? this.youtubeCurrentQuality.value
9498
+ : this.currentQuality;
9499
+ const currentQuality = this.availableQualities.find(q => q.value === qualityForDisplay);
9530
9500
  const currentQualityLabel = currentQuality ? currentQuality.label : 'Auto';
9531
9501
 
9532
9502
  menuHTML += `
@@ -9546,7 +9516,11 @@ export class WebPlayer extends BasePlayer {
9546
9516
  <div class="uvf-accordion-content" data-section="quality">`;
9547
9517
 
9548
9518
  this.availableQualities.forEach(quality => {
9549
- const isActive = quality.value === this.currentQuality ? 'active' : '';
9519
+ // For YouTube, compare against youtubeCurrentQuality
9520
+ const qualityValue = this.youtubePlayer && this.youtubePlayerReady && this.youtubeCurrentQuality
9521
+ ? this.youtubeCurrentQuality.value
9522
+ : this.currentQuality;
9523
+ const isActive = quality.value === qualityValue ? 'active' : '';
9550
9524
  const isPremium = this.isQualityPremium(quality);
9551
9525
  const isLocked = isPremium && !this.isPremiumUser();
9552
9526
  const qualityHeight = (quality as any).height || 0;
@@ -9617,7 +9591,15 @@ export class WebPlayer extends BasePlayer {
9617
9591
  this.availableQualities = [{ value: 'auto', label: 'Auto' }];
9618
9592
  let detectedQualities: Array<{ value: string; label: string; height?: number }> = [];
9619
9593
 
9620
- if (this.hls && this.hls.levels) {
9594
+ // YouTube qualities
9595
+ if (this.youtubePlayer && this.youtubePlayerReady && this.youtubeAvailableQualities.length > 0) {
9596
+ detectedQualities = this.youtubeAvailableQualities.map(q => ({
9597
+ value: q.value,
9598
+ label: q.label,
9599
+ height: q.height
9600
+ }));
9601
+ this.debugLog('Using YouTube qualities:', detectedQualities);
9602
+ } else if (this.hls && this.hls.levels) {
9621
9603
  // HLS qualities
9622
9604
  this.hls.levels.forEach((level: any, index: number) => {
9623
9605
  if (level.height) {
@@ -9989,6 +9971,13 @@ export class WebPlayer extends BasePlayer {
9989
9971
  private setQualityFromSettings(quality: string): void {
9990
9972
  this.currentQuality = quality;
9991
9973
 
9974
+ // Handle YouTube quality
9975
+ if (this.youtubePlayer && this.youtubePlayerReady) {
9976
+ this.setYouTubeQuality(quality);
9977
+ this.debugLog(`YouTube quality set to ${quality}`);
9978
+ return;
9979
+ }
9980
+
9992
9981
  if (quality === 'auto') {
9993
9982
  // Enable auto quality with filter consideration
9994
9983
  if (this.hls) {