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
|
|
9079
|
-
this.
|
|
9080
|
-
|
|
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 ===
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|