From a0f315be51ef121618e73d5b450c8616c0d11d21 Mon Sep 17 00:00:00 2001 From: Astounds Date: Mon, 20 Apr 2026 01:22:55 -0400 Subject: feature/hls: Add HLS playback support, and refactors documentation for better usability and maintainability. (#1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Overview This PR introduces HLS playback support, improves the player experience, and refactors documentation for better usability and maintainability. ## Key Features ### HLS Playback Support - Add HLS integration via new JavaScript assets: - `hls.min.js` - `plyr.hls.start.js` - `watch.hls.js` - Separate DASH and HLS logic: - `plyr-start.js` → `plyr.dash.start.js` - `watch.js` → `watch.dash.js` - Update templates (`embed.html`, `watch.html`) for conditional player loading ### Native Storyboard Preview - Add `native_player_storyboard` setting in `settings.py` - Implement hover thumbnail preview for native player modes - Add `storyboard-preview.js` ### UI and Player Adjustments - Update templates and styles (`custom_plyr.css`) - Modify backend modules to support new player modes: - `watch.py`, `channel.py`, `util.py`, and related components ### Internationalization - Update translation files: - `messages.po` - `messages.pot` ### Testing and CI - Add and update tests: - `test_shorts.py` - `test_util.py` - Minor CI and release script improvements ## Documentation ### OpenRC Service Guide Rewrite - Restructure `docs/basic-script-openrc/README.md` into: - Prerequisites - Installation - Service Management - Verification - Troubleshooting - Add admonition blocks: - `[!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, `[!CAUTION]` - Fix log inspection command: ```bash doas tail -f /var/log/ytlocal.log ```` * Add path placeholders and clarify permission requirements * Remove legacy and duplicate content Reviewed-on: https://git.fridu.us/heckyel/yt-local/pulls/1 Co-authored-by: Astounds Co-committed-by: Astounds --- youtube/static/js/plyr.dash.start.js | 180 +++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 youtube/static/js/plyr.dash.start.js (limited to 'youtube/static/js/plyr.dash.start.js') diff --git a/youtube/static/js/plyr.dash.start.js b/youtube/static/js/plyr.dash.start.js new file mode 100644 index 0000000..3cc8e9b --- /dev/null +++ b/youtube/static/js/plyr.dash.start.js @@ -0,0 +1,180 @@ +(function main() { + 'use strict'; + + // Captions + let captionsActive = false; + if (data.settings.subtitles_mode === 2 || (data.settings.subtitles_mode === 1 && data.has_manual_captions)) { + captionsActive = true; + } + + // AutoPlay + let autoplayActive = data.settings.autoplay_videos || false; + + let qualityOptions = []; + let qualityDefault; + + // Collect uni sources (integrated) + for (let src of data.uni_sources) { + qualityOptions.push(src.quality_string); + } + + // Collect pair sources (av-merge) + for (let src of data.pair_sources) { + qualityOptions.push(src.quality_string); + } + + if (data.using_pair_sources) { + qualityDefault = data.pair_sources[data.pair_idx].quality_string; + } else if (data.uni_sources.length !== 0) { + qualityDefault = data.uni_sources[data.uni_idx].quality_string; + } else { + qualityDefault = 'None'; + } + + // Current av-merge instance + let avMerge = null; + + // Change quality: handles both uni (integrated) and pair (av-merge) + function changeQuality(selection) { + let currentVideoTime = video.currentTime; + let videoPaused = video.paused; + let videoSpeed = video.playbackRate; + let srcInfo; + + // Close previous av-merge if any + if (avMerge && typeof avMerge.close === 'function') { + avMerge.close(); + } + + if (selection.type == 'uni') { + srcInfo = data.uni_sources[selection.index]; + video.src = srcInfo.url; + avMerge = null; + } else { + srcInfo = data.pair_sources[selection.index]; + avMerge = new AVMerge(video, srcInfo, currentVideoTime); + } + + video.currentTime = currentVideoTime; + if (!videoPaused) { + video.play(); + } + video.playbackRate = videoSpeed; + } + + // Fix plyr refusing to work with qualities that are strings + Object.defineProperty(Plyr.prototype, 'quality', { + set: function (input) { + const config = this.config.quality; + const options = this.options.quality; + let quality = input; + let updateStorage = true; + + if (!options.length) { + return; + } + + if (!options.includes(quality)) { + return; + } + + // Update config + config.selected = quality; + + // Set quality + this.media.quality = quality; + + // Save to storage + if (updateStorage) { + this.storage.set({ quality }); + } + }, + }); + + const playerOptions = { + autoplay: autoplayActive, + disableContextMenu: false, + captions: { + active: captionsActive, + language: data.settings.subtitles_language, + }, + controls: [ + 'play-large', + 'play', + 'progress', + 'current-time', + 'duration', + 'mute', + 'volume', + 'captions', + 'settings', + 'pip', + 'airplay', + 'fullscreen', + ], + iconUrl: '/youtube.com/static/modules/plyr/plyr.svg', + blankVideo: '/youtube.com/static/modules/plyr/blank.webm', + debug: false, + storage: { enabled: false }, + quality: { + default: qualityDefault, + options: qualityOptions, + forced: true, + onChange: function (quality) { + if (quality == 'None') { + return; + } + // Check if it's a uni source (integrated) + if (quality.includes('(integrated)')) { + for (let i = 0; i < data.uni_sources.length; i++) { + if (data.uni_sources[i].quality_string == quality) { + changeQuality({ type: 'uni', index: i }); + return; + } + } + } else { + // It's a pair source (av-merge) + for (let i = 0; i < data.pair_sources.length; i++) { + if (data.pair_sources[i].quality_string == quality) { + changeQuality({ type: 'pair', index: i }); + return; + } + } + } + }, + }, + previewThumbnails: { + enabled: storyboard_url !== null, + src: [storyboard_url], + }, + settings: ['captions', 'quality', 'speed', 'loop'], + tooltips: { + controls: true, + }, + }; + + const video = document.getElementById('js-video-player'); + const player = new Plyr(video, playerOptions); + + // Hide audio track selector (DASH doesn't support multi-audio) + const audioContainer = document.getElementById('plyr-audio-container'); + if (audioContainer) audioContainer.style.display = 'none'; + + // disable double click to fullscreen + player.eventListeners.forEach(function(eventListener) { + if(eventListener.type === 'dblclick') { + eventListener.element.removeEventListener(eventListener.type, eventListener.callback, eventListener.options); + } + }); + + // Add .started property + player.started = false; + player.once('playing', function(){ this.started = true; }); + + // Set initial time + if (data.time_start != 0) { + video.addEventListener('loadedmetadata', function() { + video.currentTime = data.time_start; + }); + } +})(); -- cgit v1.2.3