Files
yt-local/youtube/static/js/plyr.dash.start.js
Astounds a0f315be51
All checks were successful
git-sync-with-mirror / git-sync (push) Successful in 32s
CI / test (push) Successful in 46s
feature/hls: Add HLS playback support, and refactors documentation for better usability and maintainability. (#1)
## 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: #1
Co-authored-by: Astounds <kirito@disroot.org>
Co-committed-by: Astounds <kirito@disroot.org>
2026-04-20 01:22:55 -04:00

181 lines
4.7 KiB
JavaScript

(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;
});
}
})();