Add initial video player implementation with controls and features
Some checks failed
Build and release container directly from master / release (push) Failing after 34s

This commit is contained in:
2025-05-05 10:56:06 -04:00
parent d1bc15b8bf
commit f50559ac18

View File

@@ -1,6 +1,8 @@
'use strict'; 'use strict';
console.log('[Invidious Debug] player.js start');
var player_data = JSON.parse(document.getElementById('player_data').textContent); var player_data = JSON.parse(document.getElementById('player_data').textContent);
var video_data = JSON.parse(document.getElementById('video_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 = { var options = {
liveui: true, liveui: true,
@@ -50,9 +52,19 @@ videojs.Vhs.xhr.beforeRequest = function(options) {
return options; return options;
}; };
var player = videojs('player', 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 () { player.on('error', function () {
console.error('[Invidious Debug] Player error event triggered:', player.error());
if (video_data.params.quality === 'dash') return; if (video_data.params.quality === 'dash') return;
var localNotDisabled = ( var localNotDisabled = (
@@ -70,6 +82,7 @@ player.on('error', function () {
return source; return source;
})); }));
} else if (reloadMakesSense) { } else if (reloadMakesSense) {
console.log('[Invidious Debug] Player error: Attempting reload in 5 seconds.');
setTimeout(function () { setTimeout(function () {
console.warn('An error occurred in the player, reloading...'); console.warn('An error occurred in the player, reloading...');
@@ -86,13 +99,21 @@ player.on('error', function () {
player.playbackRate(playbackRate); player.playbackRate(playbackRate);
if (!paused) player.play(); if (!paused) player.play();
}, 5000); }, 5000);
} else {
console.log('[Invidious Debug] Player error: No specific action taken.');
} }
}); });
if (video_data.params.quality === 'dash') { if (video_data.params.quality === 'dash') {
player.reloadSourceOnError({ console.log('[Invidious Debug] Initializing reloadSourceOnError...');
errorInterval: 10 try {
}); player.reloadSourceOnError({
errorInterval: 10
});
console.log('[Invidious Debug] reloadSourceOnError initialized.');
} catch (e) {
console.error('[Invidious Debug] reloadSourceOnError FAILED:', e);
}
} }
/** /**
@@ -162,116 +183,6 @@ player.on('timeupdate', function () {
} }
}); });
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 "<iframe id='ivplayer' width='640' height='360' src='" +
addCurrentTimeToURL(embed_url) + "' style='border:none;'></iframe>";
}
};
if (location.pathname.startsWith('/embed/')) {
var overlay_content = '<h1><a rel="noopener" target="_blank" href="' + location.origin + '/watch?v=' + video_data.id + '">' + player_data.title + '</a></h1>';
player.overlay({
overlays: [
{ start: 'loadstart', content: overlay_content, end: 'playing', align: 'top'},
{ start: 'pause', content: overlay_content, end: 'playing', align: 'top'}
]
});
}
// Detect mobile users and initialize mobileUi for better UX
// Detection code taken from https://stackoverflow.com/a/20293441
function isMobile() {
try{ document.createEvent('TouchEvent'); return true; }
catch(e){ return false; }
}
if (isMobile()) {
player.mobileUi({ touchControls: { seekSeconds: 5 * player.playbackRate() } });
var buttons = ['playToggle', 'volumePanel', 'captionsButton'];
if (!video_data.params.listen && video_data.params.quality === 'dash') buttons.push('audioTrackButton');
if (video_data.params.listen || video_data.params.quality !== 'dash') buttons.push('qualitySelector');
// Create new control bar object for operation buttons
const ControlBar = videojs.getComponent('controlBar');
let operations_bar = new ControlBar(player, {
children: [],
playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0]
});
buttons.slice(1).forEach(function (child) {operations_bar.addChild(child);});
// Remove operation buttons from primary control bar
var primary_control_bar = player.getChild('controlBar');
buttons.forEach(function (child) {primary_control_bar.removeChild(child);});
var operations_bar_element = operations_bar.el();
operations_bar_element.classList.add('mobile-operations-bar');
player.addChild(operations_bar);
// Playback menu doesn't work when it's initialized outside of the primary control bar
var playback_element = document.getElementsByClassName('vjs-playback-rate')[0];
operations_bar_element.append(playback_element);
// The share and http source selector element can't be fetched till the players ready.
player.one('playing', function () {
var share_element = document.getElementsByClassName('vjs-share-control')[0];
operations_bar_element.append(share_element);
if (!video_data.params.listen && video_data.params.quality === 'dash') {
var http_source_selector = document.getElementsByClassName('vjs-http-source-selector vjs-menu-button')[0];
operations_bar_element.append(http_source_selector);
}
});
}
// Enable VR video support
if (!video_data.params.listen && video_data.vr && video_data.params.vr_mode) {
player.crossOrigin('anonymous');
switch (video_data.projection_type) {
case 'EQUIRECTANGULAR':
player.vr({projection: 'equirectangular'});
default: // Should only be 'MESH' but we'll use this as a fallback.
player.vr({projection: 'EAC'});
}
}
// Add markers
if (video_data.params.video_start > 0 || video_data.params.video_end > 0) {
var markers = [{ time: video_data.params.video_start, text: 'Start' }];
if (video_data.params.video_end < 0) {
markers.push({ time: video_data.length_seconds - 0.5, text: 'End' });
} else {
markers.push({ time: video_data.params.video_end, text: 'End' });
}
player.markers({
onMarkerReached: function (marker) {
if (marker.text === 'End')
player.loop() ? player.markers.prev('Start') : player.pause();
},
markers: markers
});
player.currentTime(video_data.params.video_start);
}
player.volume(video_data.params.volume / 100);
player.playbackRate(video_data.params.speed);
/** /**
* Method for getting the contents of a cookie * Method for getting the contents of a cookie
* *
@@ -351,16 +262,12 @@ if (video_data.premiere_timestamp && Math.round(new Date() / 1000) < video_data.
} }
if (video_data.params.save_player_pos) { if (video_data.params.save_player_pos) {
const url = new URL(location); console.log('[Invidious Debug] Setting up player position save/restore...');
const hasTimeParam = url.searchParams.has('t'); var lastUpdated = 0;
const rememberedTime = get_video_time(); var save_video_time = function(time) {
let lastUpdated = 0; const all_video_times = get_all_video_times();
all_video_times[video_data.id] = time;
if(!hasTimeParam) { helpers.storage.set(save_player_pos_key, all_video_times);
if (rememberedTime >= video_data.length_seconds - 20)
set_seconds_after_start(0);
else
set_seconds_after_start(rememberedTime);
} }
player.on('timeupdate', function () { player.on('timeupdate', function () {
@@ -376,10 +283,12 @@ if (video_data.params.save_player_pos) {
else remove_all_video_times(); else remove_all_video_times();
if (video_data.params.autoplay) { if (video_data.params.autoplay) {
console.log('[Invidious Debug] Handling autoplay setup...');
var bpb = player.getChild('bigPlayButton'); var bpb = player.getChild('bigPlayButton');
bpb.hide(); bpb.hide();
player.ready(function () { player.ready(function () {
console.log('[Invidious Debug] Player ready for autoplay.');
new Promise(function (resolve, reject) { new Promise(function (resolve, reject) {
setTimeout(function () {resolve(1);}, 1); setTimeout(function () {resolve(1);}, 1);
}).then(function (result) { }).then(function (result) {
@@ -387,53 +296,77 @@ if (video_data.params.autoplay) {
if (promise !== undefined) { if (promise !== undefined) {
promise.then(function () { promise.then(function () {
console.log('[Invidious Debug] Autoplay successful.');
}).catch(function (error) { }).catch(function (error) {
console.error('[Invidious Debug] Autoplay FAILED:', error);
bpb.show(); bpb.show();
}); });
} else {
console.log('[Invidious Debug] Autoplay started (no promise returned).');
} }
}); });
}); });
} }
if (!video_data.params.listen && video_data.params.quality === 'dash') { if (!video_data.params.listen && video_data.params.quality === 'dash') {
player.httpSourceSelector(); console.log('[Invidious Debug] Initializing httpSourceSelector...');
try {
player.httpSourceSelector();
console.log('[Invidious Debug] httpSourceSelector initialized.');
if (video_data.params.quality_dash !== 'auto') { if (video_data.params.quality_dash !== 'auto') {
player.ready(function () { console.log('[Invidious Debug] Setting DASH quality:', video_data.params.quality_dash);
player.on('loadedmetadata', function () { player.ready(function () {
const qualityLevels = Array.from(player.qualityLevels()).sort(function (a, b) {return a.height - b.height;}); player.on('loadedmetadata', function () {
let targetQualityLevel; try {
switch (video_data.params.quality_dash) { const qualityLevels = Array.from(player.qualityLevels()).sort(function (a, b) {return a.height - b.height;});
case 'best': let targetQualityLevel;
targetQualityLevel = qualityLevels.length - 1; switch (video_data.params.quality_dash) {
break; case 'best':
case 'worst': targetQualityLevel = qualityLevels.length - 1;
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; 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) {
qualityLevels.forEach(function (level, index) { level.enabled = (index === targetQualityLevel);
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);
} }
} }
player.vttThumbnails({ console.log('[Invidious Debug] Initializing vttThumbnails...');
src: '/api/v1/storyboards/' + video_data.id + '?height=90', try {
showTimestamp: true 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 // Enable annotations
if (!video_data.params.listen && video_data.params.annotations) { if (!video_data.params.listen && video_data.params.annotations) {
console.log('[Invidious Debug] Initializing annotations...');
addEventListener('load', function (e) { addEventListener('load', function (e) {
addEventListener('__ar_annotation_click', function (e) { addEventListener('__ar_annotation_click', function (e) {
const url = e.detail.url, const url = e.detail.url,
@@ -468,9 +401,9 @@ if (!video_data.params.listen && video_data.params.annotations) {
} else { } else {
player.youtubeAnnotationsPlugin({ annotationXml: response, videoContainer: video_container }); player.youtubeAnnotationsPlugin({ annotationXml: response, videoContainer: video_container });
} }
console.log('[Invidious Debug] Annotations initialized.');
} }
}); });
}); });
} }
@@ -732,7 +665,15 @@ addEventListener('keydown', function (e) {
}()); }());
// Since videojs-share can sometimes be blocked, we defer it until last // Since videojs-share can sometimes be blocked, we defer it until last
if (player.share) player.share(shareOptions); if (player.share) {
console.log('[Invidious Debug] Initializing share plugin...');
try {
player.share(shareOptions);
console.log('[Invidious Debug] Share plugin initialized.');
} catch(e) {
console.error('[Invidious Debug] Share plugin FAILED:', e);
}
}
// show the preferred caption by default // show the preferred caption by default
if (player_data.preferred_caption_found) { if (player_data.preferred_caption_found) {
@@ -748,6 +689,7 @@ if (player_data.preferred_caption_found) {
// Safari audio double duration fix // Safari audio double duration fix
if (navigator.vendor === 'Apple Computer, Inc.' && video_data.params.listen) { if (navigator.vendor === 'Apple Computer, Inc.' && video_data.params.listen) {
console.log('[Invidious Debug] Applying Safari listen mode duration fix...');
player.on('loadedmetadata', function () { player.on('loadedmetadata', function () {
player.on('timeupdate', function () { player.on('timeupdate', function () {
if (player.remainingTime() < player.duration() / 2 && player.remainingTime() >= 2) { if (player.remainingTime() < player.duration() / 2 && player.remainingTime() >= 2) {
@@ -759,6 +701,7 @@ if (navigator.vendor === 'Apple Computer, Inc.' && video_data.params.listen) {
// Safari screen timeout on looped video playback fix // Safari screen timeout on looped video playback fix
if (navigator.vendor === 'Apple Computer, Inc.' && !video_data.params.listen && video_data.params.video_loop) { if (navigator.vendor === 'Apple Computer, Inc.' && !video_data.params.listen && video_data.params.video_loop) {
console.log('[Invidious Debug] Applying Safari loop mode screen timeout fix...');
player.loop(false); player.loop(false);
player.ready(function () { player.ready(function () {
player.on('ended', function () { player.on('ended', function () {
@@ -770,19 +713,25 @@ if (navigator.vendor === 'Apple Computer, Inc.' && !video_data.params.listen &&
// Watch on Invidious link // Watch on Invidious link
if (location.pathname.startsWith('/embed/')) { if (location.pathname.startsWith('/embed/')) {
const Button = videojs.getComponent('Button'); console.log('[Invidious Debug] Adding Watch on Invidious button...');
let watch_on_invidious_button = new Button(player); try {
const Button = videojs.getComponent('Button');
let watch_on_invidious_button = new Button(player);
// Create hyperlink for current instance // Create hyperlink for current instance
var redirect_element = document.createElement('a'); var redirect_element = document.createElement('a');
redirect_element.setAttribute('href', location.pathname.replace('/embed/', '/watch?v=')); redirect_element.setAttribute('href', location.pathname.replace('/embed/', '/watch?v='));
redirect_element.appendChild(document.createTextNode('Invidious')); redirect_element.appendChild(document.createTextNode('Invidious'));
watch_on_invidious_button.el().appendChild(redirect_element); watch_on_invidious_button.el().appendChild(redirect_element);
watch_on_invidious_button.addClass('watch-on-invidious'); watch_on_invidious_button.addClass('watch-on-invidious');
var cb = player.getChild('ControlBar'); var cb = player.getChild('ControlBar');
cb.addChild(watch_on_invidious_button); cb.addChild(watch_on_invidious_button);
console.log('[Invidious Debug] Watch on Invidious button added.');
} catch (e) {
console.error('[Invidious Debug] Watch on Invidious button FAILED:', e);
}
} }
addEventListener('DOMContentLoaded', function () { addEventListener('DOMContentLoaded', function () {
@@ -792,3 +741,5 @@ addEventListener('DOMContentLoaded', function () {
changeInstanceLink.href = addCurrentTimeToURL(changeInstanceLink.href); changeInstanceLink.href = addCurrentTimeToURL(changeInstanceLink.href);
}); });
}); });
console.log('[Invidious Debug] player.js finished loading.');