@grfzhl/vue-hls-player 1.1.26 → 1.1.28

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.
package/README.md CHANGED
@@ -207,6 +207,14 @@ At the moment the following attribute are supported:
207
207
  ```
208
208
 
209
209
  ### Last release:
210
+ v1.1.28
211
+ - Always render the subtitle overlay container regardless of fullscreen or transcript state.
212
+ - Clear subtitle overlay text when no text track is actively showing.
213
+ v1.1.27
214
+ - Expose BasePlayer fullscreen control through wrapper components.
215
+ - Add support for forced fullscreen re-entry from parent integrations.
216
+ - Avoid fullscreen request errors when no user activation is available.
217
+ - Keep fullscreen state and emitted events in sync across native and document fullscreen modes.
210
218
  v1.1.26
211
219
  - Prevent HLS chunk preloading during player initialization.
212
220
  - Start HLS loading only after user interaction.
@@ -63,7 +63,7 @@
63
63
  </media-fullscreen-button>
64
64
  </media-control-bar>
65
65
  </media-theme-sutro>
66
- <div class="custom-subtitles" v-show="(isFullscreen) || (!showTranscriptBlock)">
66
+ <div class="custom-subtitles" v-show="isFullscreen">
67
67
  <div class="subtitle-text" ref="subtitlesContainer" style="display: none;"></div>
68
68
  </div>
69
69
  <slot name="after-media"></slot>
@@ -355,6 +355,8 @@ function emitPointerUpdate() {
355
355
 
356
356
  const videoElement = defineModel()
357
357
 
358
+ defineExpose({ startFullscreen })
359
+
358
360
  onMounted(() => {
359
361
  if (video.value) {
360
362
  video.value.muted = mutedAttr.value
@@ -417,57 +419,79 @@ watch(
417
419
  }
418
420
  )
419
421
 
420
- async function startFullscreen() {
422
+ function updateFullscreenButtonState(isActive) {
423
+ if (!buttonElement) return
424
+
425
+ buttonElement.setAttribute(
426
+ 'aria-label',
427
+ isActive ? 'Exit fullscreen mode' : 'Enter fullscreen mode'
428
+ )
429
+
430
+ if (isActive) {
431
+ buttonElement.setAttribute('mediaIsFullscreen', '')
432
+ } else {
433
+ buttonElement.removeAttribute('mediaIsFullscreen')
434
+ }
435
+
436
+ const tooltip = buttonElement.shadowRoot?.querySelector('media-tooltip')
437
+ const enterTooltip = tooltip?.querySelector('slot[name="tooltip-enter"]')
438
+ const exitTooltip = tooltip?.querySelector('slot[name="tooltip-exit"]')
439
+
440
+ if (enterTooltip) {
441
+ enterTooltip.style.display = isActive ? 'none' : 'block'
442
+ }
443
+
444
+ if (exitTooltip) {
445
+ exitTooltip.style.display = isActive ? 'block' : 'none'
446
+ }
447
+ }
448
+
449
+ async function startFullscreen(forceEnter = false) {
421
450
  let vpVideoBlock = document.getElementById(props.fullScreenElement);
451
+ const hasDocumentFullscreen = !!document.fullscreenElement;
452
+ const hasNativeVideoFullscreen = !!video.value?.webkitDisplayingFullscreen;
453
+ const hasUserActivation =
454
+ typeof navigator === 'undefined' ||
455
+ !navigator.userActivation ||
456
+ navigator.userActivation.isActive;
422
457
  if(video.value) {
423
458
  currentTime = video.value.currentTime
424
459
  }
425
- if (document.fullscreenElement) {
460
+ if (forceEnter && (hasDocumentFullscreen || hasNativeVideoFullscreen)) {
461
+ isFullscreen.value = true;
462
+ emit('video-fullscreen-change', true)
463
+ return;
464
+ }
465
+
466
+ // Browsers only allow fullscreen requests from a short-lived user interaction.
467
+ if (forceEnter && !hasUserActivation) {
468
+ return;
469
+ }
470
+
471
+ if (hasDocumentFullscreen || hasNativeVideoFullscreen) {
426
472
  if (screen.orientation && screen.orientation.unlock) {
427
473
  screen.orientation.unlock();
428
474
  }
429
- await document.exitFullscreen();
475
+ if (document.fullscreenElement && typeof document.exitFullscreen === 'function') {
476
+ await document.exitFullscreen();
477
+ }
430
478
  if (/iPhone|iPad|AppleWebKit/i.test(navigator.userAgent)) {
431
- document.webkitExitFullscreen();
432
- }
433
- isFullscreen.value = false;
434
- buttonElement.setAttribute('aria-label', "Enter fullscreen mode")
435
- buttonElement.removeAttribute('mediaIsFullscreen');
436
- const tooltip = buttonElement.shadowRoot?.querySelector('media-tooltip');
437
- if (tooltip) {
438
- // Slots für Tooltip-Enter und Tooltip-Exit finden
439
- const enterTooltip = tooltip.querySelector('slot[name="tooltip-enter"]');
440
- const exitTooltip = tooltip.querySelector('slot[name="tooltip-exit"]');
441
-
442
- if (enterTooltip && exitTooltip) {
443
- enterTooltip.style.display = 'block';
444
- exitTooltip.style.display = 'none';
445
- } else {
446
- console.warn("Tooltip-Slots nicht gefunden!");
479
+ if (typeof video.value?.webkitExitFullscreen === 'function') {
480
+ video.value.webkitExitFullscreen();
481
+ } else if (typeof video.value?.webkitExitFullScreen === 'function') {
482
+ video.value.webkitExitFullScreen();
483
+ } else if (typeof document.webkitExitFullscreen === 'function') {
484
+ document.webkitExitFullscreen();
447
485
  }
448
- } else {
449
- console.warn("Kein media-tooltip gefunden!");
450
486
  }
487
+ isFullscreen.value = false;
488
+ emit('video-fullscreen-change', false)
489
+ updateFullscreenButtonState(false)
451
490
  } else {
452
491
  isFullscreen.value = true;
492
+ emit('video-fullscreen-change', true)
453
493
  try {
454
- buttonElement.setAttribute('aria-label', "Exit fullscreen mode")
455
- buttonElement.setAttribute('mediaIsFullscreen', '');
456
- const tooltip = buttonElement.shadowRoot?.querySelector('media-tooltip');
457
- if (tooltip) {
458
- // Slots für Tooltip-Enter und Tooltip-Exit finden
459
- const enterTooltip = tooltip.querySelector('slot[name="tooltip-enter"]');
460
- const exitTooltip = tooltip.querySelector('slot[name="tooltip-exit"]');
461
-
462
- if (enterTooltip && exitTooltip) {
463
- enterTooltip.style.display = 'none';
464
- exitTooltip.style.display = 'block';
465
- } else {
466
- console.warn("Tooltip-Slots nicht gefunden!");
467
- }
468
- } else {
469
- console.warn("Kein media-tooltip gefunden!");
470
- }
494
+ updateFullscreenButtonState(true)
471
495
 
472
496
  if (vpVideoBlock.requestFullscreen) {
473
497
  await vpVideoBlock.requestFullscreen();
@@ -480,6 +504,7 @@ async function startFullscreen() {
480
504
  vpVideoBlock.style.height = "auto";
481
505
  }, 100);
482
506
  isFullscreen.value = false;
507
+ emit('video-fullscreen-change', false)
483
508
  vpVideoBlock.removeEventListener("webkitendfullscreen")
484
509
  });
485
510
  } else if (vpVideoBlock.mozRequestFullScreen) {
@@ -491,14 +516,15 @@ async function startFullscreen() {
491
516
  try {
492
517
  await screen.orientation.lock("landscape");
493
518
  } catch (error) {
494
- console.warn("Orientation lock failed", error);
519
+ // ignore unsupported mobile/browser orientation lock failures
495
520
  }
496
- } else {
497
- console.warn("Orientation lock not supported.");
498
521
  }
499
522
  } catch (error) {
500
- console.error("Fullscreen could not be activated", error);
523
+ if (!forceEnter) {
524
+ console.error("Fullscreen could not be activated", error);
525
+ }
501
526
  isFullscreen.value = false;
527
+ emit('video-fullscreen-change', false)
502
528
  }
503
529
 
504
530
  video.value.currentTime = currentTime;
@@ -520,6 +546,7 @@ function onFullscreenChange(e) {
520
546
  autoHideIntroTitle.value = false;
521
547
  }
522
548
  isFullscreen.value = !!document.fullscreenElement;
549
+ emit('video-fullscreen-change', isFullscreen.value)
523
550
  };
524
551
 
525
552
  function onOrientationChange(e) {
@@ -799,7 +826,15 @@ function prepareVideoPlayer(link, { previewOnly = false } = {}) {
799
826
  video.value.muted = props.isMuted;
800
827
  video.value.currentTime = props.progress;
801
828
  // Chrome-like: update menu whenever track mode changes
802
- video.value.textTracks.addEventListener('change', updateLangMenuState);
829
+ video.value.textTracks.addEventListener('change', () => {
830
+ updateLangMenuState();
831
+ // Clear the subtitle overlay when no track is actively showing.
832
+ const anyShowing = Array.from(video.value.textTracks || []).some(t => t.mode === 'showing');
833
+ if (!anyShowing && subtitlesContainer.value) {
834
+ subtitlesContainer.value.style.display = 'none';
835
+ subtitlesContainer.value.textContent = '';
836
+ }
837
+ });
803
838
  }
804
839
 
805
840
 
@@ -118,7 +118,7 @@ function onFullscreenChange(data) {
118
118
  function onLanguageChanged(data) {
119
119
  emit('language-changed', data);
120
120
  }
121
- function startFullscreen() {
122
- childRef.value.startFullscreen();
121
+ function startFullscreen(forceEnter = false) {
122
+ return childRef.value?.startFullscreen?.(forceEnter)
123
123
  }
124
124
  </script>
@@ -120,7 +120,7 @@ function onVideoFullScreenChange(data) {
120
120
  function onVideoEnd(data) {
121
121
  emit('video-ended', data);
122
122
  }
123
- function startFullscreen() {
124
- childRef.value.startFullscreen();
123
+ function startFullscreen(forceEnter = false) {
124
+ return childRef.value?.startFullscreen?.(forceEnter)
125
125
  }
126
126
  </script>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@grfzhl/vue-hls-player",
3
3
  "private": false,
4
- "version": "1.1.26",
4
+ "version": "1.1.28",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist"