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
- if (!this.video) return;
9044
- this.video.playbackRate = speed;
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) {