diff options
| author | Bobby <[email protected]> | 2024-12-24 06:50:59 -0500 |
|---|---|---|
| committer | Bobby <[email protected]> | 2024-12-24 06:50:59 -0500 |
| commit | 6df9f0dc40501e8f55bcc883dfe5be65e60d3c3d (patch) | |
| tree | 273148d564c11ae46e9f96c0231ce57427f5591f /static | |
| parent | 4feba2452a151ed999d52d4a0d53b0b0584bf70e (diff) | |
| download | thatcomputerscientist-6df9f0dc40501e8f55bcc883dfe5be65e60d3c3d.tar.xz thatcomputerscientist-6df9f0dc40501e8f55bcc883dfe5be65e60d3c3d.zip | |
bucket load of things
Diffstat (limited to 'static')
| -rw-r--r-- | static/css/anime/video_player.css | 32 | ||||
| -rw-r--r-- | static/css/shared/directory.css | 197 | ||||
| -rw-r--r-- | static/images/core/gifs/buffering.gif | bin | 0 -> 7054 bytes | |||
| -rw-r--r-- | static/images/core/icons/folder.png | bin | 0 -> 481 bytes | |||
| -rw-r--r-- | static/images/core/icons/music.png | bin | 0 -> 684 bytes | |||
| -rw-r--r-- | static/js/libs/videoPlayer.js | 159 | ||||
| -rw-r--r-- | static/js/shared/directory.js | 58 |
7 files changed, 434 insertions, 12 deletions
diff --git a/static/css/anime/video_player.css b/static/css/anime/video_player.css index 39abb527..e4b2f0b7 100644 --- a/static/css/anime/video_player.css +++ b/static/css/anime/video_player.css @@ -39,6 +39,17 @@ box-shadow: none; } +.win98-player:fullscreen .retro-buffer { + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.win98-player:fullscreen .retro-buffer img { + height: 64px; + width: 64px; +} + .win98-player:fullscreen .win98-title-bar, .win98-player:fullscreen .episode-title { display: none; @@ -287,6 +298,8 @@ input[type="range"].volume-slider { min-width: 100px; margin-bottom: 4px; z-index: 10; + max-height: 180px; + overflow: scroll; } .quality-menu.show, @@ -412,4 +425,23 @@ video::-webkit-media-text-track-container { video::-webkit-media-text-track { display: none !important; +} + +/* Retro Buffer Styles */ +.retro-buffer { + position: absolute; + top: 246px; + left: 50%; + transform: translateX(-50%); + z-index: 5; + display: none; +} + +.retro-buffer img { + height: 32px; + width: 32px; +} + +.video-loading .retro-buffer { + display: block; }
\ No newline at end of file diff --git a/static/css/shared/directory.css b/static/css/shared/directory.css new file mode 100644 index 00000000..94941560 --- /dev/null +++ b/static/css/shared/directory.css @@ -0,0 +1,197 @@ +.directory-viewer { + position: relative; + user-select: none; + padding: 4px; + height: 100%; +} + +.directory-content { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); + gap: 8px; + padding: 4px; +} + +.folder-item { + display: flex; + flex-direction: column; + align-items: center; + padding: 4px; + cursor: default; + position: relative; +} + +.folder-icon { + width: 32px; + height: 32px; + position: relative; +} + +.folder-icon img { + width: 100%; + height: 100%; + display: block; +} + +.selection-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: transparent; + mix-blend-mode: multiply; + pointer-events: none; +} + +.folder-item.selected .selection-overlay { + background: rgba(0, 0, 255, 0.5); +} + +.folder-name { + margin-top: 4px; + font-size: 11px; + text-align: center; + max-width: 76px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.folder-item.selected .folder-name { + color: #fff; + background: #000080; +} + +/* Selection rectangle */ +.selection-rectangle { + position: absolute; + border: 1px dotted #000; + background: rgba(0, 0, 255, 0.1); + pointer-events: none; +} + +/* Context Menu */ +.context-menu { + display: none; + position: absolute; + background: #C0C0C0; + border: 2px solid; + border-color: #FFFFFF #808080 #808080 #FFFFFF; + padding: 2px; + font-size: 11px; + box-shadow: 1px 1px 0 0 #000; + min-width: 160px; + z-index: 1000; + color: #000; +} + +.context-menu.show { + display: block; +} + +.menu-item { + padding: 3px 20px; + cursor: default; +} + +.menu-item:hover { + background: #000080; + color: #FFF; +} + +.menu-separator { + height: 1px; + background: #808080; + margin: 3px 2px; + border-bottom: 1px solid #FFFFFF; +} + +.windows98-modal { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: #c0c0c0; + border: 2px solid; + border-color: #ffffff #808080 #808080 #ffffff; + box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3); + z-index: 1000; + min-width: 300px; +} + +.windows98-modal .modal-titlebar { + background-color: navy; + color: white; + padding: 2px 4px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.windows98-modal .modal-title { + font-weight: bold; +} + +.windows98-modal .modal-close { + background: none; + border: none; + color: white; + font-weight: bold; + cursor: pointer; +} + +.windows98-modal .modal-content { + padding: 20px; + background-color: #c0c0c0; +} + +.windows98-modal .modal-buttons { + background-color: #c0c0c0; + padding: 10px; + display: flex; + justify-content: flex-end; + gap: 10px; + border-top: 1px solid #808080; +} + +.windows98-modal button { + min-width: 75px; + padding: 4px 8px; + background-color: #c0c0c0; + border: 2px solid; + border-color: #ffffff #808080 #808080 #ffffff; + cursor: pointer; +} + +.windows98-modal button:active { + border-color: #808080 #ffffff #ffffff #808080; +} + +.windows98-modal .file-upload-form, +.windows98-modal .folder-creation-form { + display: flex; + flex-direction: column; + gap: 10px; +} + +.windows98-modal .upload-progress { + background-color: white; + border: 1px solid #808080; + height: 20px; + margin-top: 10px; +} + +.windows98-modal .progress-bar { + background-color: navy; + height: 100%; + width: 0; + transition: width 0.3s ease; +} + +.windows98-modal .progress-text { + position: absolute; + left: 50%; + transform: translateX(-50%); + color: navy; +}
\ No newline at end of file diff --git a/static/images/core/gifs/buffering.gif b/static/images/core/gifs/buffering.gif Binary files differnew file mode 100644 index 00000000..524e91f9 --- /dev/null +++ b/static/images/core/gifs/buffering.gif diff --git a/static/images/core/icons/folder.png b/static/images/core/icons/folder.png Binary files differnew file mode 100644 index 00000000..807f1690 --- /dev/null +++ b/static/images/core/icons/folder.png diff --git a/static/images/core/icons/music.png b/static/images/core/icons/music.png Binary files differnew file mode 100644 index 00000000..68bb414d --- /dev/null +++ b/static/images/core/icons/music.png 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) { diff --git a/static/js/shared/directory.js b/static/js/shared/directory.js new file mode 100644 index 00000000..c3153175 --- /dev/null +++ b/static/js/shared/directory.js @@ -0,0 +1,58 @@ +class DirectoryViewer { + constructor(element) { + this.element = element; + this.folders = element.querySelectorAll('.folder-item'); + this.selectedFolder = null; + + this.setupEventListeners(); + } + + setupEventListeners() { + this.folders.forEach(folder => { + folder.addEventListener('click', (e) => this.handleFolderClick(e, folder)); + folder.addEventListener('dblclick', (e) => this.handleFolderDoubleClick(e, folder)); + }); + + // Clear selection when clicking empty space + this.element.addEventListener('click', (e) => { + if (e.target === this.element || e.target.classList.contains('directory-content')) { + this.clearSelection(); + } + }); + } + + handleFolderClick(e, folder) { + e.preventDefault(); + e.stopPropagation(); + + this.clearSelection(); + this.selectFolder(folder); + } + + handleFolderDoubleClick(e, folder) { + e.preventDefault(); + const path = folder.dataset.path; + if (!path) return; + window.location.href = path; + } + + selectFolder(folder) { + if (this.selectedFolder) { + this.selectedFolder.classList.remove('selected'); + } + folder.classList.add('selected'); + this.selectedFolder = folder; + } + + clearSelection() { + if (this.selectedFolder) { + this.selectedFolder.classList.remove('selected'); + this.selectedFolder = null; + } + } +} + +// Initialize the directory viewer +document.addEventListener('DOMContentLoaded', () => { + const dirViewer = new DirectoryViewer(document.querySelector('.directory-viewer')); +});
\ No newline at end of file |
