unified-video-framework 1.4.255 → 1.4.257

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.
@@ -94,8 +94,8 @@ export class WebPlayer extends BasePlayer {
94
94
  this.youtubePlayerReady = false;
95
95
  this.youtubeIframe = null;
96
96
  this.youtubeTimeTrackingInterval = null;
97
- this.youtubeAvailableQualities = [];
98
- this.youtubeCurrentQuality = null;
97
+ this.isYouTubeLive = false;
98
+ this.useYouTubeNativeControls = false;
99
99
  this.clickToUnmuteHandler = null;
100
100
  }
101
101
  debugLog(message, ...args) {
@@ -896,7 +896,7 @@ export class WebPlayer extends BasePlayer {
896
896
  width: '100%',
897
897
  height: '100%',
898
898
  playerVars: {
899
- controls: 0,
899
+ controls: this.config.youtubeNativeControls !== false ? 1 : 0,
900
900
  disablekb: 0,
901
901
  fs: 0,
902
902
  iv_load_policy: 3,
@@ -953,11 +953,11 @@ export class WebPlayer extends BasePlayer {
953
953
  if (this.config.muted) {
954
954
  this.youtubePlayer.mute();
955
955
  }
956
- this.extractYouTubeAvailableQualities();
956
+ this.detectYouTubeLiveStatus();
957
957
  this.getYouTubeVideoTitle();
958
958
  setTimeout(() => {
959
959
  this.updateMetadataUI();
960
- this.updateSettingsMenu();
960
+ this.updateControlsVisibility();
961
961
  }, 500);
962
962
  }
963
963
  this.startYouTubeTimeTracking();
@@ -971,9 +971,6 @@ export class WebPlayer extends BasePlayer {
971
971
  this.state.isPaused = false;
972
972
  this.state.isBuffering = false;
973
973
  this.updateYouTubeUI('playing');
974
- setTimeout(() => {
975
- this.extractYouTubeAvailableQualities();
976
- }, 1000);
977
974
  this.emit('onPlay');
978
975
  break;
979
976
  case window.YT.PlayerState.PAUSED:
@@ -986,9 +983,6 @@ export class WebPlayer extends BasePlayer {
986
983
  case window.YT.PlayerState.BUFFERING:
987
984
  this.state.isBuffering = true;
988
985
  this.updateYouTubeUI('buffering');
989
- setTimeout(() => {
990
- this.extractYouTubeAvailableQualities();
991
- }, 500);
992
986
  this.emit('onBuffering', true);
993
987
  break;
994
988
  case window.YT.PlayerState.ENDED:
@@ -1002,8 +996,9 @@ export class WebPlayer extends BasePlayer {
1002
996
  this.state.duration = this.youtubePlayer.getDuration();
1003
997
  this.updateYouTubeUI('cued');
1004
998
  setTimeout(() => {
1005
- this.getYouTubeVideoTitle();
1006
- }, 100);
999
+ this.detectYouTubeLiveStatus();
1000
+ this.updateControlsVisibility();
1001
+ }, 500);
1007
1002
  break;
1008
1003
  }
1009
1004
  }
@@ -1072,6 +1067,120 @@ export class WebPlayer extends BasePlayer {
1072
1067
  this.getYouTubeVideoTitleFromOEmbed();
1073
1068
  }
1074
1069
  }
1070
+ detectYouTubeLiveStatus() {
1071
+ if (!this.youtubePlayer || !this.youtubePlayerReady)
1072
+ return;
1073
+ try {
1074
+ const videoData = this.youtubePlayer.getVideoData();
1075
+ this.isYouTubeLive = videoData?.isLive || false;
1076
+ this.useYouTubeNativeControls = this.config.youtubeNativeControls !== false;
1077
+ this.debugLog('YouTube Live status:', {
1078
+ isLive: this.isYouTubeLive,
1079
+ useNativeControls: this.useYouTubeNativeControls,
1080
+ videoId: videoData?.video_id
1081
+ });
1082
+ }
1083
+ catch (error) {
1084
+ this.debugWarn('Could not detect YouTube Live status:', error);
1085
+ try {
1086
+ const duration = this.youtubePlayer.getDuration();
1087
+ this.isYouTubeLive = !duration || duration === 0;
1088
+ this.useYouTubeNativeControls = this.config.youtubeNativeControls !== false;
1089
+ this.debugLog('YouTube Live detected via duration check:', {
1090
+ duration,
1091
+ isLive: this.isYouTubeLive,
1092
+ useNativeControls: this.useYouTubeNativeControls
1093
+ });
1094
+ }
1095
+ catch (e) {
1096
+ this.debugWarn('Could not check YouTube duration for Live detection:', e);
1097
+ }
1098
+ }
1099
+ }
1100
+ updateControlsVisibility() {
1101
+ const controlsContainer = document.getElementById('uvf-controls-container');
1102
+ if (!controlsContainer)
1103
+ return;
1104
+ if (this.youtubePlayer && this.useYouTubeNativeControls) {
1105
+ controlsContainer.style.display = 'none';
1106
+ if (!this.config.youtubeNativeControls) {
1107
+ this.recreateYouTubePlayerWithNativeControls();
1108
+ }
1109
+ this.debugLog('✅ YouTube native controls enabled', {
1110
+ isLive: this.isYouTubeLive,
1111
+ reason: this.config.youtubeNativeControls !== false ? 'Default behavior (all YouTube content)' : 'Live stream detected'
1112
+ });
1113
+ }
1114
+ else {
1115
+ controlsContainer.style.display = 'flex';
1116
+ this.debugLog('✅ Custom controls enabled for YouTube video');
1117
+ }
1118
+ }
1119
+ recreateYouTubePlayerWithNativeControls() {
1120
+ if (!this.source?.metadata?.videoId)
1121
+ return;
1122
+ const videoId = this.source.metadata.videoId;
1123
+ const currentTime = this.youtubePlayer?.getCurrentTime() || 0;
1124
+ const container = this.playerWrapper || this.video?.parentElement;
1125
+ if (!container)
1126
+ return;
1127
+ if (this.youtubePlayer) {
1128
+ this.youtubePlayer.destroy();
1129
+ }
1130
+ const existingContainer = container.querySelector(`#youtube-player-${videoId}`);
1131
+ if (existingContainer) {
1132
+ existingContainer.remove();
1133
+ }
1134
+ const iframeContainer = document.createElement('div');
1135
+ iframeContainer.id = `youtube-player-${videoId}`;
1136
+ iframeContainer.style.cssText = `
1137
+ position: absolute;
1138
+ top: 0;
1139
+ left: 0;
1140
+ width: 100%;
1141
+ height: 100%;
1142
+ z-index: 1;
1143
+ `;
1144
+ container.appendChild(iframeContainer);
1145
+ this.youtubePlayer = new window.YT.Player(iframeContainer.id, {
1146
+ videoId: videoId,
1147
+ width: '100%',
1148
+ height: '100%',
1149
+ playerVars: {
1150
+ autoplay: this.config.autoPlay ? 1 : 0,
1151
+ controls: 1,
1152
+ modestbranding: 1,
1153
+ rel: 0,
1154
+ showinfo: 0,
1155
+ iv_load_policy: 3,
1156
+ playsinline: 1,
1157
+ start: Math.floor(currentTime)
1158
+ },
1159
+ events: {
1160
+ onReady: () => {
1161
+ this.youtubePlayerReady = true;
1162
+ this.debugLog('YouTube player with native controls ready');
1163
+ this.emit('onReady');
1164
+ },
1165
+ onStateChange: (event) => this.onYouTubePlayerStateChange(event),
1166
+ onError: (event) => this.onYouTubePlayerError(event)
1167
+ }
1168
+ });
1169
+ }
1170
+ toggleYouTubeControls(useNative = !this.useYouTubeNativeControls) {
1171
+ if (!this.youtubePlayer) {
1172
+ this.debugWarn('Cannot toggle YouTube controls - no YouTube player active');
1173
+ return;
1174
+ }
1175
+ this.useYouTubeNativeControls = useNative;
1176
+ this.config.youtubeNativeControls = useNative;
1177
+ this.updateControlsVisibility();
1178
+ this.debugLog('YouTube controls toggled:', {
1179
+ useNative: this.useYouTubeNativeControls,
1180
+ isLive: this.isYouTubeLive
1181
+ });
1182
+ this.showNotification(`YouTube Controls: ${this.useYouTubeNativeControls ? 'Native' : 'Custom'}`);
1183
+ }
1075
1184
  async getYouTubeVideoTitleFromOEmbed() {
1076
1185
  if (!this.source?.metadata?.videoId)
1077
1186
  return;
@@ -1094,172 +1203,10 @@ export class WebPlayer extends BasePlayer {
1094
1203
  this.debugWarn('Could not get YouTube title from oembed API:', error);
1095
1204
  }
1096
1205
  }
1097
- extractYouTubeAvailableQualities(retryCount = 0) {
1098
- if (!this.youtubePlayer || !this.youtubePlayerReady) {
1099
- this.debugLog('YouTube player not ready for quality extraction');
1100
- return;
1101
- }
1102
- try {
1103
- const availableQualityLevels = this.youtubePlayer.getAvailableQualityLevels();
1104
- this.debugLog('🔍 YouTube quality detection attempt', retryCount + 1, '- Found levels:', availableQualityLevels);
1105
- this.debugLog('🔍 YouTube player state:', this.youtubePlayer.getPlayerState());
1106
- this.debugLog('🔍 Current YouTube quality:', this.youtubePlayer.getPlaybackQuality());
1107
- if ((!availableQualityLevels || availableQualityLevels.length === 0 || (availableQualityLevels.length === 1 && availableQualityLevels[0] === 'auto')) && retryCount < 5) {
1108
- this.debugLog('No meaningful qualities detected, retrying in', (retryCount + 1) * 1000, 'ms...');
1109
- setTimeout(() => {
1110
- this.extractYouTubeAvailableQualities(retryCount + 1);
1111
- }, (retryCount + 1) * 1000);
1112
- return;
1113
- }
1114
- const qualityMap = {
1115
- 'hd2160': { label: '4K', value: 'hd2160', height: 2160 },
1116
- 'hd1440': { label: '1440p', value: 'hd1440', height: 1440 },
1117
- 'hd1080': { label: '1080p', value: 'hd1080', height: 1080 },
1118
- 'hd720': { label: '720p', value: 'hd720', height: 720 },
1119
- 'large': { label: '480p', value: 'large', height: 480 },
1120
- 'medium': { label: '360p', value: 'medium', height: 360 },
1121
- 'small': { label: '240p', value: 'small', height: 240 },
1122
- 'tiny': { label: '144p', value: 'tiny', height: 144 },
1123
- 'auto': { label: 'Auto', value: 'auto', height: 0 }
1124
- };
1125
- this.youtubeAvailableQualities = [];
1126
- this.youtubeAvailableQualities.push({ label: 'Auto', value: 'auto', height: 0 });
1127
- if (availableQualityLevels && availableQualityLevels.length > 0) {
1128
- availableQualityLevels.forEach((qualityLevel) => {
1129
- if (qualityLevel !== 'auto' && qualityMap[qualityLevel]) {
1130
- this.youtubeAvailableQualities.push(qualityMap[qualityLevel]);
1131
- }
1132
- });
1133
- const autoQuality = this.youtubeAvailableQualities.shift();
1134
- this.youtubeAvailableQualities.sort((a, b) => (b.height || 0) - (a.height || 0));
1135
- if (autoQuality)
1136
- this.youtubeAvailableQualities.unshift(autoQuality);
1137
- }
1138
- this.debugLog('✅ Successfully mapped YouTube qualities:', this.youtubeAvailableQualities);
1139
- try {
1140
- const currentQuality = this.youtubePlayer.getPlaybackQuality();
1141
- this.youtubeCurrentQuality = qualityMap[currentQuality] || qualityMap['auto'];
1142
- this.debugLog('Current YouTube quality:', this.youtubeCurrentQuality);
1143
- }
1144
- catch (e) {
1145
- this.debugWarn('Could not get current YouTube quality:', e);
1146
- this.youtubeCurrentQuality = qualityMap['auto'];
1147
- }
1148
- this.detectAvailableQualities();
1149
- this.debugLog('🚀 YouTube qualities successfully detected! Available qualities now:', this.availableQualities.length);
1150
- this.updateQualityBadge();
1151
- setTimeout(() => {
1152
- this.debugLog('🔄 Force refreshing settings menu with YouTube qualities');
1153
- this.updateSettingsMenu();
1154
- }, 100);
1155
- }
1156
- catch (error) {
1157
- this.debugWarn('Could not extract YouTube qualities:', error);
1158
- if (retryCount < 3) {
1159
- setTimeout(() => {
1160
- this.extractYouTubeAvailableQualities(retryCount + 1);
1161
- }, 2000);
1162
- }
1163
- }
1164
- }
1165
- setYouTubeQuality(qualityLevel) {
1166
- if (!this.youtubePlayer)
1167
- return;
1168
- try {
1169
- const qualityMap = {
1170
- 'hd2160': { label: '4K', value: 'hd2160', height: 2160 },
1171
- 'hd1440': { label: '1440p', value: 'hd1440', height: 1440 },
1172
- 'hd1080': { label: '1080p', value: 'hd1080', height: 1080 },
1173
- 'hd720': { label: '720p', value: 'hd720', height: 720 },
1174
- 'large': { label: '480p', value: 'large', height: 480 },
1175
- 'medium': { label: '360p', value: 'medium', height: 360 },
1176
- 'small': { label: '240p', value: 'small', height: 240 },
1177
- 'tiny': { label: '144p', value: 'tiny', height: 144 },
1178
- 'auto': { label: 'Auto', value: 'auto', height: 0 }
1179
- };
1180
- this.debugLog('📊 Attempting to set YouTube quality to:', qualityLevel);
1181
- if (qualityLevel === 'auto') {
1182
- this.youtubePlayer.setPlaybackQuality(qualityLevel);
1183
- }
1184
- else {
1185
- try {
1186
- if (typeof this.youtubePlayer.setPlaybackQualityRange === 'function') {
1187
- this.youtubePlayer.setPlaybackQualityRange(qualityLevel, qualityLevel);
1188
- this.debugLog('✅ setPlaybackQualityRange called for:', qualityLevel);
1189
- }
1190
- }
1191
- catch (e) {
1192
- this.debugWarn('setPlaybackQualityRange failed:', e);
1193
- }
1194
- try {
1195
- this.youtubePlayer.setPlaybackQuality(qualityLevel);
1196
- this.debugLog('✅ setPlaybackQuality called for:', qualityLevel);
1197
- }
1198
- catch (e) {
1199
- this.debugWarn('setPlaybackQuality failed:', e);
1200
- }
1201
- try {
1202
- const wasPlaying = this.youtubePlayer.getPlayerState() === window.YT.PlayerState.PLAYING;
1203
- if (wasPlaying) {
1204
- this.youtubePlayer.pauseVideo();
1205
- setTimeout(() => {
1206
- this.youtubePlayer.setPlaybackQuality(qualityLevel);
1207
- this.youtubePlayer.playVideo();
1208
- }, 100);
1209
- }
1210
- }
1211
- catch (e) {
1212
- this.debugWarn('Force quality change with pause/play failed:', e);
1213
- }
1214
- }
1215
- setTimeout(() => {
1216
- try {
1217
- const currentQuality = this.youtubePlayer.getPlaybackQuality();
1218
- this.youtubeCurrentQuality = qualityMap[currentQuality] || qualityMap['auto'];
1219
- this.debugLog('📊 Current quality after set:', currentQuality, '(', this.youtubeCurrentQuality?.label, ')');
1220
- if (currentQuality === qualityLevel) {
1221
- this.showNotification(`Quality: ${qualityMap[currentQuality]?.label || 'requested quality'}`);
1222
- this.debugLog('✅ Quality successfully changed to:', qualityLevel);
1223
- }
1224
- else {
1225
- this.showNotification(`Quality: ${qualityMap[currentQuality]?.label || currentQuality}`);
1226
- this.debugLog('⚠️ Requested quality:', qualityLevel, '| Actual quality:', currentQuality, '(YouTube may have optimized)');
1227
- }
1228
- this.updateQualityBadge();
1229
- this.updateSettingsMenu();
1230
- }
1231
- catch (verifyError) {
1232
- this.debugWarn('Could not verify YouTube quality:', verifyError);
1233
- }
1234
- }, 1000);
1235
- }
1236
- catch (error) {
1237
- this.debugWarn('❌ Could not set YouTube quality:', error);
1238
- this.showNotification('Quality control limited on YouTube');
1239
- }
1240
- }
1241
- updateQualityBadge() {
1242
- const qualityBadge = document.getElementById('uvf-quality-badge');
1243
- if (!qualityBadge)
1244
- return;
1245
- if (this.youtubePlayer && this.youtubePlayerReady && this.youtubeCurrentQuality) {
1246
- qualityBadge.textContent = this.youtubeCurrentQuality.label || 'AUTO';
1247
- this.debugLog('Updated quality badge for YouTube:', this.youtubeCurrentQuality.label);
1248
- }
1249
- else if (this.currentQualityIndex >= 0 && this.qualities[this.currentQualityIndex]) {
1250
- const quality = this.qualities[this.currentQualityIndex];
1251
- qualityBadge.textContent = quality.label || 'HD';
1252
- this.debugLog('Updated quality badge for standard video:', quality.label);
1253
- }
1254
- else {
1255
- qualityBadge.textContent = 'AUTO';
1256
- }
1257
- }
1258
1206
  startYouTubeTimeTracking() {
1259
1207
  if (this.youtubeTimeTrackingInterval) {
1260
1208
  clearInterval(this.youtubeTimeTrackingInterval);
1261
1209
  }
1262
- let qualityCheckCounter = 0;
1263
1210
  this.youtubeTimeTrackingInterval = setInterval(() => {
1264
1211
  if (this.youtubePlayer && this.youtubePlayerReady) {
1265
1212
  try {
@@ -1269,12 +1216,6 @@ export class WebPlayer extends BasePlayer {
1269
1216
  this.state.currentTime = currentTime || 0;
1270
1217
  this.state.duration = duration || 0;
1271
1218
  this.state.bufferedPercentage = buffered || 0;
1272
- qualityCheckCounter++;
1273
- if (qualityCheckCounter >= 40 && this.youtubeAvailableQualities.length <= 1) {
1274
- this.debugLog('Periodic YouTube quality check triggered');
1275
- this.extractYouTubeAvailableQualities();
1276
- qualityCheckCounter = 0;
1277
- }
1278
1219
  this.updateYouTubeProgressBar(currentTime, duration, buffered);
1279
1220
  this.emit('onTimeUpdate', this.state.currentTime);
1280
1221
  this.emit('onProgress', this.state.bufferedPercentage);
@@ -8136,18 +8077,13 @@ export class WebPlayer extends BasePlayer {
8136
8077
  }
8137
8078
  setQualityByLabel(quality) {
8138
8079
  if (this.youtubePlayer && this.youtubePlayerReady) {
8139
- this.setYouTubeQuality(quality);
8080
+ this.showShortcutIndicator('Quality control not available for YouTube');
8140
8081
  document.querySelectorAll('.quality-option').forEach(option => {
8141
8082
  option.classList.remove('active');
8142
- if (option.dataset.quality === quality) {
8083
+ if (option.dataset.quality === 'auto') {
8143
8084
  option.classList.add('active');
8144
8085
  }
8145
8086
  });
8146
- const qualityBadge = document.getElementById('uvf-quality-badge');
8147
- if (qualityBadge) {
8148
- const qualityOption = this.youtubeAvailableQualities.find(q => q.value === quality);
8149
- qualityBadge.textContent = qualityOption ? qualityOption.label : 'AUTO';
8150
- }
8151
8087
  return;
8152
8088
  }
8153
8089
  const qualityBadge = document.getElementById('uvf-quality-badge');
@@ -8448,13 +8384,8 @@ export class WebPlayer extends BasePlayer {
8448
8384
  </div>`;
8449
8385
  }
8450
8386
  if (this.settingsConfig.quality && this.availableQualities.length > 0) {
8451
- this.debugLog('🎛️ Building quality menu with', this.availableQualities.length, 'qualities:', this.availableQualities.map(q => q.label));
8452
- const qualityForDisplay = this.youtubePlayer && this.youtubePlayerReady && this.youtubeCurrentQuality
8453
- ? this.youtubeCurrentQuality.value
8454
- : this.currentQuality;
8455
- const currentQuality = this.availableQualities.find(q => q.value === qualityForDisplay);
8387
+ const currentQuality = this.availableQualities.find(q => q.value === this.currentQuality);
8456
8388
  const currentQualityLabel = currentQuality ? currentQuality.label : 'Auto';
8457
- this.debugLog('🎛️ Current quality for display:', qualityForDisplay, '(', currentQualityLabel, ')');
8458
8389
  menuHTML += `
8459
8390
  <div class="uvf-accordion-item">
8460
8391
  <div class="uvf-accordion-header" data-section="quality">
@@ -8471,10 +8402,7 @@ export class WebPlayer extends BasePlayer {
8471
8402
  </div>
8472
8403
  <div class="uvf-accordion-content" data-section="quality">`;
8473
8404
  this.availableQualities.forEach(quality => {
8474
- const qualityValue = this.youtubePlayer && this.youtubePlayerReady && this.youtubeCurrentQuality
8475
- ? this.youtubeCurrentQuality.value
8476
- : this.currentQuality;
8477
- const isActive = quality.value === qualityValue ? 'active' : '';
8405
+ const isActive = quality.value === this.currentQuality ? 'active' : '';
8478
8406
  const isPremium = this.isQualityPremium(quality);
8479
8407
  const isLocked = isPremium && !this.isPremiumUser();
8480
8408
  const qualityHeight = quality.height || 0;
@@ -8524,20 +8452,9 @@ export class WebPlayer extends BasePlayer {
8524
8452
  this.debugLog('Settings event listeners setup complete');
8525
8453
  }
8526
8454
  detectAvailableQualities() {
8527
- const isYouTube = this.youtubePlayer && this.youtubePlayerReady && this.youtubeAvailableQualities.length > 0;
8528
- this.availableQualities = [];
8455
+ this.availableQualities = [{ value: 'auto', label: 'Auto' }];
8529
8456
  let detectedQualities = [];
8530
- if (isYouTube) {
8531
- detectedQualities = this.youtubeAvailableQualities.map(q => ({
8532
- value: q.value,
8533
- label: q.label,
8534
- height: q.height
8535
- }));
8536
- this.debugLog('Using YouTube qualities:', detectedQualities);
8537
- this.availableQualities = detectedQualities;
8538
- return;
8539
- }
8540
- else if (this.hls && this.hls.levels) {
8457
+ if (this.hls && this.hls.levels) {
8541
8458
  this.hls.levels.forEach((level, index) => {
8542
8459
  if (level.height) {
8543
8460
  detectedQualities.push({
@@ -8782,11 +8699,6 @@ export class WebPlayer extends BasePlayer {
8782
8699
  }
8783
8700
  setQualityFromSettings(quality) {
8784
8701
  this.currentQuality = quality;
8785
- if (this.youtubePlayer && this.youtubePlayerReady) {
8786
- this.setYouTubeQuality(quality);
8787
- this.debugLog(`YouTube quality set to ${quality}`);
8788
- return;
8789
- }
8790
8702
  if (quality === 'auto') {
8791
8703
  if (this.hls) {
8792
8704
  if (this.qualityFilter) {