'use strict'; console.log('[Invidious Debug] player.js start'); var player_data = JSON.parse(document.getElementById('player_data').textContent); var video_data = JSON.parse(document.getElementById('video_data').textContent); console.log('[Invidious Debug] video_data.params:', JSON.stringify(video_data.params)); var options = { liveui: true, playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0], controlBar: { children: [ 'playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'remainingTimeDisplay', 'Spacer', 'captionsButton', 'audioTrackButton', 'qualitySelector', 'playbackRateMenuButton', 'fullscreenToggle' ] }, html5: { preloadTextTracks: false, vhs: { overrideNative: true } } }; if (player_data.aspect_ratio) { options.aspectRatio = player_data.aspect_ratio; } var embed_url = new URL(location); embed_url.searchParams.delete('v'); var short_url = location.origin + '/' + video_data.id + embed_url.search; embed_url = location.origin + '/embed/' + video_data.id + embed_url.search; var save_player_pos_key = 'save_player_pos'; videojs.Vhs.xhr.beforeRequest = function(options) { // set local if requested not videoplayback if (!options.uri.includes('videoplayback')) { if (!options.uri.includes('local=true')) options.uri += '?local=true'; } return options; }; console.log('[Invidious Debug] Initializing videojs...'); var player; try { player = videojs('player', options); console.log('[Invidious Debug] videojs initialized successfully.'); } catch (e) { console.error('[Invidious Debug] videojs initialization FAILED:', e); // Stop further execution if player init fails throw e; } player.on('error', function () { console.error('[Invidious Debug] Player error event triggered:', player.error()); if (video_data.params.quality === 'dash') return; var localNotDisabled = ( !player.currentSrc().includes('local=true') && !video_data.local_disabled ); var reloadMakesSense = ( player.error().code === MediaError.MEDIA_ERR_NETWORK || player.error().code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED ); if (localNotDisabled) { // add local=true to all current sources player.src(player.currentSources().map(function (source) { source.src += '&local=true'; return source; })); } else if (reloadMakesSense) { console.log('[Invidious Debug] Player error: Attempting reload in 5 seconds.'); setTimeout(function () { console.warn('An error occurred in the player, reloading...'); // After load() all parameters are reset. Save them var currentTime = player.currentTime(); var playbackRate = player.playbackRate(); var paused = player.paused(); player.load(); if (currentTime > 0.5) currentTime -= 0.5; player.currentTime(currentTime); player.playbackRate(playbackRate); if (!paused) player.play(); }, 5000); } else { console.log('[Invidious Debug] Player error: No specific action taken.'); } }); if (video_data.params.quality === 'dash') { console.log('[Invidious Debug] Initializing reloadSourceOnError...'); try { player.reloadSourceOnError({ errorInterval: 10 }); console.log('[Invidious Debug] reloadSourceOnError initialized.'); } catch (e) { console.error('[Invidious Debug] reloadSourceOnError FAILED:', e); } } /** * Function for add time argument to url * * @param {String} url * @param {String} [base] * @returns {URL} urlWithTimeArg */ function addCurrentTimeToURL(url, base) { var urlUsed = new URL(url, base); urlUsed.searchParams.delete('start'); var currentTime = Math.ceil(player.currentTime()); if (currentTime > 0) urlUsed.searchParams.set('t', currentTime); else if (urlUsed.searchParams.has('t')) urlUsed.searchParams.delete('t'); return urlUsed; } /** * Global variable to save the last timestamp (in full seconds) at which the external * links were updated by the 'timeupdate' callback below. * * It is initialized to 5s so that the video will always restart from the beginning * if the user hasn't really started watching before switching to the other website. */ var timeupdate_last_ts = 5; /** * Callback that updates the timestamp on all external links */ player.on('timeupdate', function () { // Only update once every second let current_ts = Math.floor(player.currentTime()); if (current_ts > timeupdate_last_ts) timeupdate_last_ts = current_ts; else return; // YouTube links let elem_yt_watch = document.getElementById('link-yt-watch'); if (elem_yt_watch) { let base_url_yt_watch = elem_yt_watch.getAttribute('data-base-url'); elem_yt_watch.href = addCurrentTimeToURL(base_url_yt_watch); } let elem_yt_embed = document.getElementById('link-yt-embed'); if (elem_yt_embed) { let base_url_yt_embed = elem_yt_embed.getAttribute('data-base-url'); elem_yt_embed.href = addCurrentTimeToURL(base_url_yt_embed); } // Invidious links let domain = window.location.origin; let elem_iv_embed = document.getElementById('link-iv-embed'); if (elem_iv_embed) { let base_url_iv_embed = elem_iv_embed.getAttribute('data-base-url'); elem_iv_embed.href = addCurrentTimeToURL(base_url_iv_embed, domain); } let elem_iv_other = document.getElementById('link-iv-other'); if (elem_iv_other) { let base_url_iv_other = elem_iv_other.getAttribute('data-base-url'); elem_iv_other.href = addCurrentTimeToURL(base_url_iv_other, domain); } }); /** * Method for getting the contents of a cookie * * @param {String} name Name of cookie * @returns {String|null} cookieValue */ function getCookieValue(name) { var cookiePrefix = name + '='; var matchedCookie = document.cookie.split(';').find(function (item) {return item.includes(cookiePrefix);}); if (matchedCookie) return matchedCookie.replace(cookiePrefix, ''); return null; } /** * Method for updating the 'PREFS' cookie (or creating it if missing) * * @param {number} newVolume New volume defined (null if unchanged) * @param {number} newSpeed New speed defined (null if unchanged) */ function updateCookie(newVolume, newSpeed) { var volumeValue = newVolume !== null ? newVolume : video_data.params.volume; var speedValue = newSpeed !== null ? newSpeed : video_data.params.speed; var cookieValue = getCookieValue('PREFS'); var cookieData; if (cookieValue !== null) { var cookieJson = JSON.parse(decodeURIComponent(cookieValue)); cookieJson.volume = volumeValue; cookieJson.speed = speedValue; cookieData = encodeURIComponent(JSON.stringify(cookieJson)); } else { cookieData = encodeURIComponent(JSON.stringify({ 'volume': volumeValue, 'speed': speedValue })); } // Set expiration in 2 year var date = new Date(); date.setFullYear(date.getFullYear() + 2); var ipRegex = /^((\d+\.){3}\d+|[\dA-Fa-f]*:[\d:A-Fa-f]*:[\d:A-Fa-f]+)$/; var domainUsed = location.hostname; // Fix for a bug in FF where the leading dot in the FQDN is not ignored if (domainUsed.charAt(0) !== '.' && !ipRegex.test(domainUsed) && domainUsed !== 'localhost') domainUsed = '.' + location.hostname; var secure = location.protocol.startsWith("https") ? " Secure;" : ""; document.cookie = 'PREFS=' + cookieData + '; SameSite=Lax; path=/; domain=' + domainUsed + '; expires=' + date.toGMTString() + ';' + secure; video_data.params.volume = volumeValue; video_data.params.speed = speedValue; } player.on('ratechange', function () { updateCookie(null, player.playbackRate()); if (isMobile()) { player.mobileUi({ touchControls: { seekSeconds: 5 * player.playbackRate() } }); } }); player.on('volumechange', function () { updateCookie(Math.ceil(player.volume() * 100), null); }); player.on('waiting', function () { if (player.playbackRate() > 1 && player.liveTracker.isLive() && player.liveTracker.atLiveEdge()) { console.info('Player has caught up to source, resetting playbackRate'); player.playbackRate(1); } }); if (video_data.premiere_timestamp && Math.round(new Date() / 1000) < video_data.premiere_timestamp) { player.getChild('bigPlayButton').hide(); } if (video_data.params.save_player_pos) { console.log('[Invidious Debug] Setting up player position save/restore...'); var lastUpdated = 0; var save_video_time = function(time) { const all_video_times = get_all_video_times(); all_video_times[video_data.id] = time; helpers.storage.set(save_player_pos_key, all_video_times); } player.on('timeupdate', function () { const raw = player.currentTime(); const time = Math.floor(raw); if(lastUpdated !== time && raw <= video_data.length_seconds - 15) { save_video_time(time); lastUpdated = time; } }); } else remove_all_video_times(); if (video_data.params.autoplay) { console.log('[Invidious Debug] Handling autoplay setup...'); var bpb = player.getChild('bigPlayButton'); bpb.hide(); player.ready(function () { console.log('[Invidious Debug] Player ready for autoplay.'); new Promise(function (resolve, reject) { setTimeout(function () {resolve(1);}, 1); }).then(function (result) { var promise = player.play(); if (promise !== undefined) { promise.then(function () { console.log('[Invidious Debug] Autoplay successful.'); }).catch(function (error) { console.error('[Invidious Debug] Autoplay FAILED:', error); bpb.show(); }); } else { console.log('[Invidious Debug] Autoplay started (no promise returned).'); } }); }); } if (!video_data.params.listen && video_data.params.quality === 'dash') { console.log('[Invidious Debug] Initializing httpSourceSelector...'); try { player.httpSourceSelector(); console.log('[Invidious Debug] httpSourceSelector initialized.'); if (video_data.params.quality_dash !== 'auto') { console.log('[Invidious Debug] Setting DASH quality:', video_data.params.quality_dash); player.ready(function () { player.on('loadedmetadata', function () { try { const qualityLevels = Array.from(player.qualityLevels()).sort(function (a, b) {return a.height - b.height;}); let targetQualityLevel; switch (video_data.params.quality_dash) { case 'best': targetQualityLevel = qualityLevels.length - 1; break; case 'worst': targetQualityLevel = 0; break; default: const targetHeight = parseInt(video_data.params.quality_dash); for (let i = 0; i < qualityLevels.length; i++) { if (qualityLevels[i].height <= targetHeight) targetQualityLevel = i; else break; } } qualityLevels.forEach(function (level, index) { level.enabled = (index === targetQualityLevel); }); console.log('[Invidious Debug] DASH quality levels set.'); } catch (e) { console.error('[Invidious Debug] Error setting DASH quality levels:', e); } }); }); } } catch (e) { console.error('[Invidious Debug] httpSourceSelector FAILED:', e); } } console.log('[Invidious Debug] Initializing vttThumbnails...'); try { player.vttThumbnails({ src: '/api/v1/storyboards/' + video_data.id + '?height=90', showTimestamp: true }); console.log('[Invidious Debug] vttThumbnails initialized.'); } catch (e) { console.error('[Invidious Debug] vttThumbnails FAILED:', e); } // Enable annotations if (!video_data.params.listen && video_data.params.annotations) { console.log('[Invidious Debug] Initializing annotations...'); addEventListener('load', function (e) { addEventListener('__ar_annotation_click', function (e) { const url = e.detail.url, target = e.detail.target, seconds = e.detail.seconds; var path = new URL(url); if (path.href.startsWith('https://www.youtube.com/watch?') && seconds) { path.search += '&t=' + seconds; } path = path.pathname + path.search; if (target === 'current') { location.href = path; } else if (target === 'new') { open(path, '_blank'); } }); helpers.xhr('GET', '/api/v1/annotations/' + video_data.id, { responseType: 'text', timeout: 60000 }, { on200: function (response) { var video_container = document.getElementById('player'); videojs.registerPlugin('youtubeAnnotationsPlugin', youtubeAnnotationsPlugin); if (player.paused()) { player.one('play', function (event) { player.youtubeAnnotationsPlugin({ annotationXml: response, videoContainer: video_container }); }); } else { player.youtubeAnnotationsPlugin({ annotationXml: response, videoContainer: video_container }); } console.log('[Invidious Debug] Annotations initialized.'); } }); }); } var shareOptions = { socials: ['fbFeed', 'tw', 'reddit', 'email'], get url() { return addCurrentTimeToURL(short_url); }, title: player_data.title, description: player_data.description, image: player_data.thumbnail, get embedCode() { // Single quotes inside here required. HTML inserted as is into value attribute of input return ""; } }; if (location.pathname.startsWith('/embed/')) { console.log('[Invidious Debug] Initializing overlay...'); try { var overlay_content = '