unified-video-framework 1.4.245 → 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 */
|
|
@@ -6900,15 +6912,21 @@ export class WebPlayer extends BasePlayer {
|
|
|
6900
6912
|
}
|
|
6901
6913
|
});
|
|
6902
6914
|
|
|
6903
|
-
// Skip buttons with null safety
|
|
6915
|
+
// Skip buttons with null safety - works with both video and YouTube
|
|
6904
6916
|
skipBackBtn?.addEventListener('click', () => {
|
|
6905
|
-
|
|
6906
|
-
|
|
6917
|
+
const currentTime = this.getCurrentTime();
|
|
6918
|
+
const duration = this.getDuration();
|
|
6919
|
+
if (!isNaN(duration) && duration > 0) {
|
|
6920
|
+
this.seek(Math.max(0, currentTime - 10));
|
|
6921
|
+
this.showShortcutIndicator('-10s');
|
|
6907
6922
|
}
|
|
6908
6923
|
});
|
|
6909
6924
|
skipForwardBtn?.addEventListener('click', () => {
|
|
6910
|
-
|
|
6911
|
-
|
|
6925
|
+
const currentTime = this.getCurrentTime();
|
|
6926
|
+
const duration = this.getDuration();
|
|
6927
|
+
if (!isNaN(duration) && duration > 0) {
|
|
6928
|
+
this.seek(Math.min(duration, currentTime + 10));
|
|
6929
|
+
this.showShortcutIndicator('+10s');
|
|
6912
6930
|
}
|
|
6913
6931
|
});
|
|
6914
6932
|
|
|
@@ -7726,12 +7744,29 @@ export class WebPlayer extends BasePlayer {
|
|
|
7726
7744
|
private togglePlayPause(): void {
|
|
7727
7745
|
this.debugLog('togglePlayPause called, video state:', {
|
|
7728
7746
|
videoExists: !!this.video,
|
|
7729
|
-
|
|
7747
|
+
youtubePlayerExists: !!this.youtubePlayer,
|
|
7748
|
+
youtubePlayerReady: this.youtubePlayerReady,
|
|
7730
7749
|
playerState: this.state
|
|
7731
7750
|
});
|
|
7732
7751
|
|
|
7752
|
+
// Handle YouTube player
|
|
7753
|
+
if (this.youtubePlayer && this.youtubePlayerReady) {
|
|
7754
|
+
const playerState = this.youtubePlayer.getPlayerState();
|
|
7755
|
+
this.debugLog('YouTube player state:', playerState);
|
|
7756
|
+
|
|
7757
|
+
if (playerState === window.YT.PlayerState.PLAYING) {
|
|
7758
|
+
this.debugLog('YouTube video is playing, calling pause()');
|
|
7759
|
+
this.pause();
|
|
7760
|
+
} else {
|
|
7761
|
+
this.debugLog('YouTube video is paused, calling play()');
|
|
7762
|
+
this.play();
|
|
7763
|
+
}
|
|
7764
|
+
return;
|
|
7765
|
+
}
|
|
7766
|
+
|
|
7767
|
+
// Handle regular video element
|
|
7733
7768
|
if (!this.video) {
|
|
7734
|
-
this.debugError('No video element available for toggle');
|
|
7769
|
+
this.debugError('No video element or YouTube player available for toggle');
|
|
7735
7770
|
return;
|
|
7736
7771
|
}
|
|
7737
7772
|
|
|
@@ -8035,12 +8070,28 @@ export class WebPlayer extends BasePlayer {
|
|
|
8035
8070
|
|
|
8036
8071
|
private updateTimeDisplay(): void {
|
|
8037
8072
|
const timeDisplay = document.getElementById('uvf-time-display');
|
|
8038
|
-
if (timeDisplay
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8073
|
+
if (!timeDisplay) return;
|
|
8074
|
+
|
|
8075
|
+
let current = 0;
|
|
8076
|
+
let duration = 0;
|
|
8077
|
+
|
|
8078
|
+
// Get time from YouTube player if available
|
|
8079
|
+
if (this.youtubePlayer && this.youtubePlayerReady) {
|
|
8080
|
+
try {
|
|
8081
|
+
current = this.youtubePlayer.getCurrentTime() || 0;
|
|
8082
|
+
duration = this.youtubePlayer.getDuration() || 0;
|
|
8083
|
+
} catch (error) {
|
|
8084
|
+
this.debugWarn('Error getting YouTube player time:', error);
|
|
8085
|
+
}
|
|
8086
|
+
} else if (this.video) {
|
|
8087
|
+
current = this.video.currentTime || 0;
|
|
8088
|
+
duration = this.video.duration || 0;
|
|
8043
8089
|
}
|
|
8090
|
+
|
|
8091
|
+
const currentFormatted = this.formatTime(current);
|
|
8092
|
+
const durationFormatted = this.formatTime(duration);
|
|
8093
|
+
timeDisplay.textContent = `${currentFormatted} / ${durationFormatted}`;
|
|
8094
|
+
this.debugLog('Time display updated:', `${currentFormatted} / ${durationFormatted}`);
|
|
8044
8095
|
}
|
|
8045
8096
|
|
|
8046
8097
|
private showControls(): void {
|
|
@@ -9001,8 +9052,16 @@ export class WebPlayer extends BasePlayer {
|
|
|
9001
9052
|
}
|
|
9002
9053
|
|
|
9003
9054
|
private setSpeed(speed: number): void {
|
|
9004
|
-
|
|
9005
|
-
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
|
+
}
|
|
9006
9065
|
|
|
9007
9066
|
// Update UI
|
|
9008
9067
|
document.querySelectorAll('.speed-option').forEach(option => {
|
|
@@ -9014,6 +9073,20 @@ export class WebPlayer extends BasePlayer {
|
|
|
9014
9073
|
}
|
|
9015
9074
|
|
|
9016
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
|
+
|
|
9017
9090
|
const qualityBadge = document.getElementById('uvf-quality-badge');
|
|
9018
9091
|
|
|
9019
9092
|
// Update UI
|
|
@@ -9047,6 +9120,17 @@ export class WebPlayer extends BasePlayer {
|
|
|
9047
9120
|
|
|
9048
9121
|
private async togglePiP(): Promise<void> {
|
|
9049
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
|
|
9050
9134
|
if ((document as any).pictureInPictureElement) {
|
|
9051
9135
|
await this.exitPictureInPicture();
|
|
9052
9136
|
} else {
|
|
@@ -9057,6 +9141,137 @@ export class WebPlayer extends BasePlayer {
|
|
|
9057
9141
|
}
|
|
9058
9142
|
}
|
|
9059
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
|
+
|
|
9060
9275
|
private setupCastContextSafe(): void {
|
|
9061
9276
|
try {
|
|
9062
9277
|
const castNs = (window as any).cast;
|
|
@@ -9582,6 +9797,17 @@ export class WebPlayer extends BasePlayer {
|
|
|
9582
9797
|
private detectAvailableSubtitles(): void {
|
|
9583
9798
|
this.availableSubtitles = [{ value: 'off', label: 'Off' }];
|
|
9584
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
|
+
|
|
9585
9811
|
if (this.video?.textTracks) {
|
|
9586
9812
|
// HTML5 text tracks
|
|
9587
9813
|
Array.from(this.video.textTracks).forEach((track, index) => {
|
|
@@ -9939,6 +10165,25 @@ export class WebPlayer extends BasePlayer {
|
|
|
9939
10165
|
private setSubtitle(subtitle: string): void {
|
|
9940
10166
|
this.currentSubtitle = subtitle;
|
|
9941
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
|
+
|
|
9942
10187
|
if (subtitle === 'off') {
|
|
9943
10188
|
// Disable all subtitles
|
|
9944
10189
|
if (this.video?.textTracks) {
|