diff options
| author | Astounds <kirito@disroot.org> | 2026-04-20 01:22:55 -0400 |
|---|---|---|
| committer | heckyel <heckyel@noreply.git.fridu.us> | 2026-04-20 01:22:55 -0400 |
| commit | a0f315be51ef121618e73d5b450c8616c0d11d21 (patch) | |
| tree | b68f1268a901ded1a7afd2f12a16aed8d9f3d307 /youtube/templates | |
| parent | 62a028968e6d9b4e821b6014d6658b8317328fcf (diff) | |
| download | yt-local-a0f315be51ef121618e73d5b450c8616c0d11d21.tar.lz yt-local-a0f315be51ef121618e73d5b450c8616c0d11d21.tar.xz yt-local-a0f315be51ef121618e73d5b450c8616c0d11d21.zip | |
feature/hls: Add HLS playback support, and refactors documentation for better usability and maintainability. (#1)HEADv0.5.0master
## 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 <kirito@disroot.org>
Co-committed-by: Astounds <kirito@disroot.org>
Diffstat (limited to 'youtube/templates')
| -rw-r--r-- | youtube/templates/base.html | 2 | ||||
| -rw-r--r-- | youtube/templates/channel.html | 12 | ||||
| -rw-r--r-- | youtube/templates/comments.html | 2 | ||||
| -rw-r--r-- | youtube/templates/embed.html | 72 | ||||
| -rw-r--r-- | youtube/templates/licenses.html | 33 | ||||
| -rw-r--r-- | youtube/templates/settings.html | 12 | ||||
| -rw-r--r-- | youtube/templates/watch.html | 165 |
7 files changed, 217 insertions, 81 deletions
diff --git a/youtube/templates/base.html b/youtube/templates/base.html index dd7c628..a67e745 100644 --- a/youtube/templates/base.html +++ b/youtube/templates/base.html @@ -8,7 +8,7 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> - <meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval'; media-src 'self' blob: {{ app_url }}/* data: https://*.googlevideo.com; {{ "img-src 'self' https://*.googleusercontent.com https://*.ggpht.com https://*.ytimg.com;" if not settings.proxy_images else "" }}"> + <meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; media-src 'self' blob: {{ app_url }}/* data: https://*.googlevideo.com; img-src 'self' https://*.googleusercontent.com https://*.ggpht.com https://*.ytimg.com; connect-src 'self' https://*.googlevideo.com; font-src 'self' data:; worker-src 'self' blob:;"> <title>{{ page_title }}</title> <link title="YT Local" href="/youtube.com/opensearch.xml" rel="search" type="application/opensearchdescription+xml"> <link href="/youtube.com/static/favicon.ico" type="image/x-icon" rel="icon"> diff --git a/youtube/templates/channel.html b/youtube/templates/channel.html index 2c0a1a2..274b727 100644 --- a/youtube/templates/channel.html +++ b/youtube/templates/channel.html @@ -82,7 +82,11 @@ <div id="links-metadata"> {% if current_tab in ('videos', 'shorts', 'streams') %} {% set sorts = [('3', 'newest'), ('4', 'newest - no shorts')] %} - <div id="number-of-results">{{ number_of_videos }} videos</div> + {% if current_tab in ('shorts', 'streams') and not is_last_page %} + <div id="number-of-results">{{ number_of_videos }}+ videos</div> + {% else %} + <div id="number-of-results">{{ number_of_videos }} videos</div> + {% endif %} {% elif current_tab == 'playlists' %} {% set sorts = [('3', 'newest'), ('4', 'last video added')] %} {% if items %} @@ -117,7 +121,11 @@ <hr/> <footer class="pagination-container"> - {% if current_tab in ('videos', 'shorts', 'streams') %} + {% if current_tab in ('shorts', 'streams') %} + <nav class="next-previous-button-row"> + {{ common_elements.next_previous_buttons(is_last_page, channel_url + '/' + current_tab, parameters_dictionary) }} + </nav> + {% elif current_tab == 'videos' %} <nav class="pagination-list"> {{ common_elements.page_buttons(number_of_pages, channel_url + '/' + current_tab, parameters_dictionary, include_ends=(current_sort.__str__() in '34')) }} </nav> diff --git a/youtube/templates/comments.html b/youtube/templates/comments.html index 4728a0a..dac0d23 100644 --- a/youtube/templates/comments.html +++ b/youtube/templates/comments.html @@ -58,7 +58,7 @@ {% endfor %} </div> {% if 'more_comments_url' is in comments_info %} - <a class="page-button more-comments" href="{{ comments_info['more_comments_url'] }}">More comments</a> + <a class="page-button more-comments" href="{{ comments_info['more_comments_url'] }}">{{ _('More comments') }}</a> {% endif %} {% endif %} diff --git a/youtube/templates/embed.html b/youtube/templates/embed.html index 85d2d78..b1ad63f 100644 --- a/youtube/templates/embed.html +++ b/youtube/templates/embed.html @@ -3,13 +3,13 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> - <meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'; media-src 'self' https://*.googlevideo.com; {{ "img-src 'self' https://*.googleusercontent.com https://*.ggpht.com https://*.ytimg.com;" if not settings.proxy_images else "" }}"> + <meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval'; media-src 'self' blob: https://*.googlevideo.com; img-src 'self' https://*.googleusercontent.com https://*.ggpht.com https://*.ytimg.com; connect-src 'self' https://*.googlevideo.com; font-src 'self' data:;"> <title>{{ title }}</title> <link href="/youtube.com/static/favicon.ico" type="image/x-icon" rel="icon"> {% if settings.use_video_player == 2 %} <!-- plyr --> <link href="/youtube.com/static/modules/plyr/plyr.css" rel="stylesheet"> - <!--/ plyr --> + <!-- /plyr --> {% endif %} <style> body { @@ -37,9 +37,6 @@ <body> <video id="js-video-player" controls autofocus onmouseleave="{{ title }}" oncontextmenu="{{ title }}" onmouseenter="{{ title }}" title="{{ title }}"> - {% if uni_sources %} - <source src="{{ uni_sources[uni_idx]['url'] }}" type="{{ uni_sources[uni_idx]['type'] }}" data-res="{{ uni_sources[uni_idx]['quality'] }}"> - {% endif %} {% for source in subtitle_sources %} {% if source['on'] %} <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}" default> @@ -47,28 +44,71 @@ <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}"> {% endif %} {% endfor %} + + {% if uni_sources %} + {% for source in uni_sources %} + <source src="{{ source['url'] }}" type="{{ source['type'] }}" title="{{ source['quality_string'] }}"> + {% endfor %} + {% endif %} </video> - {% if js_data %} - <script> - // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later - data = {{ js_data|tojson }}; - // @license-end - </script> - {% endif %} + <script> // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later let storyboard_url = {{ storyboard_url | tojson }}; + let hls_manifest_url = {{ hls_manifest_url | tojson }}; + let hls_unavailable = {{ hls_unavailable | tojson }}; + let playback_mode = {{ playback_mode | tojson }}; + let pair_sources = {{ pair_sources | tojson }}; + let pair_idx = {{ pair_idx | tojson }}; // @license-end </script> - {% if settings.use_video_player == 2 %} + + {% set hls_should_work = (playback_mode == 'hls' or playback_mode == 'auto') and not hls_unavailable %} + {% set use_dash = not hls_should_work %} + + {% if not use_dash %} + <script src="/youtube.com/static/js/hls.min.js" + integrity="sha512-CSVqc4a7tn+tizDNt+eDoVn2fXYAwMDpCLrwGlWrOktNfZQ9gp4dKKScElMeRlrIifhliXs0a06BLaUgmMlCUw==" + crossorigin="anonymous"></script> + {% endif %} + + <script src="/youtube.com/static/js/common.js"></script> + + {% if settings.use_video_player == 0 %} + <!-- Native player --> + {% if use_dash %} + <script src="/youtube.com/static/js/watch.dash.js"></script> + {% else %} + <script src="/youtube.com/static/js/watch.hls.js"></script> + {% endif %} + {% elif settings.use_video_player == 1 %} + <!-- Native player with hotkeys --> + <script src="/youtube.com/static/js/hotkeys.js"></script> + {% if use_dash %} + <script src="/youtube.com/static/js/watch.dash.js"></script> + {% else %} + <script src="/youtube.com/static/js/watch.hls.js"></script> + {% endif %} + {% elif settings.use_video_player == 2 %} <!-- plyr --> <script src="/youtube.com/static/modules/plyr/plyr.min.js" integrity="sha512-l6ZzdXpfMHRfifqaR79wbYCEWjLDMI9DnROvb+oLkKq6d7MGroGpMbI7HFpicvmAH/2aQO+vJhewq8rhysrImw==" crossorigin="anonymous"></script> - <script src="/youtube.com/static/js/plyr-start.js"></script> + {% if use_dash %} + <script src="/youtube.com/static/js/plyr.dash.start.js"></script> + {% else %} + <script src="/youtube.com/static/js/plyr.hls.start.js"></script> + {% endif %} <!-- /plyr --> - {% elif settings.use_video_player == 1 %} - <script src="/youtube.com/static/js/hotkeys.js"></script> + {% endif %} + + {% if use_dash %} + <script src="/youtube.com/static/js/av-merge.js"></script> + {% endif %} + + <!-- Storyboard Preview Thumbnails (native players only; Plyr handles this internally) --> + {% if settings.use_video_player != 2 and settings.native_player_storyboard %} + <script src="/youtube.com/static/js/storyboard-preview.js"></script> {% endif %} </body> </html> diff --git a/youtube/templates/licenses.html b/youtube/templates/licenses.html index dc73bfb..e2af1bf 100644 --- a/youtube/templates/licenses.html +++ b/youtube/templates/licenses.html @@ -30,6 +30,11 @@ <td data-label="Source"><a href="/youtube.com/static/js/common.js">common.js</a></td> </tr> <tr> + <td data-label="File"><a href="/youtube.com/static/js/hls.min.js">hls.min.js</a></td> + <td data-label="License"><a href="https://spdx.org/licenses/BSD-3-Clause.html">BSD-3-Clause</a></td> + <td data-label="Source"><a href="https://github.com/video-dev/hls.js/tree/v1.6.15/src">hls.js v1.6.15 source</a></td> + </tr> + <tr> <td data-label="File"><a href="/youtube.com/static/js/hotkeys.js">hotkeys.js</a></td> <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td> <td data-label="Source"><a href="/youtube.com/static/js/hotkeys.js">hotkeys.js</a></td> @@ -40,9 +45,24 @@ <td data-label="Source"><a href="/youtube.com/static/js/playlistadd.js">playlistadd.js</a></td> </tr> <tr> - <td data-label="File"><a href="/youtube.com/static/js/plyr-start.js">plyr-start.js</a></td> + <td data-label="File"><a href="/youtube.com/static/js/plyr.dash.start.js">plyr.dash.start.js</a></td> + <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td> + <td data-label="Source"><a href="/youtube.com/static/js/plyr.dash.start.js">plyr.dash.start.js</a></td> + </tr> + <tr> + <td data-label="File"><a href="/youtube.com/static/js/plyr.hls.start.js">plyr.hls.start.js</a></td> + <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td> + <td data-label="Source"><a href="/youtube.com/static/js/plyr.hls.start.js">plyr.hls.start.js</a></td> + </tr> + <tr> + <td data-label="File"><a href="/youtube.com/static/js/sponsorblock.js">sponsorblock.js</a></td> + <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td> + <td data-label="Source"><a href="/youtube.com/static/js/sponsorblock.js">sponsorblock.js</a></td> + </tr> + <tr> + <td data-label="File"><a href="/youtube.com/static/js/storyboard-preview.js">storyboard-preview.js</a></td> <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td> - <td data-label="Source"><a href="/youtube.com/static/js/plyr-start.js">plyr-start.js</a></td> + <td data-label="Source"><a href="/youtube.com/static/js/storyboard-preview.js">storyboard-preview.js</a></td> </tr> <tr> <td data-label="File"><a href="/youtube.com/static/modules/plyr/plyr.min.js">plyr.min.js</a></td> @@ -55,9 +75,14 @@ <td data-label="Source"><a href="/youtube.com/static/js/transcript-table.js">transcript-table.js</a></td> </tr> <tr> - <td data-label="File"><a href="/youtube.com/static/js/watch.js">watch.js</a></td> + <td data-label="File"><a href="/youtube.com/static/js/watch.dash.js">watch.dash.js</a></td> + <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td> + <td data-label="Source"><a href="/youtube.com/static/js/watch.dash.js">watch.dash.js</a></td> + </tr> + <tr> + <td data-label="File"><a href="/youtube.com/static/js/watch.hls.js">watch.hls.js</a></td> <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td> - <td data-label="Source"><a href="/youtube.com/static/js/watch.js">watch.js</a></td> + <td data-label="Source"><a href="/youtube.com/static/js/watch.hls.js">watch.hls.js</a></td> </tr> </tbody> </table> diff --git a/youtube/templates/settings.html b/youtube/templates/settings.html index a5bb1e4..940eeae 100644 --- a/youtube/templates/settings.html +++ b/youtube/templates/settings.html @@ -7,15 +7,15 @@ {% block main %} <form method="POST" class="settings-form"> {% for categ in categories %} - <h2>{{ categ|capitalize }}</h2> + <h2>{{ _(categ|capitalize) }}</h2> <ul class="settings-list"> {% for setting_name, setting_info, value in settings_by_category[categ] %} {% if not setting_info.get('hidden', false) %} <li class="setting-item"> {% if 'label' is in(setting_info) %} - <label for="{{ 'setting_' + setting_name }}" {% if 'comment' is in(setting_info) %}title="{{ setting_info['comment'] }}" {% endif %}>{{ setting_info['label'] }}</label> + <label for="{{ 'setting_' + setting_name }}" {% if 'comment' is in(setting_info) %}title="{{ setting_info['comment'] }}" {% endif %}>{{ _(setting_info['label']) }}</label> {% else %} - <label for="{{ 'setting_' + setting_name }}" {% if 'comment' is in(setting_info) %}title="{{ setting_info['comment'] }}" {% endif %}>{{ setting_name.replace('_', ' ')|capitalize }}</label> + <label for="{{ 'setting_' + setting_name }}" {% if 'comment' is in(setting_info) %}title="{{ setting_info['comment'] }}" {% endif %}>{{ _(setting_name.replace('_', ' ')|capitalize) }}</label> {% endif %} {% if setting_info['type'].__name__ == 'bool' %} @@ -24,7 +24,7 @@ {% if 'options' is in(setting_info) %} <select id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}"> {% for option in setting_info['options'] %} - <option value="{{ option[0] }}" {{ 'selected' if option[0] == value else '' }}>{{ option[1] }}</option> + <option value="{{ option[0] }}" {{ 'selected' if option[0] == value else '' }}>{{ _(option[1]) }}</option> {% endfor %} </select> {% else %} @@ -36,7 +36,7 @@ {% if 'options' is in(setting_info) %} <select id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}"> {% for option in setting_info['options'] %} - <option value="{{ option[0] }}" {{ 'selected' if option[0] == value else '' }}>{{ option[1] }}</option> + <option value="{{ option[0] }}" {{ 'selected' if option[0] == value else '' }}>{{ _(option[1]) }}</option> {% endfor %} </select> {% else %} @@ -50,6 +50,6 @@ {% endfor %} </ul> {% endfor %} - <input type="submit" value="Save settings"> + <input type="submit" value="{{ _('Save settings') }}"> </form> {% endblock main %} diff --git a/youtube/templates/watch.html b/youtube/templates/watch.html index d62884f..079a01c 100644 --- a/youtube/templates/watch.html +++ b/youtube/templates/watch.html @@ -9,7 +9,7 @@ <!-- plyr --> <link href="/youtube.com/static/modules/plyr/plyr.css" rel="stylesheet"> <link href="/youtube.com/static/modules/plyr/custom_plyr.css" rel="stylesheet"> - <!--/ plyr --> + <!-- /plyr --> {% endif %} {% endblock style %} @@ -23,22 +23,9 @@ {% endif %} </span> </div> - {% elif (uni_sources.__len__() == 0 or live) and hls_formats.__len__() != 0 %} - <div class="live-url-choices"> - <span>Copy a url into your video player:</span> - <ol> - {% for fmt in hls_formats %} - <li class="url-choice"><div class="url-choice-label">{{ fmt['video_quality'] }}: </div><input class="url-choice-copy" value="{{ fmt['url'] }}" readonly onclick="this.select();"></li> - {% endfor %} - </ol> - </div> {% else %} <figure class="sc-video"> <video id="js-video-player" playsinline controls {{ 'autoplay' if settings.autoplay_videos }}> - {% if uni_sources %} - <source src="{{ uni_sources[uni_idx]['url'] }}" type="{{ uni_sources[uni_idx]['type'] }}" data-res="{{ uni_sources[uni_idx]['quality'] }}"> - {% endif %} - {% for source in subtitle_sources %} {% if source['on'] %} <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}" default> @@ -46,7 +33,18 @@ <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}"> {% endif %} {% endfor %} + + {% if uni_sources %} + {% for source in uni_sources %} + <source src="{{ source['url'] }}" type="{{ source['type'] }}" title="{{ source['quality_string'] }}"> + {% endfor %} + {% endif %} </video> + {% if hls_unavailable and not uni_sources %} + <div class="playability-error"> + <span>Error: HLS streams unavailable. Video may not play without JavaScript fallback.</span> + </div> + {% endif %} </figure> {% endif %} @@ -76,41 +74,68 @@ <div class="external-player-controls"> <input class="speed" id="speed-control" type="text" title="Video speed"> + {% if settings.use_video_player < 2 %} + <!-- Native player quality selector --> + <select id="quality-select" autocomplete="off"> + <option value="-1" selected>Auto</option> + <!-- Quality options will be populated by HLS --> + </select> + {% else %} + <select id="quality-select" autocomplete="off" style="display: none;"> + <!-- Quality options will be populated by HLS --> + </select> + {% endif %} {% if settings.use_video_player != 2 %} - <select id="quality-select" autocomplete="off"> - {% for src in uni_sources %} - <option value='{"type": "uni", "index": {{ loop.index0 }}}' {{ 'selected' if loop.index0 == uni_idx and not using_pair_sources else '' }} >{{ src['quality_string'] }}</option> - {% endfor %} - {% for src_pair in pair_sources %} - <option value='{"type": "pair", "index": {{ loop.index0}}}' {{ 'selected' if loop.index0 == pair_idx and using_pair_sources else '' }} >{{ src_pair['quality_string'] }}</option> + {% if audio_tracks|length > 1 %} + <select id="audio-track-select" autocomplete="off"> + {% for track in audio_tracks %} + <option value="{{ track['id'] }}" {{ 'selected' if track['is_default'] else '' }}>{{ track['name'] }}</option> {% endfor %} </select> - + {% endif %} {% endif %} </div> <input class="v-checkbox" name="video_info_list" value="{{ video_info }}" form="playlist-edit" type="checkbox"> - <span class="v-direct-link"><a href="https://youtu.be/{{ video_id }}" rel="noopener noreferrer" target="_blank">Direct Link</a></span> + <span class="v-direct-link"><a href="https://youtu.be/{{ video_id }}" rel="noopener noreferrer" target="_blank">{{ _('Direct Link') }}</a></span> {% if settings.use_video_download != 0 %} <details class="v-download"> - <summary class="download-dropdown-label">Download</summary> - <ul class="download-dropdown-content"> - {% for format in download_formats %} - <li class="download-format"> - <a class="download-link" href="{{ format['url'] }}" download="{{ title }}.{{ format['ext'] }}"> - {{ format['ext'] }} {{ format['video_quality'] }} {{ format['audio_quality'] }} {{ format['file_size'] }} {{ format['codecs'] }} - </a> - </li> - {% endfor %} - {% for download in other_downloads %} - <li class="download-format"> - <a href="{{ download['url'] }}" download> - {{ download['ext'] }} {{ download['label'] }} - </a> - </li> - {% endfor %} - </ul> + <summary class="download-dropdown-label">{{ _('Download') }}</summary> + <div class="download-table-container"> + <table class="download-table" aria-label="Download formats"> + <thead> + <tr> + <th scope="col">{{ _('Ext') }}</th> + <th scope="col">{{ _('Video') }}</th> + <th scope="col">{{ _('Audio') }}</th> + <th scope="col">{{ _('Size') }}</th> + <th scope="col">{{ _('Codecs') }}</th> + <th scope="col">{{ _('Link') }}</th> + </tr> + </thead> + <tbody> + {% for format in download_formats %} + <tr> + <td data-label="{{ _('Ext') }}">{{ format['ext'] }}</td> + <td data-label="{{ _('Video') }}">{{ format['video_quality'] }}</td> + <td data-label="{{ _('Audio') }}">{{ format['audio_quality'] }}</td> + <td data-label="{{ _('Size') }}">{{ format['file_size'] }}</td> + <td data-label="{{ _('Codecs') }}">{{ format['codecs'] }}</td> + <td data-label="{{ _('Link') }}"><a class="download-link" href="{{ format['url'] }}" download="{{ title }}.{{ format['ext'] }}" aria-label="{{ _('Download') }} {{ format['ext'] }} {{ format['video_quality'] }} {{ format['audio_quality'] }}">{{ _('Download') }}</a></td> + </tr> + {% endfor %} + {% for download in other_downloads %} + <tr> + <td data-label="{{ _('Ext') }}">{{ download['ext'] }}</td> + <td data-label="{{ _('Video') }}" colspan="3">{{ download['label'] }}</td> + <td data-label="{{ _('Codecs') }}">{{ download.get('codecs', 'N/A') }}</td> + <td data-label="{{ _('Link') }}"><a class="download-link" href="{{ download['url'] }}" download aria-label="{{ _('Download') }} {{ download['label'] }}">{{ _('Download') }}</a></td> + </tr> + {% endfor %} + </tbody> + </table> + </div> </details> {% else %} <span class="v-download"></span> @@ -142,7 +167,7 @@ {% endif %} </div> <details class="v-more-info"> - <summary>More info</summary> + <summary>{{ _('More info') }}</summary> <div class="more-info-content"> <p>Tor exit node: {{ ip_address }}</p> {% if invidious_used %} @@ -166,7 +191,7 @@ <div class="playlist-header"> <a href="{{ playlist['url'] }}" title="{{ playlist['title'] }}"><h3>{{ playlist['title'] }}</h3></a> <ul class="playlist-metadata"> - <li><label for="playlist-autoplay-toggle">Autoplay: </label><input id="playlist-autoplay-toggle" type="checkbox" class="autoplay-toggle"></li> + <li><label for="playlist-autoplay-toggle">{{ _('AutoNext') }}: </label><input id="playlist-autoplay-toggle" type="checkbox" class="autoplay-toggle"></li> {% if playlist['current_index'] is none %} <li>[Error!]/{{ playlist['video_count'] }}</li> {% else %} @@ -193,7 +218,7 @@ </nav> </div> {% elif settings.related_videos_mode != 0 %} - <div class="related-autoplay"><label for="related-autoplay-toggle">Autoplay: </label><input id="related-autoplay-toggle" type="checkbox" class="autoplay-toggle"></div> + <div class="related-autoplay"><label for="related-autoplay-toggle">{{ _('AutoNext') }}: </label><input id="related-autoplay-toggle" type="checkbox" class="autoplay-toggle"></div> {% endif %} {% if subtitle_sources %} @@ -215,7 +240,7 @@ {% if settings.related_videos_mode != 0 %} <details class="related-videos-outer" {{'open' if settings.related_videos_mode == 1 else ''}}> - <summary>Related Videos</summary> + <summary>{{ _('Related Videos') }}</summary> <nav class="related-videos-inner"> {% for info in related %} {{ common_elements.item(info, include_badges=false) }} @@ -229,10 +254,10 @@ <!-- comments --> {% if settings.comments_mode != 0 %} {% if comments_disabled %} - <div class="comments-area-outer comments-disabled">Comments disabled</div> + <div class="comments-area-outer comments-disabled">{{ _('Comments disabled') }}</div> {% else %} <details class="comments-area-outer" {{'open' if settings.comments_mode == 1 else ''}}> - <summary>{{ comment_count|commatize }} comment{{'s' if comment_count != '1' else ''}}</summary> + <summary>{{ comment_count|commatize }} {{ _('Comment') }}{{'s' if comment_count != '1' else ''}}</summary> <div class="comments-area-inner comments-area"> {% if comments_info %} {{ comments.video_comments(comments_info) }} @@ -244,26 +269,64 @@ </div> - <script src="/youtube.com/static/js/av-merge.js"></script> - <script src="/youtube.com/static/js/watch.js"></script> <script> // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later let storyboard_url = {{ storyboard_url | tojson }}; + let hls_manifest_url = {{ hls_manifest_url | tojson }}; + let hls_unavailable = {{ hls_unavailable | tojson }}; + let playback_mode = {{ playback_mode | tojson }}; + let pair_sources = {{ pair_sources | tojson }}; + let pair_idx = {{ pair_idx | tojson }}; // @license-end </script> <script src="/youtube.com/static/js/common.js"></script> <script src="/youtube.com/static/js/transcript-table.js"></script> - {% if settings.use_video_player == 2 %} + + {% set hls_should_work = (playback_mode == 'hls' or playback_mode == 'auto') and not hls_unavailable %} + {% set use_dash = not hls_should_work %} + + {% if use_dash %} + <script src="/youtube.com/static/js/av-merge.js"></script> + {% else %} + <script src="/youtube.com/static/js/hls.min.js" + integrity="sha512-CSVqc4a7tn+tizDNt+eDoVn2fXYAwMDpCLrwGlWrOktNfZQ9gp4dKKScElMeRlrIifhliXs0a06BLaUgmMlCUw==" + crossorigin="anonymous"></script> + {% endif %} + + {% if settings.use_video_player == 0 %} + <!-- Native player (no hotkeys) --> + {% if use_dash %} + <script src="/youtube.com/static/js/watch.dash.js"></script> + {% else %} + <script src="/youtube.com/static/js/watch.hls.js"></script> + {% endif %} + {% elif settings.use_video_player == 1 %} + <!-- Native player with hotkeys --> + <script src="/youtube.com/static/js/hotkeys.js"></script> + {% if use_dash %} + <script src="/youtube.com/static/js/watch.dash.js"></script> + {% else %} + <script src="/youtube.com/static/js/watch.hls.js"></script> + {% endif %} + {% elif settings.use_video_player == 2 %} <!-- plyr --> <script src="/youtube.com/static/modules/plyr/plyr.min.js" integrity="sha512-l6ZzdXpfMHRfifqaR79wbYCEWjLDMI9DnROvb+oLkKq6d7MGroGpMbI7HFpicvmAH/2aQO+vJhewq8rhysrImw==" crossorigin="anonymous"></script> - <script src="/youtube.com/static/js/plyr-start.js"></script> + {% if use_dash %} + <script src="/youtube.com/static/js/plyr.dash.start.js"></script> + {% else %} + <script src="/youtube.com/static/js/plyr.hls.start.js"></script> + {% endif %} <!-- /plyr --> - {% elif settings.use_video_player == 1 %} - <script src="/youtube.com/static/js/hotkeys.js"></script> {% endif %} + + <!-- Storyboard Preview Thumbnails (native players only; Plyr handles this internally) --> + {% if settings.use_video_player != 2 and settings.native_player_storyboard %} + <script src="/youtube.com/static/js/storyboard-preview.js"></script> + {% endif %} + {% if settings.use_comments_js %} <script src="/youtube.com/static/js/comments.js"></script> {% endif %} {% if settings.use_sponsorblock_js %} <script src="/youtube.com/static/js/sponsorblock.js"></script> {% endif %} {% endblock main %} |
