aboutsummaryrefslogtreecommitdiffstats
path: root/youtube/static/js/watch.hls.js
diff options
context:
space:
mode:
Diffstat (limited to 'youtube/static/js/watch.hls.js')
-rw-r--r--youtube/static/js/watch.hls.js323
1 files changed, 323 insertions, 0 deletions
diff --git a/youtube/static/js/watch.hls.js b/youtube/static/js/watch.hls.js
new file mode 100644
index 0000000..cedfbf3
--- /dev/null
+++ b/youtube/static/js/watch.hls.js
@@ -0,0 +1,323 @@
+const video = document.getElementById('js-video-player');
+
+window.hls = null;
+let hls = null;
+
+// ===========
+// HLS NATIVE
+// ===========
+function initHLSNative(manifestUrl) {
+ if (!manifestUrl) {
+ console.error('No HLS manifest URL provided');
+ return;
+ }
+
+ console.log('Initializing native HLS player with manifest:', manifestUrl);
+
+ if (hls) {
+ window.hls = null;
+ hls.destroy();
+ hls = null;
+ }
+
+ if (Hls.isSupported()) {
+ hls = new Hls({
+ enableWorker: true,
+ lowLatencyMode: false,
+ maxBufferLength: 30,
+ maxMaxBufferLength: 60,
+ startLevel: -1,
+ });
+
+ window.hls = hls;
+
+ hls.loadSource(manifestUrl);
+ hls.attachMedia(video);
+
+ hls.on(Hls.Events.MANIFEST_PARSED, function(event, data) {
+ console.log('Native manifest parsed');
+ console.log('Levels:', data.levels.length);
+
+ const qualitySelect = document.getElementById('quality-select');
+
+ if (qualitySelect && data.levels?.length) {
+ qualitySelect.innerHTML = '<option value="-1">Auto</option>';
+
+ const sorted = [...data.levels].sort((a, b) => b.height - a.height);
+ const seen = new Set();
+
+ sorted.forEach(level => {
+ if (!seen.has(level.height)) {
+ seen.add(level.height);
+
+ const i = data.levels.indexOf(level);
+ const opt = document.createElement('option');
+
+ opt.value = i;
+ opt.textContent = level.height + 'p';
+
+ qualitySelect.appendChild(opt);
+ }
+ });
+
+ // Set initial quality from settings
+ if (typeof window.data !== 'undefined' && window.data.settings) {
+ const defaultRes = window.data.settings.default_resolution;
+ if (defaultRes !== 'auto' && defaultRes) {
+ const target = parseInt(defaultRes);
+ let bestLevel = -1;
+ let bestHeight = 0;
+ for (let i = 0; i < hls.levels.length; i++) {
+ const h = hls.levels[i].height;
+ if (h <= target && h > bestHeight) {
+ bestHeight = h;
+ bestLevel = i;
+ }
+ }
+ if (bestLevel !== -1) {
+ hls.currentLevel = bestLevel;
+ qualitySelect.value = bestLevel;
+ console.log('Starting at resolution:', bestHeight + 'p');
+ }
+ }
+ }
+ }
+ });
+
+ hls.on(Hls.Events.ERROR, function(_, data) {
+ if (data.fatal) {
+ console.error('HLS fatal error:', data.type, data.details);
+ switch(data.type) {
+ case Hls.ErrorTypes.NETWORK_ERROR:
+ hls.startLoad();
+ break;
+ case Hls.ErrorTypes.MEDIA_ERROR:
+ hls.recoverMediaError();
+ break;
+ default:
+ hls.destroy();
+ break;
+ }
+ }
+ });
+
+ } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
+ video.src = manifestUrl;
+ } else {
+ console.error('HLS not supported');
+ }
+}
+
+// ======
+// INIT
+// ======
+function initPlayer() {
+ console.log('Init native player');
+
+ if (typeof hls_manifest_url === 'undefined' || !hls_manifest_url) {
+ console.error('No manifest URL');
+ return;
+ }
+
+ initHLSNative(hls_manifest_url);
+
+ const qualitySelect = document.getElementById('quality-select');
+ if (qualitySelect) {
+ qualitySelect.addEventListener('change', function () {
+ const level = parseInt(this.value);
+
+ if (hls) {
+ hls.currentLevel = level;
+ console.log('Quality:', level === -1 ? 'Auto' : hls.levels[level]?.height + 'p');
+ }
+ });
+ }
+}
+
+// DOM READY
+if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', initPlayer);
+} else {
+ initPlayer();
+}
+
+// =============
+// AUDIO TRACKS
+// =============
+document.addEventListener('DOMContentLoaded', function() {
+ const audioTrackSelect = document.getElementById('audio-track-select');
+
+ if (audioTrackSelect) {
+ audioTrackSelect.addEventListener('change', function() {
+ const trackId = this.value;
+
+ if (hls && hls.audioTracks) {
+ const index = hls.audioTracks.findIndex(t =>
+ t.lang === trackId || t.name === trackId
+ );
+
+ if (index !== -1) {
+ hls.audioTrack = index;
+ console.log('Audio track changed to:', index);
+ }
+ }
+ });
+ }
+
+ if (hls) {
+ hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, (_, data) => {
+ console.log('Audio tracks:', data.audioTracks);
+
+ // Populate audio track select if needed
+ if (audioTrackSelect && data.audioTracks.length > 0) {
+ audioTrackSelect.innerHTML = '<option value="">Select audio track</option>';
+ data.audioTracks.forEach(track => {
+ const option = document.createElement('option');
+ option.value = track.lang || track.name;
+ option.textContent = track.name || track.lang || `Track ${track.id}`;
+ audioTrackSelect.appendChild(option);
+ });
+ audioTrackSelect.disabled = false;
+ }
+ });
+ }
+});
+
+// ============
+// START TIME
+// ============
+if (typeof data !== 'undefined' && data.time_start != 0 && video) {
+ video.addEventListener('loadedmetadata', function() {
+ video.currentTime = data.time_start;
+ });
+}
+
+// ==============
+// SPEED CONTROL
+// ==============
+let speedInput = document.getElementById('speed-control');
+
+if (speedInput) {
+ speedInput.addEventListener('keyup', (event) => {
+ if (event.key === 'Enter') {
+ let speed = parseFloat(speedInput.value);
+ if(!isNaN(speed)){
+ video.playbackRate = speed;
+ }
+ }
+ });
+}
+
+// =========
+// Autoplay
+// =========
+(function() {
+ if (typeof data === 'undefined' || (data.settings.related_videos_mode === 0 && data.playlist === null)) {
+ return;
+ }
+
+ let playability_error = !!data.playability_error;
+ let isPlaylist = false;
+ if (data.playlist !== null && data.playlist['current_index'] !== null)
+ isPlaylist = true;
+
+ // read cookies on whether to autoplay
+ // https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie
+ let cookieValue;
+ let playlist_id;
+ if (isPlaylist) {
+ // from https://stackoverflow.com/a/6969486
+ function escapeRegExp(string) {
+ // $& means the whole matched string
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ }
+ playlist_id = data.playlist['id'];
+ playlist_id = escapeRegExp(playlist_id);
+
+ cookieValue = document.cookie.replace(new RegExp(
+ '(?:(?:^|.*;\\s*)autoplay_'
+ + playlist_id + '\\s*\\=\\s*([^;]*).*$)|^.*$'
+ ), '$1');
+ } else {
+ cookieValue = document.cookie.replace(new RegExp(
+ '(?:(?:^|.*;\\s*)autoplay\\s*\\=\\s*([^;]*).*$)|^.*$'
+ ),'$1');
+ }
+
+ let autoplayEnabled = 0;
+ if(cookieValue.length === 0){
+ autoplayEnabled = 0;
+ } else {
+ autoplayEnabled = Number(cookieValue);
+ }
+
+ // check the checkbox if autoplay is on
+ let checkbox = document.querySelector('.autoplay-toggle');
+ if(autoplayEnabled){
+ checkbox.checked = true;
+ }
+
+ // listen for checkbox to turn autoplay on and off
+ let cookie = 'autoplay'
+ if (isPlaylist)
+ cookie += '_' + playlist_id;
+
+ checkbox.addEventListener( 'change', function() {
+ if(this.checked) {
+ autoplayEnabled = 1;
+ document.cookie = cookie + '=1; SameSite=Strict';
+ } else {
+ autoplayEnabled = 0;
+ document.cookie = cookie + '=0; SameSite=Strict';
+ }
+ });
+
+ if(!playability_error){
+ // play the video if autoplay is on
+ if(autoplayEnabled){
+ video.play().catch(function(e) {
+ // Autoplay blocked by browser - ignore silently
+ console.log('Autoplay blocked:', e.message);
+ });
+ }
+ }
+
+ // determine next video url
+ let nextVideoUrl;
+ if (isPlaylist) {
+ let currentIndex = data.playlist['current_index'];
+ if (data.playlist['current_index']+1 == data.playlist['items'].length)
+ nextVideoUrl = null;
+ else
+ nextVideoUrl = data.playlist['items'][data.playlist['current_index']+1]['url'];
+
+ // scroll playlist to proper position
+ // item height + gap == 100
+ let pl = document.querySelector('.playlist-videos');
+ pl.scrollTop = 100*currentIndex;
+ } else {
+ if (data.related.length === 0)
+ nextVideoUrl = null;
+ else
+ nextVideoUrl = data.related[0]['url'];
+ }
+ let nextVideoDelay = 1000;
+
+ // go to next video when video ends
+ // https://stackoverflow.com/a/2880950
+ if (nextVideoUrl) {
+ if(playability_error){
+ videoEnded();
+ } else {
+ video.addEventListener('ended', videoEnded, false);
+ }
+ function nextVideo(){
+ if(autoplayEnabled){
+ window.location.href = nextVideoUrl;
+ }
+ }
+ function videoEnded(e) {
+ window.setTimeout(nextVideo, nextVideoDelay);
+ }
+ }
+})();