@cloudflare/realtimekit-ui 1.0.4 → 1.0.5-staging.2

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.
Files changed (31) hide show
  1. package/dist/browser.js +1 -1
  2. package/dist/cjs/loader.cjs.js +1 -1
  3. package/dist/cjs/realtimekit-ui.cjs.js +1 -1
  4. package/dist/cjs/rtk-avatar_54.cjs.entry.js +1 -1
  5. package/dist/cjs/rtk-livestream-indicator_3.cjs.entry.js +101 -9
  6. package/dist/collection/components/rtk-dialog/rtk-dialog.css +9 -1
  7. package/dist/collection/components/rtk-livestream-player/rtk-livestream-player.css +66 -2
  8. package/dist/collection/components/rtk-livestream-player/rtk-livestream-player.js +104 -9
  9. package/dist/components/{p-734d61ed.js → p-1745c048.js} +105 -10
  10. package/dist/components/{p-aaaa2ffd.js → p-1d624be9.js} +3 -3
  11. package/dist/components/{p-750d5890.js → p-7960f355.js} +1 -1
  12. package/dist/components/{p-72ca9fd3.js → p-8f513cbb.js} +1 -1
  13. package/dist/components/{p-431bde35.js → p-cb12ef1a.js} +1 -1
  14. package/dist/components/rtk-channel-header.js +1 -1
  15. package/dist/components/rtk-chat.js +1 -1
  16. package/dist/components/rtk-dialog-manager.js +1 -1
  17. package/dist/components/rtk-dialog.js +1 -1
  18. package/dist/components/rtk-grid.js +1 -1
  19. package/dist/components/rtk-livestream-player.js +1 -1
  20. package/dist/components/rtk-meeting.js +4 -4
  21. package/dist/components/rtk-participants-audio.js +1 -1
  22. package/dist/docs/docs-components.json +1 -1
  23. package/dist/esm/loader.js +103 -11
  24. package/dist/esm/realtimekit-ui.js +1 -1
  25. package/dist/esm/rtk-avatar_54.entry.js +1 -1
  26. package/dist/esm/rtk-livestream-indicator_3.entry.js +101 -9
  27. package/dist/realtimekit-ui/{p-65830fdd.entry.js → p-31af32f0.entry.js} +1 -1
  28. package/dist/realtimekit-ui/{p-066ac543.entry.js → p-617e5efa.entry.js} +1 -1
  29. package/dist/realtimekit-ui/realtimekit-ui.esm.js +1 -1
  30. package/dist/types/components/rtk-livestream-player/rtk-livestream-player.d.ts +11 -0
  31. package/package.json +2 -3
@@ -35672,7 +35672,7 @@ function formatSecondsToHHMMSS(seconds) {
35672
35672
  : `${mins}:${secs.toString().padStart(2, '0')}`;
35673
35673
  }
35674
35674
 
35675
- const rtkLivestreamPlayerCss = ":host{line-height:initial;font-family:var(--rtk-font-family, sans-serif);font-feature-settings:normal;font-variation-settings:normal}p{margin:var(--rtk-space-0, 0px);padding:var(--rtk-space-0, 0px)}:host{display:flex;height:100%;max-height:100%;min-height:100%;width:100%;min-width:100%;max-width:100%;border-radius:var(--rtk-border-radius-md, 8px);--tw-bg-opacity:1;background-color:rgba(var(--rtk-colors-background-900, 26 26 26) / var(--tw-bg-opacity))}.player-container{position:relative;margin-left:var(--rtk-space-4, 16px);margin-right:var(--rtk-space-4, 16px);margin-top:var(--rtk-space-4, 16px);margin-bottom:var(--rtk-space-0, 0px);display:flex;flex-grow:1;align-items:center;justify-content:center;border-radius:var(--rtk-border-radius-md, 8px)}.loader{position:absolute;z-index:10;height:100%;width:100%;--tw-bg-opacity:1;background-color:rgba(var(--rtk-colors-background-900, 26 26 26) / var(--tw-bg-opacity));display:flex;flex-direction:column;align-items:center;justify-content:center}p{margin-top:var(--rtk-space-1, 4px);margin-bottom:var(--rtk-space-1, 4px);font-size:16px;color:rgb(var(--rtk-colors-text-700, 255 255 255 / 0.64))}.unmute-popup{position:absolute;z-index:30 !important;display:flex;width:var(--rtk-space-72, 288px);flex-direction:column;border-radius:var(--rtk-border-radius-md, 8px);--tw-bg-opacity:1;background-color:rgba(var(--rtk-colors-background-800, 30 30 30) / var(--tw-bg-opacity));padding:var(--rtk-space-4, 16px);text-align:center;max-width:70%}.unmute-popup h3{margin:var(--rtk-space-0, 0px);font-size:16px;font-weight:500}.unmute-popup p{margin-top:var(--rtk-space-3, 12px);margin-bottom:var(--rtk-space-3, 12px);font-size:14px}.control-bar{position:absolute;bottom:0;left:16px;right:16px;display:flex;height:auto;justify-content:space-between;align-items:center;padding:12px 16px;z-index:30;--tw-bg-opacity:1;background-color:rgba(var(--rtk-colors-background-900, 26 26 26) / var(--tw-bg-opacity));border-radius:6px;border-radius:var(--rtk-border-radius-md, 8px);background-color:rgba(var(--rtk-colors-background-900, 26 26 26) / 0.8)}.timings{color:rgb(var(--rtk-colors-text-on-brand-1000, var(--rtk-colors-text-1000, 255 255 255)))}.control-btn{border:none;margin-right:var(--rtk-space-2, 8px);border-radius:var(--rtk-border-radius-sm, 4px);--tw-bg-opacity:1;background-color:rgba(var(--rtk-colors-brand-500, 33 96 253) / var(--tw-bg-opacity));color:rgb(var(--rtk-colors-text-on-brand-1000, var(--rtk-colors-text-1000, 255 255 255)));cursor:pointer;font-size:24px;height:30px;width:30px}.fullscreen-btn{margin-right:20px;height:30px}.control-btn:hover{opacity:0.8}.control-btn:focus{outline:none}.control-groups{display:flex;align-items:center;justify-content:space-around}#livestream-video,.livestream-video{position:absolute;top:0;left:0;width:100% !important;height:100% !important;max-width:none !important;max-height:none !important;min-width:100% !important;min-height:100% !important;-o-object-fit:fill !important;object-fit:fill !important;z-index:10;border-radius:var(--rtk-border-radius-md, 8px);border-width:0px}:host:not(:fullscreen) .video-container{padding:16px 16px 0 16px}:host(:fullscreen) .video-container,:host(:-webkit-full-screen) .video-container,:host(:-moz-full-screen) .video-container{padding:0}.level-select{border-radius:var(--rtk-border-radius-sm, 4px);--tw-bg-opacity:1;background-color:rgba(var(--rtk-colors-brand-500, 33 96 253) / var(--tw-bg-opacity));color:rgb(var(--rtk-colors-text-on-brand-1000, var(--rtk-colors-text-1000, 255 255 255)));border:none;padding:5px 10px;font-size:14px;height:30px;cursor:pointer;border-radius:5px;margin-right:10px}.level-select:focus{outline:none}.level-select option{border-radius:var(--rtk-border-radius-sm, 4px);--tw-bg-opacity:1;background-color:rgba(var(--rtk-colors-brand-500, 33 96 253) / var(--tw-bg-opacity));color:rgb(var(--rtk-colors-text-on-brand-1000, var(--rtk-colors-text-1000, 255 255 255)))}.volume-control-holder{display:flex;justify-content:center;align-items:center}";
35675
+ const rtkLivestreamPlayerCss = ":host{line-height:initial;font-family:var(--rtk-font-family, sans-serif);font-feature-settings:normal;font-variation-settings:normal}p{margin:var(--rtk-space-0, 0px);padding:var(--rtk-space-0, 0px)}:host{display:flex;height:100%;max-height:100%;min-height:100%;width:100%;min-width:100%;max-width:100%;border-radius:var(--rtk-border-radius-md, 8px);--tw-bg-opacity:1;background-color:rgba(var(--rtk-colors-background-900, 26 26 26) / var(--tw-bg-opacity))}.player-container{position:relative;margin-left:var(--rtk-space-4, 16px);margin-right:var(--rtk-space-4, 16px);margin-top:var(--rtk-space-4, 16px);margin-bottom:var(--rtk-space-0, 0px);display:flex;flex-grow:1;align-items:center;justify-content:center;border-radius:var(--rtk-border-radius-md, 8px)}.loader{position:absolute;z-index:10;height:100%;width:100%;--tw-bg-opacity:1;background-color:rgba(var(--rtk-colors-background-900, 26 26 26) / var(--tw-bg-opacity));display:flex;flex-direction:column;align-items:center;justify-content:center}p{margin-top:var(--rtk-space-1, 4px);margin-bottom:var(--rtk-space-1, 4px);font-size:16px;color:rgb(var(--rtk-colors-text-700, 255 255 255 / 0.64))}.unmute-popup{position:absolute;z-index:30 !important;display:flex;width:var(--rtk-space-72, 288px);flex-direction:column;border-radius:var(--rtk-border-radius-md, 8px);--tw-bg-opacity:1;background-color:rgba(var(--rtk-colors-background-800, 30 30 30) / var(--tw-bg-opacity));padding:var(--rtk-space-4, 16px);text-align:center;max-width:70%}.unmute-popup h3{margin:var(--rtk-space-0, 0px);font-size:16px;font-weight:500}.unmute-popup p{margin-top:var(--rtk-space-3, 12px);margin-bottom:var(--rtk-space-3, 12px);font-size:14px}.control-bar{position:absolute;bottom:0;left:16px;right:16px;display:flex;height:auto;align-items:center;padding:12px 16px;z-index:30;--tw-bg-opacity:1;background-color:rgba(var(--rtk-colors-background-900, 26 26 26) / var(--tw-bg-opacity));border-radius:6px;border-radius:var(--rtk-border-radius-md, 8px);background-color:rgba(var(--rtk-colors-background-900, 26 26 26) / 0.8);gap:12px}.timings{color:rgb(var(--rtk-colors-text-on-brand-1000, var(--rtk-colors-text-1000, 255 255 255)))}.control-btn{border:none;margin-right:var(--rtk-space-2, 8px);border-radius:var(--rtk-border-radius-sm, 4px);--tw-bg-opacity:1;background-color:rgba(var(--rtk-colors-brand-500, 33 96 253) / var(--tw-bg-opacity));color:rgb(var(--rtk-colors-text-on-brand-1000, var(--rtk-colors-text-1000, 255 255 255)));cursor:pointer;font-size:24px;height:30px;width:30px}.fullscreen-btn{margin-right:20px;height:30px}.control-btn:hover{opacity:0.8}.control-btn:focus{outline:none}.control-groups{display:flex;align-items:center;gap:12px;flex-shrink:0}#livestream-video,.livestream-video{position:absolute;top:0;left:0;width:100% !important;height:100% !important;max-width:none !important;max-height:none !important;min-width:100% !important;min-height:100% !important;-o-object-fit:fill !important;object-fit:fill !important;z-index:10;border-radius:var(--rtk-border-radius-md, 8px);border-width:0px}:host:not(:fullscreen) .video-container{padding:16px 16px 0 16px}:host(:fullscreen) .video-container,:host(:-webkit-full-screen) .video-container,:host(:-moz-full-screen) .video-container{padding:0}.level-select{border-radius:var(--rtk-border-radius-sm, 4px);--tw-bg-opacity:1;background-color:rgba(var(--rtk-colors-brand-500, 33 96 253) / var(--tw-bg-opacity));color:rgb(var(--rtk-colors-text-on-brand-1000, var(--rtk-colors-text-1000, 255 255 255)));border:none;padding:5px 10px;font-size:14px;height:30px;cursor:pointer;border-radius:5px;margin-right:10px}.level-select:focus{outline:none}.level-select option{border-radius:var(--rtk-border-radius-sm, 4px);--tw-bg-opacity:1;background-color:rgba(var(--rtk-colors-brand-500, 33 96 253) / var(--tw-bg-opacity));color:rgb(var(--rtk-colors-text-on-brand-1000, var(--rtk-colors-text-1000, 255 255 255)))}.volume-control-holder{display:flex;justify-content:center;align-items:center}.seekbar-container{flex:1;min-width:100px;padding:0 12px;display:flex;align-items:center}.seekbar{width:100%;height:20px;cursor:pointer;display:flex;align-items:center}.seekbar-track{position:relative;width:100%;height:4px;border-radius:9999px;--tw-bg-opacity:1;background-color:rgba(var(--rtk-colors-background-600, 60 60 60) / var(--tw-bg-opacity))}.seekbar-progress{position:absolute;top:0;left:0;height:100%;border-radius:9999px;--tw-bg-opacity:1;background-color:rgba(var(--rtk-colors-brand-500, 33 96 253) / var(--tw-bg-opacity));transition:width 0.1s ease}.seekbar-handle{position:absolute;top:50%;width:12px;height:12px;border-radius:9999px;--tw-bg-opacity:1;background-color:rgba(var(--rtk-colors-brand-500, 33 96 253) / var(--tw-bg-opacity));transform:translate(-50%, -50%);transition:left 0.1s ease;opacity:0}.seekbar:hover .seekbar-handle{opacity:1}.seekbar:hover .seekbar-track{height:6px}.seekbar:hover .seekbar-handle{width:14px;height:14px}";
35676
35676
  const RtkLivestreamPlayerStyle0 = rtkLivestreamPlayerCss;
35677
35677
 
35678
35678
  var __decorate$1 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
@@ -35704,21 +35704,42 @@ const RtkLivestreamPlayer = class {
35704
35704
  this.currentTime = 0;
35705
35705
  this.duration = 0;
35706
35706
  this.hideControls = true;
35707
+ this.isDragging = false;
35708
+ this.seekPosition = 0;
35709
+ this.isSeeking = false;
35707
35710
  this.hideControlsTimeout = null;
35711
+ this.seekingTimeout = null;
35708
35712
  this.livestreamUpdateListener = (state) => {
35709
35713
  this.playbackUrl = this.meeting.livestream.playbackUrl;
35710
35714
  this.livestreamState = state;
35711
35715
  };
35712
35716
  this.updateProgress = () => {
35713
- this.currentTime = this.videoRef.currentTime;
35717
+ // During seeking, avoid updating currentTime to prevent fluctuations
35718
+ if (!this.isSeeking) {
35719
+ this.currentTime = this.videoRef.currentTime;
35720
+ }
35714
35721
  };
35715
35722
  this.updateHlsStatsPeriodically = () => {
35716
- var _a, _b;
35717
- // Total duration is where video is + the latency that is there
35718
- this.duration = (((_a = this.videoRef) === null || _a === void 0 ? void 0 : _a.currentTime) || 0) + (((_b = this.hls) === null || _b === void 0 ? void 0 : _b.latency) || 0);
35723
+ var _a, _b, _c;
35724
+ // Use HLS seekable ranges to get actual duration instead of currentTime + latency
35725
+ // This prevents duration from fluctuating when seeking
35726
+ if (((_a = this.videoRef) === null || _a === void 0 ? void 0 : _a.seekable) && this.videoRef.seekable.length > 0) {
35727
+ this.duration = this.videoRef.seekable.end(this.videoRef.seekable.length - 1);
35728
+ }
35729
+ else {
35730
+ // Fallback to currentTime + latency if seekable ranges aren't available
35731
+ this.duration = (((_b = this.videoRef) === null || _b === void 0 ? void 0 : _b.currentTime) || 0) + (((_c = this.hls) === null || _c === void 0 ? void 0 : _c.latency) || 0);
35732
+ }
35719
35733
  };
35720
35734
  this.fastForwardToLatest = () => {
35721
- this.videoRef.currentTime = this.duration - 1; // Move to the latest time
35735
+ var _a;
35736
+ // Use seekable range for more accurate live edge positioning
35737
+ if (((_a = this.videoRef) === null || _a === void 0 ? void 0 : _a.seekable) && this.videoRef.seekable.length > 0) {
35738
+ this.videoRef.currentTime = this.videoRef.seekable.end(this.videoRef.seekable.length - 1) - 1;
35739
+ }
35740
+ else {
35741
+ this.videoRef.currentTime = this.duration - 1; // Fallback
35742
+ }
35722
35743
  };
35723
35744
  this.togglePlay = () => {
35724
35745
  if (this.videoRef.paused) {
@@ -35751,6 +35772,74 @@ const RtkLivestreamPlayer = class {
35751
35772
  this.hideControls = true;
35752
35773
  }, 5000);
35753
35774
  };
35775
+ this.seekToPosition = (position) => {
35776
+ if (!this.videoRef)
35777
+ return;
35778
+ // Clamp position to valid range
35779
+ const clampedPosition = Math.max(0, Math.min(position, this.duration));
35780
+ // Set seeking state to prevent currentTime fluctuations
35781
+ this.isSeeking = true;
35782
+ // Update currentTime immediately for UI feedback
35783
+ this.currentTime = clampedPosition;
35784
+ try {
35785
+ this.videoRef.currentTime = clampedPosition;
35786
+ // Clear any existing timeout
35787
+ if (this.seekingTimeout) {
35788
+ clearTimeout(this.seekingTimeout);
35789
+ }
35790
+ // Reset seeking state after a short delay to allow video to stabilize
35791
+ this.seekingTimeout = setTimeout(() => {
35792
+ this.isSeeking = false;
35793
+ // Update currentTime one final time to ensure accuracy
35794
+ this.currentTime = this.videoRef.currentTime;
35795
+ }, 200);
35796
+ }
35797
+ catch (error) {
35798
+ this.isSeeking = false;
35799
+ this.meeting.__internals__.logger.warn('rtk-livestream-player:: Seek failed', { error });
35800
+ }
35801
+ };
35802
+ this.onSeekbarMouseDown = (event) => {
35803
+ event.preventDefault();
35804
+ this.isDragging = true;
35805
+ this.updateSeekPosition(event);
35806
+ document.addEventListener('mousemove', this.onSeekbarMouseMove);
35807
+ document.addEventListener('mouseup', this.onSeekbarMouseUp);
35808
+ };
35809
+ this.onSeekbarMouseMove = (event) => {
35810
+ if (!this.isDragging)
35811
+ return;
35812
+ this.updateSeekPosition(event);
35813
+ };
35814
+ this.onSeekbarMouseUp = (event) => {
35815
+ if (!this.isDragging)
35816
+ return;
35817
+ this.isDragging = false;
35818
+ this.updateSeekPosition(event);
35819
+ this.seekToPosition(this.seekPosition);
35820
+ document.removeEventListener('mousemove', this.onSeekbarMouseMove);
35821
+ document.removeEventListener('mouseup', this.onSeekbarMouseUp);
35822
+ };
35823
+ this.onSeekbarClick = (event) => {
35824
+ if (this.isDragging)
35825
+ return;
35826
+ this.updateSeekPosition(event);
35827
+ this.seekToPosition(this.seekPosition);
35828
+ };
35829
+ this.updateSeekPosition = (event) => {
35830
+ const seekbar = event.currentTarget;
35831
+ const rect = seekbar.getBoundingClientRect();
35832
+ const clickX = event.clientX - rect.left;
35833
+ const progress = Math.max(0, Math.min(1, clickX / rect.width));
35834
+ // Map progress to duration
35835
+ this.seekPosition = progress * this.duration;
35836
+ };
35837
+ this.getSeekbarProgress = () => {
35838
+ if (this.isDragging) {
35839
+ return this.duration > 0 ? this.seekPosition / this.duration : 0;
35840
+ }
35841
+ return this.duration > 0 ? this.currentTime / this.duration : 0;
35842
+ };
35754
35843
  this.getLoadingState = () => {
35755
35844
  let loadingMessage = '';
35756
35845
  let isLoading = false;
@@ -35820,7 +35909,7 @@ const RtkLivestreamPlayer = class {
35820
35909
  });
35821
35910
  window.rtk_hls = this.hls;
35822
35911
  this.meeting.__internals__.logger.info(`rtk-livestream-player:: Loading source`);
35823
- this.hls.loadSource(this.playbackUrl);
35912
+ this.hls.loadSource(this.playbackUrl + '?dvrEnabled=true');
35824
35913
  this.meeting.__internals__.logger.info(`rtk-livestream-player:: Attaching video element to HLS`);
35825
35914
  this.hls.attachMedia(this.videoRef);
35826
35915
  this.meeting.__internals__.logger.info(`rtk-livestream-player:: Waiting async for HLS manifest parsing`);
@@ -35838,7 +35927,7 @@ const RtkLivestreamPlayer = class {
35838
35927
  setTimeout(() => {
35839
35928
  if (this.playbackUrl && this.livestreamState === 'LIVESTREAMING') {
35840
35929
  this.meeting.__internals__.logger.info('rtk-livestream-player:: Retrying playbackUrl');
35841
- this.hls.loadSource(this.playbackUrl);
35930
+ this.hls.loadSource(this.playbackUrl + '?dvrEnabled=true');
35842
35931
  }
35843
35932
  }, 5000);
35844
35933
  return;
@@ -35931,6 +36020,9 @@ const RtkLivestreamPlayer = class {
35931
36020
  this.meeting.livestream.removeListener('livestreamUpdate', this.livestreamUpdateListener);
35932
36021
  this.videoRef.removeEventListener('timeupdate', this.updateProgress);
35933
36022
  clearInterval(this.statsIntervalTimer);
36023
+ if (this.seekingTimeout) {
36024
+ clearTimeout(this.seekingTimeout);
36025
+ }
35934
36026
  this.videoRef = null;
35935
36027
  if (this.hls) {
35936
36028
  this.hls.destroy();
@@ -35962,7 +36054,7 @@ const RtkLivestreamPlayer = class {
35962
36054
  // <!-- Control Bar -->
35963
36055
  index$1.h("div", { class: "control-bar" }, index$1.h("div", { class: "control-groups" }, index$1.h("rtk-icon", { id: "playPause", onClick: this.togglePlay, size: "lg", class: "control-btn", icon: this.playerState === uiStore.PlayerState.PLAYING
35964
36056
  ? this.iconPack.pause
35965
- : this.iconPack.play }), index$1.h("rtk-icon", { size: "lg", class: "control-btn", icon: this.iconPack.fastForward, onClick: this.fastForwardToLatest }), index$1.h("span", { class: "timings" }, formatSecondsToHHMMSS(this.currentTime), " /", ' ', formatSecondsToHHMMSS(this.duration))), index$1.h("div", { class: "control-groups" }, index$1.h("select", { class: "level-select", onChange: (e) => this.changeQuality(parseInt(e.target.value)) }, this.qualityLevels.map((level) => (index$1.h("option", { value: level.level, selected: this.selectedQuality === level.level }, level.resolution)))), index$1.h("rtk-fullscreen-toggle", { id: "fullscreen", class: "control-btn fullscreen-btn", targetElement: this.videoContainerRef, size: "sm", iconPack: this.iconPack, t: this.t, ref: (fullScreenToggle) => {
36057
+ : this.iconPack.play }), index$1.h("rtk-icon", { size: "lg", class: "control-btn", icon: this.iconPack.fastForward, onClick: this.fastForwardToLatest }), index$1.h("span", { class: "timings" }, formatSecondsToHHMMSS(this.currentTime), " /", ' ', formatSecondsToHHMMSS(this.duration))), index$1.h("div", { class: "seekbar-container" }, index$1.h("div", { class: "seekbar", onMouseDown: this.onSeekbarMouseDown, onClick: this.onSeekbarClick }, index$1.h("div", { class: "seekbar-track" }, index$1.h("div", { class: "seekbar-progress", style: { width: `${this.getSeekbarProgress() * 100}%` } }), index$1.h("div", { class: "seekbar-handle", style: { left: `${this.getSeekbarProgress() * 100}%` } })))), index$1.h("div", { class: "control-groups" }, index$1.h("select", { class: "level-select", onChange: (e) => this.changeQuality(parseInt(e.target.value)) }, this.qualityLevels.map((level) => (index$1.h("option", { value: level.level, selected: this.selectedQuality === level.level }, level.resolution)))), index$1.h("rtk-fullscreen-toggle", { id: "fullscreen", class: "control-btn fullscreen-btn", targetElement: this.videoContainerRef, size: "sm", iconPack: this.iconPack, t: this.t, ref: (fullScreenToggle) => {
35966
36058
  var _a;
35967
36059
  // Create a <style> element
35968
36060
  const style = document.createElement('style');
@@ -42,6 +42,14 @@ p {
42
42
  }
43
43
 
44
44
  ::slotted(*) {
45
- max-height: 100%;
45
+ /*
46
+ * In Safari,
47
+ * Adding max-h-full might break settings, audio_playback and troubleshooting modals.
48
+ * If you ever have to add max-h-full back,
49
+ * Make sure to test with Safari.
50
+ */
46
51
  max-width: 100%;
52
+ height: auto;
53
+ min-height: -moz-fit-content;
54
+ min-height: fit-content;
47
55
  }
@@ -94,11 +94,11 @@ p {
94
94
  right: 16px;
95
95
  display: flex;
96
96
  height: auto;
97
- justify-content: space-between;
98
97
  align-items: center;
99
98
  padding: 12px 16px;
100
99
  z-index: 30; /* Higher z-index to ensure it's above the video */ --tw-bg-opacity: 1; background-color: rgba(var(--rtk-colors-background-900, 26 26 26) / var(--tw-bg-opacity));
101
100
  border-radius: 6px; /* rounded-md fallback */ border-radius: var(--rtk-border-radius-md, 8px); background-color: rgba(var(--rtk-colors-background-900, 26 26 26) / 0.8);
101
+ gap: 12px;
102
102
  }
103
103
 
104
104
  .timings {
@@ -135,7 +135,8 @@ p {
135
135
  .control-groups{
136
136
  display: flex;
137
137
  align-items: center;
138
- justify-content: space-around;
138
+ gap: 12px;
139
+ flex-shrink: 0;
139
140
  }
140
141
 
141
142
  #livestream-video,
@@ -198,4 +199,67 @@ p {
198
199
  display: flex;
199
200
  justify-content: center;
200
201
  align-items: center;
202
+ }
203
+
204
+ /* Seekbar Styles */
205
+ .seekbar-container {
206
+ flex: 1;
207
+ min-width: 100px;
208
+ padding: 0 12px;
209
+ display: flex;
210
+ align-items: center;
211
+ }
212
+
213
+ .seekbar {
214
+ width: 100%;
215
+ height: 20px;
216
+ cursor: pointer;
217
+ display: flex;
218
+ align-items: center;
219
+ }
220
+
221
+ .seekbar-track {
222
+ position: relative;
223
+ width: 100%;
224
+ height: 4px;
225
+ border-radius: 9999px;
226
+ --tw-bg-opacity: 1;
227
+ background-color: rgba(var(--rtk-colors-background-600, 60 60 60) / var(--tw-bg-opacity));
228
+ }
229
+
230
+ .seekbar-progress {
231
+ position: absolute;
232
+ top: 0;
233
+ left: 0;
234
+ height: 100%;
235
+ border-radius: 9999px;
236
+ --tw-bg-opacity: 1;
237
+ background-color: rgba(var(--rtk-colors-brand-500, 33 96 253) / var(--tw-bg-opacity));
238
+ transition: width 0.1s ease;
239
+ }
240
+
241
+ .seekbar-handle {
242
+ position: absolute;
243
+ top: 50%;
244
+ width: 12px;
245
+ height: 12px;
246
+ border-radius: 9999px;
247
+ --tw-bg-opacity: 1;
248
+ background-color: rgba(var(--rtk-colors-brand-500, 33 96 253) / var(--tw-bg-opacity));
249
+ transform: translate(-50%, -50%);
250
+ transition: left 0.1s ease;
251
+ opacity: 0;
252
+ }
253
+
254
+ .seekbar:hover .seekbar-handle {
255
+ opacity: 1;
256
+ }
257
+
258
+ .seekbar:hover .seekbar-track {
259
+ height: 6px;
260
+ }
261
+
262
+ .seekbar:hover .seekbar-handle {
263
+ width: 14px;
264
+ height: 14px;
201
265
  }
@@ -32,21 +32,42 @@ export class RtkLivestreamPlayer {
32
32
  this.currentTime = 0;
33
33
  this.duration = 0;
34
34
  this.hideControls = true;
35
+ this.isDragging = false;
36
+ this.seekPosition = 0;
37
+ this.isSeeking = false;
35
38
  this.hideControlsTimeout = null;
39
+ this.seekingTimeout = null;
36
40
  this.livestreamUpdateListener = (state) => {
37
41
  this.playbackUrl = this.meeting.livestream.playbackUrl;
38
42
  this.livestreamState = state;
39
43
  };
40
44
  this.updateProgress = () => {
41
- this.currentTime = this.videoRef.currentTime;
45
+ // During seeking, avoid updating currentTime to prevent fluctuations
46
+ if (!this.isSeeking) {
47
+ this.currentTime = this.videoRef.currentTime;
48
+ }
42
49
  };
43
50
  this.updateHlsStatsPeriodically = () => {
44
- var _a, _b;
45
- // Total duration is where video is + the latency that is there
46
- this.duration = (((_a = this.videoRef) === null || _a === void 0 ? void 0 : _a.currentTime) || 0) + (((_b = this.hls) === null || _b === void 0 ? void 0 : _b.latency) || 0);
51
+ var _a, _b, _c;
52
+ // Use HLS seekable ranges to get actual duration instead of currentTime + latency
53
+ // This prevents duration from fluctuating when seeking
54
+ if (((_a = this.videoRef) === null || _a === void 0 ? void 0 : _a.seekable) && this.videoRef.seekable.length > 0) {
55
+ this.duration = this.videoRef.seekable.end(this.videoRef.seekable.length - 1);
56
+ }
57
+ else {
58
+ // Fallback to currentTime + latency if seekable ranges aren't available
59
+ this.duration = (((_b = this.videoRef) === null || _b === void 0 ? void 0 : _b.currentTime) || 0) + (((_c = this.hls) === null || _c === void 0 ? void 0 : _c.latency) || 0);
60
+ }
47
61
  };
48
62
  this.fastForwardToLatest = () => {
49
- this.videoRef.currentTime = this.duration - 1; // Move to the latest time
63
+ var _a;
64
+ // Use seekable range for more accurate live edge positioning
65
+ if (((_a = this.videoRef) === null || _a === void 0 ? void 0 : _a.seekable) && this.videoRef.seekable.length > 0) {
66
+ this.videoRef.currentTime = this.videoRef.seekable.end(this.videoRef.seekable.length - 1) - 1;
67
+ }
68
+ else {
69
+ this.videoRef.currentTime = this.duration - 1; // Fallback
70
+ }
50
71
  };
51
72
  this.togglePlay = () => {
52
73
  if (this.videoRef.paused) {
@@ -79,6 +100,74 @@ export class RtkLivestreamPlayer {
79
100
  this.hideControls = true;
80
101
  }, 5000);
81
102
  };
103
+ this.seekToPosition = (position) => {
104
+ if (!this.videoRef)
105
+ return;
106
+ // Clamp position to valid range
107
+ const clampedPosition = Math.max(0, Math.min(position, this.duration));
108
+ // Set seeking state to prevent currentTime fluctuations
109
+ this.isSeeking = true;
110
+ // Update currentTime immediately for UI feedback
111
+ this.currentTime = clampedPosition;
112
+ try {
113
+ this.videoRef.currentTime = clampedPosition;
114
+ // Clear any existing timeout
115
+ if (this.seekingTimeout) {
116
+ clearTimeout(this.seekingTimeout);
117
+ }
118
+ // Reset seeking state after a short delay to allow video to stabilize
119
+ this.seekingTimeout = setTimeout(() => {
120
+ this.isSeeking = false;
121
+ // Update currentTime one final time to ensure accuracy
122
+ this.currentTime = this.videoRef.currentTime;
123
+ }, 200);
124
+ }
125
+ catch (error) {
126
+ this.isSeeking = false;
127
+ this.meeting.__internals__.logger.warn('rtk-livestream-player:: Seek failed', { error });
128
+ }
129
+ };
130
+ this.onSeekbarMouseDown = (event) => {
131
+ event.preventDefault();
132
+ this.isDragging = true;
133
+ this.updateSeekPosition(event);
134
+ document.addEventListener('mousemove', this.onSeekbarMouseMove);
135
+ document.addEventListener('mouseup', this.onSeekbarMouseUp);
136
+ };
137
+ this.onSeekbarMouseMove = (event) => {
138
+ if (!this.isDragging)
139
+ return;
140
+ this.updateSeekPosition(event);
141
+ };
142
+ this.onSeekbarMouseUp = (event) => {
143
+ if (!this.isDragging)
144
+ return;
145
+ this.isDragging = false;
146
+ this.updateSeekPosition(event);
147
+ this.seekToPosition(this.seekPosition);
148
+ document.removeEventListener('mousemove', this.onSeekbarMouseMove);
149
+ document.removeEventListener('mouseup', this.onSeekbarMouseUp);
150
+ };
151
+ this.onSeekbarClick = (event) => {
152
+ if (this.isDragging)
153
+ return;
154
+ this.updateSeekPosition(event);
155
+ this.seekToPosition(this.seekPosition);
156
+ };
157
+ this.updateSeekPosition = (event) => {
158
+ const seekbar = event.currentTarget;
159
+ const rect = seekbar.getBoundingClientRect();
160
+ const clickX = event.clientX - rect.left;
161
+ const progress = Math.max(0, Math.min(1, clickX / rect.width));
162
+ // Map progress to duration
163
+ this.seekPosition = progress * this.duration;
164
+ };
165
+ this.getSeekbarProgress = () => {
166
+ if (this.isDragging) {
167
+ return this.duration > 0 ? this.seekPosition / this.duration : 0;
168
+ }
169
+ return this.duration > 0 ? this.currentTime / this.duration : 0;
170
+ };
82
171
  this.getLoadingState = () => {
83
172
  let loadingMessage = '';
84
173
  let isLoading = false;
@@ -148,7 +237,7 @@ export class RtkLivestreamPlayer {
148
237
  });
149
238
  window.rtk_hls = this.hls;
150
239
  this.meeting.__internals__.logger.info(`rtk-livestream-player:: Loading source`);
151
- this.hls.loadSource(this.playbackUrl);
240
+ this.hls.loadSource(this.playbackUrl + '?dvrEnabled=true');
152
241
  this.meeting.__internals__.logger.info(`rtk-livestream-player:: Attaching video element to HLS`);
153
242
  this.hls.attachMedia(this.videoRef);
154
243
  this.meeting.__internals__.logger.info(`rtk-livestream-player:: Waiting async for HLS manifest parsing`);
@@ -166,7 +255,7 @@ export class RtkLivestreamPlayer {
166
255
  setTimeout(() => {
167
256
  if (this.playbackUrl && this.livestreamState === 'LIVESTREAMING') {
168
257
  this.meeting.__internals__.logger.info('rtk-livestream-player:: Retrying playbackUrl');
169
- this.hls.loadSource(this.playbackUrl);
258
+ this.hls.loadSource(this.playbackUrl + '?dvrEnabled=true');
170
259
  }
171
260
  }, 5000);
172
261
  return;
@@ -259,6 +348,9 @@ export class RtkLivestreamPlayer {
259
348
  this.meeting.livestream.removeListener('livestreamUpdate', this.livestreamUpdateListener);
260
349
  this.videoRef.removeEventListener('timeupdate', this.updateProgress);
261
350
  clearInterval(this.statsIntervalTimer);
351
+ if (this.seekingTimeout) {
352
+ clearTimeout(this.seekingTimeout);
353
+ }
262
354
  this.videoRef = null;
263
355
  if (this.hls) {
264
356
  this.hls.destroy();
@@ -290,7 +382,7 @@ export class RtkLivestreamPlayer {
290
382
  // <!-- Control Bar -->
291
383
  h("div", { class: "control-bar" }, h("div", { class: "control-groups" }, h("rtk-icon", { id: "playPause", onClick: this.togglePlay, size: "lg", class: "control-btn", icon: this.playerState === PlayerState.PLAYING
292
384
  ? this.iconPack.pause
293
- : this.iconPack.play }), h("rtk-icon", { size: "lg", class: "control-btn", icon: this.iconPack.fastForward, onClick: this.fastForwardToLatest }), h("span", { class: "timings" }, formatSecondsToHHMMSS(this.currentTime), " /", ' ', formatSecondsToHHMMSS(this.duration))), h("div", { class: "control-groups" }, h("select", { class: "level-select", onChange: (e) => this.changeQuality(parseInt(e.target.value)) }, this.qualityLevels.map((level) => (h("option", { value: level.level, selected: this.selectedQuality === level.level }, level.resolution)))), h("rtk-fullscreen-toggle", { id: "fullscreen", class: "control-btn fullscreen-btn", targetElement: this.videoContainerRef, size: "sm", iconPack: this.iconPack, t: this.t, ref: (fullScreenToggle) => {
385
+ : this.iconPack.play }), h("rtk-icon", { size: "lg", class: "control-btn", icon: this.iconPack.fastForward, onClick: this.fastForwardToLatest }), h("span", { class: "timings" }, formatSecondsToHHMMSS(this.currentTime), " /", ' ', formatSecondsToHHMMSS(this.duration))), h("div", { class: "seekbar-container" }, h("div", { class: "seekbar", onMouseDown: this.onSeekbarMouseDown, onClick: this.onSeekbarClick }, h("div", { class: "seekbar-track" }, h("div", { class: "seekbar-progress", style: { width: `${this.getSeekbarProgress() * 100}%` } }), h("div", { class: "seekbar-handle", style: { left: `${this.getSeekbarProgress() * 100}%` } })))), h("div", { class: "control-groups" }, h("select", { class: "level-select", onChange: (e) => this.changeQuality(parseInt(e.target.value)) }, this.qualityLevels.map((level) => (h("option", { value: level.level, selected: this.selectedQuality === level.level }, level.resolution)))), h("rtk-fullscreen-toggle", { id: "fullscreen", class: "control-btn fullscreen-btn", targetElement: this.videoContainerRef, size: "sm", iconPack: this.iconPack, t: this.t, ref: (fullScreenToggle) => {
294
386
  var _a;
295
387
  // Create a <style> element
296
388
  const style = document.createElement('style');
@@ -438,7 +530,10 @@ export class RtkLivestreamPlayer {
438
530
  "selectedQuality": {},
439
531
  "currentTime": {},
440
532
  "duration": {},
441
- "hideControls": {}
533
+ "hideControls": {},
534
+ "isDragging": {},
535
+ "seekPosition": {},
536
+ "isSeeking": {}
442
537
  };
443
538
  }
444
539
  static get events() {