@cloudflare/realtimekit-ui 1.0.4-staging.5 → 1.0.5-staging.1

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.
@@ -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');
@@ -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() {
@@ -35616,7 +35616,7 @@ function formatSecondsToHHMMSS(seconds) {
35616
35616
  : `${mins}:${secs.toString().padStart(2, '0')}`;
35617
35617
  }
35618
35618
 
35619
- 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}";
35619
+ 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}";
35620
35620
  const RtkLivestreamPlayerStyle0 = rtkLivestreamPlayerCss;
35621
35621
 
35622
35622
  var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
@@ -35650,21 +35650,42 @@ const RtkLivestreamPlayer = /*@__PURE__*/ proxyCustomElement(class RtkLivestream
35650
35650
  this.currentTime = 0;
35651
35651
  this.duration = 0;
35652
35652
  this.hideControls = true;
35653
+ this.isDragging = false;
35654
+ this.seekPosition = 0;
35655
+ this.isSeeking = false;
35653
35656
  this.hideControlsTimeout = null;
35657
+ this.seekingTimeout = null;
35654
35658
  this.livestreamUpdateListener = (state) => {
35655
35659
  this.playbackUrl = this.meeting.livestream.playbackUrl;
35656
35660
  this.livestreamState = state;
35657
35661
  };
35658
35662
  this.updateProgress = () => {
35659
- this.currentTime = this.videoRef.currentTime;
35663
+ // During seeking, avoid updating currentTime to prevent fluctuations
35664
+ if (!this.isSeeking) {
35665
+ this.currentTime = this.videoRef.currentTime;
35666
+ }
35660
35667
  };
35661
35668
  this.updateHlsStatsPeriodically = () => {
35662
- var _a, _b;
35663
- // Total duration is where video is + the latency that is there
35664
- 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);
35669
+ var _a, _b, _c;
35670
+ // Use HLS seekable ranges to get actual duration instead of currentTime + latency
35671
+ // This prevents duration from fluctuating when seeking
35672
+ if (((_a = this.videoRef) === null || _a === void 0 ? void 0 : _a.seekable) && this.videoRef.seekable.length > 0) {
35673
+ this.duration = this.videoRef.seekable.end(this.videoRef.seekable.length - 1);
35674
+ }
35675
+ else {
35676
+ // Fallback to currentTime + latency if seekable ranges aren't available
35677
+ 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);
35678
+ }
35665
35679
  };
35666
35680
  this.fastForwardToLatest = () => {
35667
- this.videoRef.currentTime = this.duration - 1; // Move to the latest time
35681
+ var _a;
35682
+ // Use seekable range for more accurate live edge positioning
35683
+ if (((_a = this.videoRef) === null || _a === void 0 ? void 0 : _a.seekable) && this.videoRef.seekable.length > 0) {
35684
+ this.videoRef.currentTime = this.videoRef.seekable.end(this.videoRef.seekable.length - 1) - 1;
35685
+ }
35686
+ else {
35687
+ this.videoRef.currentTime = this.duration - 1; // Fallback
35688
+ }
35668
35689
  };
35669
35690
  this.togglePlay = () => {
35670
35691
  if (this.videoRef.paused) {
@@ -35697,6 +35718,74 @@ const RtkLivestreamPlayer = /*@__PURE__*/ proxyCustomElement(class RtkLivestream
35697
35718
  this.hideControls = true;
35698
35719
  }, 5000);
35699
35720
  };
35721
+ this.seekToPosition = (position) => {
35722
+ if (!this.videoRef)
35723
+ return;
35724
+ // Clamp position to valid range
35725
+ const clampedPosition = Math.max(0, Math.min(position, this.duration));
35726
+ // Set seeking state to prevent currentTime fluctuations
35727
+ this.isSeeking = true;
35728
+ // Update currentTime immediately for UI feedback
35729
+ this.currentTime = clampedPosition;
35730
+ try {
35731
+ this.videoRef.currentTime = clampedPosition;
35732
+ // Clear any existing timeout
35733
+ if (this.seekingTimeout) {
35734
+ clearTimeout(this.seekingTimeout);
35735
+ }
35736
+ // Reset seeking state after a short delay to allow video to stabilize
35737
+ this.seekingTimeout = setTimeout(() => {
35738
+ this.isSeeking = false;
35739
+ // Update currentTime one final time to ensure accuracy
35740
+ this.currentTime = this.videoRef.currentTime;
35741
+ }, 200);
35742
+ }
35743
+ catch (error) {
35744
+ this.isSeeking = false;
35745
+ this.meeting.__internals__.logger.warn('rtk-livestream-player:: Seek failed', { error });
35746
+ }
35747
+ };
35748
+ this.onSeekbarMouseDown = (event) => {
35749
+ event.preventDefault();
35750
+ this.isDragging = true;
35751
+ this.updateSeekPosition(event);
35752
+ document.addEventListener('mousemove', this.onSeekbarMouseMove);
35753
+ document.addEventListener('mouseup', this.onSeekbarMouseUp);
35754
+ };
35755
+ this.onSeekbarMouseMove = (event) => {
35756
+ if (!this.isDragging)
35757
+ return;
35758
+ this.updateSeekPosition(event);
35759
+ };
35760
+ this.onSeekbarMouseUp = (event) => {
35761
+ if (!this.isDragging)
35762
+ return;
35763
+ this.isDragging = false;
35764
+ this.updateSeekPosition(event);
35765
+ this.seekToPosition(this.seekPosition);
35766
+ document.removeEventListener('mousemove', this.onSeekbarMouseMove);
35767
+ document.removeEventListener('mouseup', this.onSeekbarMouseUp);
35768
+ };
35769
+ this.onSeekbarClick = (event) => {
35770
+ if (this.isDragging)
35771
+ return;
35772
+ this.updateSeekPosition(event);
35773
+ this.seekToPosition(this.seekPosition);
35774
+ };
35775
+ this.updateSeekPosition = (event) => {
35776
+ const seekbar = event.currentTarget;
35777
+ const rect = seekbar.getBoundingClientRect();
35778
+ const clickX = event.clientX - rect.left;
35779
+ const progress = Math.max(0, Math.min(1, clickX / rect.width));
35780
+ // Map progress to duration
35781
+ this.seekPosition = progress * this.duration;
35782
+ };
35783
+ this.getSeekbarProgress = () => {
35784
+ if (this.isDragging) {
35785
+ return this.duration > 0 ? this.seekPosition / this.duration : 0;
35786
+ }
35787
+ return this.duration > 0 ? this.currentTime / this.duration : 0;
35788
+ };
35700
35789
  this.getLoadingState = () => {
35701
35790
  let loadingMessage = '';
35702
35791
  let isLoading = false;
@@ -35766,7 +35855,7 @@ const RtkLivestreamPlayer = /*@__PURE__*/ proxyCustomElement(class RtkLivestream
35766
35855
  });
35767
35856
  window.rtk_hls = this.hls;
35768
35857
  this.meeting.__internals__.logger.info(`rtk-livestream-player:: Loading source`);
35769
- this.hls.loadSource(this.playbackUrl);
35858
+ this.hls.loadSource(this.playbackUrl + '?dvrEnabled=true');
35770
35859
  this.meeting.__internals__.logger.info(`rtk-livestream-player:: Attaching video element to HLS`);
35771
35860
  this.hls.attachMedia(this.videoRef);
35772
35861
  this.meeting.__internals__.logger.info(`rtk-livestream-player:: Waiting async for HLS manifest parsing`);
@@ -35784,7 +35873,7 @@ const RtkLivestreamPlayer = /*@__PURE__*/ proxyCustomElement(class RtkLivestream
35784
35873
  setTimeout(() => {
35785
35874
  if (this.playbackUrl && this.livestreamState === 'LIVESTREAMING') {
35786
35875
  this.meeting.__internals__.logger.info('rtk-livestream-player:: Retrying playbackUrl');
35787
- this.hls.loadSource(this.playbackUrl);
35876
+ this.hls.loadSource(this.playbackUrl + '?dvrEnabled=true');
35788
35877
  }
35789
35878
  }, 5000);
35790
35879
  return;
@@ -35877,6 +35966,9 @@ const RtkLivestreamPlayer = /*@__PURE__*/ proxyCustomElement(class RtkLivestream
35877
35966
  this.meeting.livestream.removeListener('livestreamUpdate', this.livestreamUpdateListener);
35878
35967
  this.videoRef.removeEventListener('timeupdate', this.updateProgress);
35879
35968
  clearInterval(this.statsIntervalTimer);
35969
+ if (this.seekingTimeout) {
35970
+ clearTimeout(this.seekingTimeout);
35971
+ }
35880
35972
  this.videoRef = null;
35881
35973
  if (this.hls) {
35882
35974
  this.hls.destroy();
@@ -35908,7 +36000,7 @@ const RtkLivestreamPlayer = /*@__PURE__*/ proxyCustomElement(class RtkLivestream
35908
36000
  // <!-- Control Bar -->
35909
36001
  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
35910
36002
  ? this.iconPack.pause
35911
- : 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) => {
36003
+ : 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) => {
35912
36004
  var _a;
35913
36005
  // Create a <style> element
35914
36006
  const style = document.createElement('style');
@@ -35953,7 +36045,10 @@ const RtkLivestreamPlayer = /*@__PURE__*/ proxyCustomElement(class RtkLivestream
35953
36045
  "selectedQuality": [32],
35954
36046
  "currentTime": [32],
35955
36047
  "duration": [32],
35956
- "hideControls": [32]
36048
+ "hideControls": [32],
36049
+ "isDragging": [32],
36050
+ "seekPosition": [32],
36051
+ "isSeeking": [32]
35957
36052
  }, undefined, {
35958
36053
  "livestreamState": ["updateLivestreamId"],
35959
36054
  "meeting": ["meetingChanged"]