aboutsummaryrefslogtreecommitdiff
path: root/static/js/libs/videoPlayer.js
diff options
context:
space:
mode:
authorBobby <[email protected]>2024-12-24 06:50:59 -0500
committerBobby <[email protected]>2024-12-24 06:50:59 -0500
commit6df9f0dc40501e8f55bcc883dfe5be65e60d3c3d (patch)
tree273148d564c11ae46e9f96c0231ce57427f5591f /static/js/libs/videoPlayer.js
parent4feba2452a151ed999d52d4a0d53b0b0584bf70e (diff)
downloadthatcomputerscientist-6df9f0dc40501e8f55bcc883dfe5be65e60d3c3d.tar.xz
thatcomputerscientist-6df9f0dc40501e8f55bcc883dfe5be65e60d3c3d.zip
bucket load of things
Diffstat (limited to 'static/js/libs/videoPlayer.js')
-rw-r--r--static/js/libs/videoPlayer.js159
1 files changed, 147 insertions, 12 deletions
diff --git a/static/js/libs/videoPlayer.js b/static/js/libs/videoPlayer.js
index 30febb29..076d2aed 100644
--- a/static/js/libs/videoPlayer.js
+++ b/static/js/libs/videoPlayer.js
@@ -81,6 +81,11 @@ class VideoPlayer {
}
};
+ static STORAGE_KEYS = {
+ VOLUME: 'videoplayer_volume',
+ QUALITY: 'videoplayer_quality'
+ };
+
constructor(config = {}) {
this.config = this.mergeConfig(VideoPlayer.defaultConfig, config);
this.initializeElements();
@@ -89,6 +94,8 @@ class VideoPlayer {
this.setupSubtitles();
this.setupFullscreenHandling();
this.setupVideoInteractions();
+ this.setupBufferingIndicator();
+ this.loadVolume();
if (this.config.keyboard.enabled) {
this.setupKeyboardControls();
@@ -176,6 +183,7 @@ class VideoPlayer {
setupSource() {
const { url, type } = this.config.source;
+ if (!url) return;
if (type === 'hls') {
if (!Hls.isSupported()) return;
@@ -237,7 +245,9 @@ class VideoPlayer {
setupMenuEvents() {
const closeMenus = (except) => {
Object.entries(this.elements.menus).forEach(([key, menu]) => {
- if (key !== except) menu.classList.remove('show');
+ try {
+ if (key !== except) menu.classList.remove('show');
+ } catch (e) { }
});
};
@@ -261,12 +271,135 @@ class VideoPlayer {
// Close menus when clicking outside
document.addEventListener('click', (e) => {
- if (!e.target.closest('.quality-control')) this.elements.menus.quality.classList.remove('show');
- if (!e.target.closest('.sub-dub-control')) this.elements.menus.subDub.classList.remove('show');
- if (!e.target.closest('#ccBtn')) this.elements.menus.caption.classList.remove('show');
+ try {
+ if (!e.target.closest('.quality-control')) this.elements.menus.quality.classList.remove('show');
+ } catch (e) { }
+
+ try {
+ if (!e.target.closest('.sub-dub-control')) this.elements.menus.subDub.classList.remove('show');
+ } catch (e) { }
+
+ try {
+ if (!e.target.closest('#ccBtn')) this.elements.menus.caption.classList.remove('show');
+ } catch (e) { }
});
}
+ saveVolume(volume) {
+ try {
+ localStorage.setItem(VideoPlayer.STORAGE_KEYS.VOLUME, volume);
+ } catch (e) { }
+ }
+
+ loadVolume() {
+ try {
+ const savedVolume = localStorage.getItem(VideoPlayer.STORAGE_KEYS.VOLUME);
+ if (savedVolume !== null) {
+ this.updateVolume(parseFloat(savedVolume));
+ }
+ } catch (e) { }
+ }
+
+ saveQuality(quality) {
+ try {
+ localStorage.setItem(VideoPlayer.STORAGE_KEYS.QUALITY, quality);
+ } catch (e) { }
+ }
+
+ loadQuality() {
+ try {
+ const savedQuality = localStorage.getItem(VideoPlayer.STORAGE_KEYS.QUALITY);
+ if (savedQuality !== null && this.hls) {
+ this.hls.currentLevel = parseInt(savedQuality);
+ // Update quality button text
+ const qualityButton = this.elements.controls.quality;
+ const qualityText = savedQuality === '-1' ? 'Auto' :
+ `${this.hls.levels[savedQuality].height}p`;
+ qualityButton.innerHTML = this.getQualityButtonHTML(qualityText);
+ }
+ } catch (e) { }
+ }
+
+ getQualityButtonHTML(text) {
+ return `<svg class="win98-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke="currentColor">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
+ </svg>
+ <svg class="win98-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke="currentColor">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 15 12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9" />
+ </svg>${text}`;
+ }
+
+ setupBufferingIndicator() {
+ const playerContainer = this.elements.video.closest('.win98-player-content');
+ let loadingTimeout;
+
+ const isBuffering = () => {
+ const video = this.elements.video;
+
+ // If seeking or initial load
+ if (video.seeking || video.readyState < 3) return true;
+
+ // If playing but not enough data
+ if (!video.paused && video.currentTime > 0) {
+ // Check if we have data for the current position
+ for (let i = 0; i < video.buffered.length; i++) {
+ if (video.currentTime >= video.buffered.start(i) &&
+ video.currentTime <= video.buffered.end(i)) {
+ // Check if we have enough buffer ahead
+ const aheadBuffer = video.buffered.end(i) - video.currentTime;
+ if (aheadBuffer < 0.5) return true; // Less than 0.5 seconds ahead
+ return false;
+ }
+ }
+ return true; // Current time not in any buffer range
+ }
+ return false;
+ };
+
+ const showLoading = () => {
+ if (loadingTimeout) clearTimeout(loadingTimeout);
+ loadingTimeout = setTimeout(() => {
+ if (isBuffering()) {
+ playerContainer.classList.add('video-loading');
+ }
+ }, 100);
+ };
+
+ const hideLoading = () => {
+ if (loadingTimeout) clearTimeout(loadingTimeout);
+ loadingTimeout = setTimeout(() => {
+ if (!isBuffering()) {
+ playerContainer.classList.remove('video-loading');
+ }
+ }, 100);
+ };
+
+ // Video events
+ this.elements.video.addEventListener('waiting', showLoading);
+ this.elements.video.addEventListener('canplay', hideLoading);
+ this.elements.video.addEventListener('playing', hideLoading);
+ this.elements.video.addEventListener('progress', showLoading); // Check on data load
+ this.elements.video.addEventListener('timeupdate', () => {
+ if (isBuffering()) showLoading();
+ else hideLoading();
+ });
+ this.elements.video.addEventListener('seeked', hideLoading);
+ this.elements.video.addEventListener('stalled', showLoading);
+
+ // HLS specific events
+ if (this.hls) {
+ this.hls.on(Hls.Events.FRAG_LOADING, showLoading);
+ this.hls.on(Hls.Events.FRAG_BUFFERED, hideLoading);
+ this.hls.on(Hls.Events.ERROR, showLoading);
+ }
+
+ // Initial loading state
+ if (!this.elements.video.readyState || this.elements.video.readyState < 3) {
+ playerContainer.classList.add('video-loading');
+ }
+ }
+
setupQualityMenu(levels) {
this.elements.menus.quality.innerHTML = `
<button data-quality="-1">Auto</button>
@@ -279,16 +412,14 @@ class VideoPlayer {
if (e.target.tagName === 'BUTTON') {
const quality = parseInt(e.target.dataset.quality);
this.hls.currentLevel = quality;
- this.elements.controls.quality.innerHTML = `<svg class="win98-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke="currentColor">
- <path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
- <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
- </svg>
- <svg class="win98-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke="currentColor">
- <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 15 12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9" />
- </svg>${e.target.textContent}`;
+ this.elements.controls.quality.innerHTML = this.getQualityButtonHTML(e.target.textContent);
this.elements.menus.quality.classList.remove('show');
+ this.saveQuality(quality); // Save quality setting
}
});
+
+ // Load saved quality after menu setup
+ this.loadQuality();
}
setupSubtitles() {
@@ -536,7 +667,10 @@ class VideoPlayer {
updateTimeDisplay() {
const progress = (this.elements.video.currentTime / this.elements.video.duration) * 100;
- this.elements.displays.timeCurrent.textContent = this.formatTime(this.elements.video.currentTime);
+ // If there is a single digit in minutes, add a leading zero
+ const currentTime = this.formatTime(this.elements.video.currentTime);
+ const singleDigitMinutes = currentTime.length === 4 && currentTime[1] === ':';
+ this.elements.displays.timeCurrent.textContent = singleDigitMinutes ? `0${currentTime}` : currentTime;
this.updateSeekDisplay(progress);
}
@@ -559,6 +693,7 @@ class VideoPlayer {
this.updateVolumeSliders(volume);
this.updateMuteButton(volume > 0);
if (this.elements.video.muted && volume > 0) this.elements.video.muted = false;
+ this.saveVolume(volume); // Save volume setting
}
updateVolumeSliders(volume) {