aboutsummaryrefslogtreecommitdiffstats
path: root/youtube/templates
diff options
context:
space:
mode:
authorAstounds <kirito@disroot.org>2026-04-20 01:22:55 -0400
committerheckyel <heckyel@noreply.git.fridu.us>2026-04-20 01:22:55 -0400
commita0f315be51ef121618e73d5b450c8616c0d11d21 (patch)
treeb68f1268a901ded1a7afd2f12a16aed8d9f3d307 /youtube/templates
parent62a028968e6d9b4e821b6014d6658b8317328fcf (diff)
downloadyt-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.html2
-rw-r--r--youtube/templates/channel.html12
-rw-r--r--youtube/templates/comments.html2
-rw-r--r--youtube/templates/embed.html72
-rw-r--r--youtube/templates/licenses.html33
-rw-r--r--youtube/templates/settings.html12
-rw-r--r--youtube/templates/watch.html165
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 %}