@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.
- package/dist/browser.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/realtimekit-ui.cjs.js +1 -1
- package/dist/cjs/rtk-avatar_54.cjs.entry.js +1 -1
- package/dist/cjs/rtk-livestream-indicator_3.cjs.entry.js +101 -9
- package/dist/collection/components/rtk-dialog/rtk-dialog.css +9 -1
- package/dist/collection/components/rtk-livestream-player/rtk-livestream-player.css +66 -2
- package/dist/collection/components/rtk-livestream-player/rtk-livestream-player.js +104 -9
- package/dist/components/{p-734d61ed.js → p-1745c048.js} +105 -10
- package/dist/components/{p-aaaa2ffd.js → p-1d624be9.js} +3 -3
- package/dist/components/{p-750d5890.js → p-7960f355.js} +1 -1
- package/dist/components/{p-72ca9fd3.js → p-8f513cbb.js} +1 -1
- package/dist/components/{p-431bde35.js → p-cb12ef1a.js} +1 -1
- package/dist/components/rtk-channel-header.js +1 -1
- package/dist/components/rtk-chat.js +1 -1
- package/dist/components/rtk-dialog-manager.js +1 -1
- package/dist/components/rtk-dialog.js +1 -1
- package/dist/components/rtk-grid.js +1 -1
- package/dist/components/rtk-livestream-player.js +1 -1
- package/dist/components/rtk-meeting.js +4 -4
- package/dist/components/rtk-participants-audio.js +1 -1
- package/dist/docs/docs-components.json +1 -1
- package/dist/esm/loader.js +103 -11
- package/dist/esm/realtimekit-ui.js +1 -1
- package/dist/esm/rtk-avatar_54.entry.js +1 -1
- package/dist/esm/rtk-livestream-indicator_3.entry.js +101 -9
- package/dist/realtimekit-ui/{p-65830fdd.entry.js → p-31af32f0.entry.js} +1 -1
- package/dist/realtimekit-ui/{p-066ac543.entry.js → p-617e5efa.entry.js} +1 -1
- package/dist/realtimekit-ui/realtimekit-ui.esm.js +1 -1
- package/dist/types/components/rtk-livestream-player/rtk-livestream-player.d.ts +11 -0
- 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;
|
|
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
|
-
|
|
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
|
-
//
|
|
35718
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
46
|
-
|
|
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
|
-
|
|
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() {
|