unified-video-framework 1.4.246 → 1.4.247
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,6 +1129,10 @@ 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 = '';
|
|
1132
1136
|
|
|
1133
1137
|
private async createYouTubePlayer(videoId: string): Promise<void> {
|
|
1134
1138
|
const container = this.playerWrapper || this.video?.parentElement;
|
|
@@ -6239,10 +6243,18 @@ export class WebPlayer extends BasePlayer {
|
|
|
6239
6243
|
.ytp-endscreen-element,
|
|
6240
6244
|
.ytp-ce-element,
|
|
6241
6245
|
.ytp-suggested-action,
|
|
6242
|
-
.ytp-pause-overlay
|
|
6246
|
+
.ytp-pause-overlay,
|
|
6247
|
+
.ytp-endscreen,
|
|
6248
|
+
.ytp-videowall-still,
|
|
6249
|
+
.ytp-ce-covering-overlay,
|
|
6250
|
+
.ytp-ce-element-show,
|
|
6251
|
+
.ytp-cards-button,
|
|
6252
|
+
.ytp-cards-button-icon,
|
|
6253
|
+
.ytp-impression-link {
|
|
6243
6254
|
display: none !important;
|
|
6244
6255
|
visibility: hidden !important;
|
|
6245
6256
|
opacity: 0 !important;
|
|
6257
|
+
pointer-events: none !important;
|
|
6246
6258
|
}
|
|
6247
6259
|
|
|
6248
6260
|
/* Ultra-wide screens */
|
|
@@ -9040,8 +9052,16 @@ export class WebPlayer extends BasePlayer {
|
|
|
9040
9052
|
}
|
|
9041
9053
|
|
|
9042
9054
|
private setSpeed(speed: number): void {
|
|
9043
|
-
|
|
9044
|
-
this.
|
|
9055
|
+
// Handle YouTube player
|
|
9056
|
+
if (this.youtubePlayer && this.youtubePlayerReady) {
|
|
9057
|
+
try {
|
|
9058
|
+
this.youtubePlayer.setPlaybackRate(speed);
|
|
9059
|
+
} catch (error) {
|
|
9060
|
+
this.debugWarn('YouTube playback rate not supported:', error);
|
|
9061
|
+
}
|
|
9062
|
+
} else if (this.video) {
|
|
9063
|
+
this.video.playbackRate = speed;
|
|
9064
|
+
}
|
|
9045
9065
|
|
|
9046
9066
|
// Update UI
|
|
9047
9067
|
document.querySelectorAll('.speed-option').forEach(option => {
|
|
@@ -9053,6 +9073,20 @@ export class WebPlayer extends BasePlayer {
|
|
|
9053
9073
|
}
|
|
9054
9074
|
|
|
9055
9075
|
private setQualityByLabel(quality: string): void {
|
|
9076
|
+
// Handle YouTube player
|
|
9077
|
+
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
|
|
9081
|
+
document.querySelectorAll('.quality-option').forEach(option => {
|
|
9082
|
+
option.classList.remove('active');
|
|
9083
|
+
if ((option as HTMLElement).dataset.quality === 'auto') {
|
|
9084
|
+
option.classList.add('active');
|
|
9085
|
+
}
|
|
9086
|
+
});
|
|
9087
|
+
return;
|
|
9088
|
+
}
|
|
9089
|
+
|
|
9056
9090
|
const qualityBadge = document.getElementById('uvf-quality-badge');
|
|
9057
9091
|
|
|
9058
9092
|
// Update UI
|
|
@@ -9086,6 +9120,17 @@ export class WebPlayer extends BasePlayer {
|
|
|
9086
9120
|
|
|
9087
9121
|
private async togglePiP(): Promise<void> {
|
|
9088
9122
|
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
|
|
9089
9134
|
if ((document as any).pictureInPictureElement) {
|
|
9090
9135
|
await this.exitPictureInPicture();
|
|
9091
9136
|
} else {
|
|
@@ -9096,6 +9141,137 @@ export class WebPlayer extends BasePlayer {
|
|
|
9096
9141
|
}
|
|
9097
9142
|
}
|
|
9098
9143
|
|
|
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
|
+
|
|
9099
9275
|
private setupCastContextSafe(): void {
|
|
9100
9276
|
try {
|
|
9101
9277
|
const castNs = (window as any).cast;
|
|
@@ -9621,6 +9797,17 @@ export class WebPlayer extends BasePlayer {
|
|
|
9621
9797
|
private detectAvailableSubtitles(): void {
|
|
9622
9798
|
this.availableSubtitles = [{ value: 'off', label: 'Off' }];
|
|
9623
9799
|
|
|
9800
|
+
// YouTube player - subtitles available but not fully controllable
|
|
9801
|
+
if (this.youtubePlayer && this.youtubePlayerReady) {
|
|
9802
|
+
// YouTube supports captions but control is limited
|
|
9803
|
+
// We can only toggle on/off, not select language
|
|
9804
|
+
this.availableSubtitles.push({
|
|
9805
|
+
value: 'youtube-cc',
|
|
9806
|
+
label: 'YouTube Captions'
|
|
9807
|
+
});
|
|
9808
|
+
return;
|
|
9809
|
+
}
|
|
9810
|
+
|
|
9624
9811
|
if (this.video?.textTracks) {
|
|
9625
9812
|
// HTML5 text tracks
|
|
9626
9813
|
Array.from(this.video.textTracks).forEach((track, index) => {
|
|
@@ -9978,6 +10165,25 @@ export class WebPlayer extends BasePlayer {
|
|
|
9978
10165
|
private setSubtitle(subtitle: string): void {
|
|
9979
10166
|
this.currentSubtitle = subtitle;
|
|
9980
10167
|
|
|
10168
|
+
if (this.youtubePlayer && this.youtubePlayerReady) {
|
|
10169
|
+
// YouTube subtitles - limited control
|
|
10170
|
+
if (subtitle === 'off') {
|
|
10171
|
+
try {
|
|
10172
|
+
this.youtubePlayer.setOption('captions', 'fontSize', -1); // Hide captions
|
|
10173
|
+
} catch (e) {
|
|
10174
|
+
this.debugWarn('YouTube caption control limited:', e);
|
|
10175
|
+
}
|
|
10176
|
+
} else if (subtitle === 'youtube-cc') {
|
|
10177
|
+
try {
|
|
10178
|
+
this.youtubePlayer.setOption('captions', 'fontSize', 0); // Show captions
|
|
10179
|
+
} catch (e) {
|
|
10180
|
+
this.debugWarn('YouTube caption control limited:', e);
|
|
10181
|
+
}
|
|
10182
|
+
}
|
|
10183
|
+
this.showShortcutIndicator('Subtitle language selection not available for YouTube');
|
|
10184
|
+
return;
|
|
10185
|
+
}
|
|
10186
|
+
|
|
9981
10187
|
if (subtitle === 'off') {
|
|
9982
10188
|
// Disable all subtitles
|
|
9983
10189
|
if (this.video?.textTracks) {
|