mirror of
				https://github.com/iv-org/invidious.git
				synced 2025-10-31 04:32:02 +00:00 
			
		
		
		
	 e6b4e12689
			
		
	
	e6b4e12689
	
	
	
		
			
			* js: add support for keydown events This will modify the player behavior even if the player element is unfocused. Based on the YouTube key bindings, allow to - toggle playback with space and 'k' key - increase and decrease player volume with up / down arrow key - mute and unmute player with 'm' key - jump forwards and backwards by 5 seconds with right / left arrow key - jump forwards and backwards by 10 seconds with 'l' / 'j' key - set video progress with number keys 0–9 - toggle captions with 'c' key - toggle fullscreen mode with 'f' key - play next video with 'N' key - increase and decrease playback speed with '>' / '<' key * js: remove unused dependency 'videojs.hotkeys.min.js' Support for controlling the player volume by scrolling over it is still retained by copying over the relevant code part from the aforementioned library.
		
			
				
	
	
		
			454 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			454 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| String.prototype.supplant = function (o) {
 | |
|     return this.replace(/{([^{}]*)}/g, function (a, b) {
 | |
|         var r = o[b];
 | |
|         return typeof r === 'string' || typeof r === 'number' ? r : a;
 | |
|     });
 | |
| }
 | |
| 
 | |
| function toggle_parent(target) {
 | |
|     body = target.parentNode.parentNode.children[1];
 | |
|     if (body.style.display === null || body.style.display === '') {
 | |
|         target.innerHTML = '[ + ]';
 | |
|         body.style.display = 'none';
 | |
|     } else {
 | |
|         target.innerHTML = '[ - ]';
 | |
|         body.style.display = '';
 | |
|     }
 | |
| }
 | |
| 
 | |
| function toggle_comments(event) {
 | |
|     var target = event.target;
 | |
|     body = target.parentNode.parentNode.parentNode.children[1];
 | |
|     if (body.style.display === null || body.style.display === '') {
 | |
|         target.innerHTML = '[ + ]';
 | |
|         body.style.display = 'none';
 | |
|     } else {
 | |
|         target.innerHTML = '[ - ]';
 | |
|         body.style.display = '';
 | |
|     }
 | |
| }
 | |
| 
 | |
| function swap_comments(event) {
 | |
|     var source = event.target.getAttribute('data-comments');
 | |
| 
 | |
|     if (source === 'youtube') {
 | |
|         get_youtube_comments();
 | |
|     } else if (source === 'reddit') {
 | |
|         get_reddit_comments();
 | |
|     }
 | |
| }
 | |
| 
 | |
| function hide_youtube_replies(event) {
 | |
|     var target = event.target;
 | |
| 
 | |
|     sub_text = target.getAttribute('data-inner-text');
 | |
|     inner_text = target.getAttribute('data-sub-text');
 | |
| 
 | |
|     body = target.parentNode.parentNode.children[1];
 | |
|     body.style.display = 'none';
 | |
| 
 | |
|     target.innerHTML = sub_text;
 | |
|     target.onclick = show_youtube_replies;
 | |
|     target.setAttribute('data-inner-text', inner_text);
 | |
|     target.setAttribute('data-sub-text', sub_text);
 | |
| }
 | |
| 
 | |
| function show_youtube_replies(event) {
 | |
|     var target = event.target;
 | |
| 
 | |
|     sub_text = target.getAttribute('data-inner-text');
 | |
|     inner_text = target.getAttribute('data-sub-text');
 | |
| 
 | |
|     body = target.parentNode.parentNode.children[1];
 | |
|     body.style.display = '';
 | |
| 
 | |
|     target.innerHTML = sub_text;
 | |
|     target.onclick = hide_youtube_replies;
 | |
|     target.setAttribute('data-inner-text', inner_text);
 | |
|     target.setAttribute('data-sub-text', sub_text);
 | |
| }
 | |
| 
 | |
| var continue_button = document.getElementById('continue');
 | |
| if (continue_button) {
 | |
|     continue_button.onclick = continue_autoplay;
 | |
| }
 | |
| 
 | |
| function next_video() {
 | |
|     var url = new URL('https://example.com/watch?v=' + video_data.next_video);
 | |
| 
 | |
|     if (video_data.params.autoplay || video_data.params.continue_autoplay) {
 | |
|         url.searchParams.set('autoplay', '1');
 | |
|     }
 | |
| 
 | |
|     if (video_data.params.listen !== video_data.preferences.listen) {
 | |
|         url.searchParams.set('listen', video_data.params.listen);
 | |
|     }
 | |
| 
 | |
|     if (video_data.params.speed !== video_data.preferences.speed) {
 | |
|         url.searchParams.set('speed', video_data.params.speed);
 | |
|     }
 | |
| 
 | |
|     if (video_data.params.local !== video_data.preferences.local) {
 | |
|         url.searchParams.set('local', video_data.params.local);
 | |
|     }
 | |
| 
 | |
|     url.searchParams.set('continue', '1');
 | |
|     location.assign(url.pathname + url.search);
 | |
| }
 | |
| 
 | |
| function continue_autoplay(event) {
 | |
|     if (event.target.checked) {
 | |
|         player.on('ended', function () {
 | |
|             next_video();
 | |
|         });
 | |
|     } else {
 | |
|         player.off('ended');
 | |
|     }
 | |
| }
 | |
| 
 | |
| function number_with_separator(val) {
 | |
|     while (/(\d+)(\d{3})/.test(val.toString())) {
 | |
|         val = val.toString().replace(/(\d+)(\d{3})/, '$1' + ',' + '$2');
 | |
|     }
 | |
|     return val;
 | |
| }
 | |
| 
 | |
| function get_playlist(plid, retries) {
 | |
|     if (retries == undefined) retries = 5;
 | |
|     playlist = document.getElementById('playlist');
 | |
| 
 | |
|     if (retries <= 0) {
 | |
|         console.log('Failed to pull playlist');
 | |
|         playlist.innerHTML = '';
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     playlist.innerHTML = ' \
 | |
|         <h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3> \
 | |
|         <hr>'
 | |
| 
 | |
|     if (plid.startsWith('RD')) {
 | |
|         var plid_url = '/api/v1/mixes/' + plid +
 | |
|             '?continuation=' + video_data.id +
 | |
|             '&format=html&hl=' + video_data.preferences.locale;
 | |
|     } else {
 | |
|         var plid_url = '/api/v1/playlists/' + plid +
 | |
|             '?continuation=' + video_data.id +
 | |
|             '&format=html&hl=' + video_data.preferences.locale;
 | |
|     }
 | |
| 
 | |
|     var xhr = new XMLHttpRequest();
 | |
|     xhr.responseType = 'json';
 | |
|     xhr.timeout = 10000;
 | |
|     xhr.open('GET', plid_url, true);
 | |
| 
 | |
|     xhr.onreadystatechange = function () {
 | |
|         if (xhr.readyState == 4) {
 | |
|             if (xhr.status == 200) {
 | |
|                 playlist.innerHTML = xhr.response.playlistHtml;
 | |
| 
 | |
|                 if (xhr.response.nextVideo) {
 | |
|                     player.on('ended', function () {
 | |
|                         var url = new URL('https://example.com/watch?v=' + xhr.response.nextVideo);
 | |
| 
 | |
|                         if (video_data.params.autoplay || video_data.params.continue_autoplay) {
 | |
|                             url.searchParams.set('autoplay', '1');
 | |
|                         }
 | |
| 
 | |
|                         if (video_data.params.listen !== video_data.preferences.listen) {
 | |
|                             url.searchParams.set('listen', video_data.params.listen);
 | |
|                         }
 | |
| 
 | |
|                         if (video_data.params.speed !== video_data.preferences.speed) {
 | |
|                             url.searchParams.set('speed', video_data.params.speed);
 | |
|                         }
 | |
| 
 | |
|                         if (video_data.params.local !== video_data.preferences.local) {
 | |
|                             url.searchParams.set('local', video_data.params.local);
 | |
|                         }
 | |
| 
 | |
|                         url.searchParams.set('list', plid);
 | |
|                         location.assign(url.pathname + url.search);
 | |
|                     });
 | |
|                 }
 | |
|             } else {
 | |
|                 playlist.innerHTML = '';
 | |
|                 document.getElementById('continue').style.display = '';
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     xhr.onerror = function () {
 | |
|         playlist = document.getElementById('playlist');
 | |
|         playlist.innerHTML =
 | |
|             '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3><hr>';
 | |
| 
 | |
|         console.log('Pulling playlist timed out... ' + retries + '/5');
 | |
|         setTimeout(function () { get_playlist(plid, retries - 1) }, 1000);
 | |
|     }
 | |
| 
 | |
|     xhr.ontimeout = function () {
 | |
|         playlist = document.getElementById('playlist');
 | |
|         playlist.innerHTML =
 | |
|             '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3><hr>';
 | |
| 
 | |
|         console.log('Pulling playlist timed out... ' + retries + '/5');
 | |
|         get_playlist(plid, retries - 1);
 | |
|     }
 | |
| 
 | |
|     xhr.send();
 | |
| }
 | |
| 
 | |
| function get_reddit_comments(retries) {
 | |
|     if (retries == undefined) retries = 5;
 | |
|     comments = document.getElementById('comments');
 | |
| 
 | |
|     if (retries <= 0) {
 | |
|         console.log('Failed to pull comments');
 | |
|         comments.innerHTML = '';
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     var fallback = comments.innerHTML;
 | |
|     comments.innerHTML =
 | |
|         '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
 | |
| 
 | |
|     var url = '/api/v1/comments/' + video_data.id +
 | |
|         '?source=reddit&format=html' +
 | |
|         '&hl=' + video_data.preferences.locale;
 | |
|     var xhr = new XMLHttpRequest();
 | |
|     xhr.responseType = 'json';
 | |
|     xhr.timeout = 10000;
 | |
|     xhr.open('GET', url, true);
 | |
| 
 | |
|     xhr.onreadystatechange = function () {
 | |
|         if (xhr.readyState == 4) {
 | |
|             if (xhr.status == 200) {
 | |
|                 comments.innerHTML = ' \
 | |
|                 <div> \
 | |
|                     <h3> \
 | |
|                         <a href="javascript:void(0)">[ - ]</a> \
 | |
|                         {title} \
 | |
|                     </h3> \
 | |
|                     <p> \
 | |
|                         <b> \
 | |
|                             <a href="javascript:void(0)" data-comments="youtube"> \
 | |
|                                 {youtubeCommentsText} \
 | |
|                             </a> \
 | |
|                         </b> \
 | |
|                     </p> \
 | |
|                     <b> \
 | |
|                         <a rel="noopener" target="_blank" href="https://reddit.com{permalink}">{redditPermalinkText}</a> \
 | |
|                     </b> \
 | |
|                 </div> \
 | |
|                 <div>{contentHtml}</div> \
 | |
|                 <hr>'.supplant({
 | |
|                     title: xhr.response.title,
 | |
|                     youtubeCommentsText: video_data.youtube_comments_text,
 | |
|                     redditPermalinkText: video_data.reddit_permalink_text,
 | |
|                     permalink: xhr.response.permalink,
 | |
|                     contentHtml: xhr.response.contentHtml
 | |
|                 });
 | |
| 
 | |
|                 comments.children[0].children[0].children[0].onclick = toggle_comments;
 | |
|                 comments.children[0].children[1].children[0].onclick = swap_comments;
 | |
|             } else {
 | |
|                 if (video_data.params.comments[1] === 'youtube') {
 | |
|                     console.log('Pulling comments failed... ' + retries + '/5');
 | |
|                     setTimeout(function () { get_youtube_comments(retries - 1) }, 1000);
 | |
|                 } else {
 | |
|                     comments.innerHTML = fallback;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     xhr.onerror = function () {
 | |
|         console.log('Pulling comments failed... ' + retries + '/5');
 | |
|         setInterval(function () { get_reddit_comments(retries - 1) }, 1000);
 | |
|     }
 | |
| 
 | |
|     xhr.ontimeout = function () {
 | |
|         console.log('Pulling comments failed... ' + retries + '/5');
 | |
|         get_reddit_comments(retries - 1);
 | |
|     }
 | |
| 
 | |
|     xhr.send();
 | |
| }
 | |
| 
 | |
| function get_youtube_comments(retries) {
 | |
|     if (retries == undefined) retries = 5;
 | |
|     comments = document.getElementById('comments');
 | |
| 
 | |
|     if (retries <= 0) {
 | |
|         console.log('Failed to pull comments');
 | |
|         comments.innerHTML = '';
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     var fallback = comments.innerHTML;
 | |
|     comments.innerHTML =
 | |
|         '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
 | |
| 
 | |
|     var url = '/api/v1/comments/' + video_data.id +
 | |
|         '?format=html' +
 | |
|         '&hl=' + video_data.preferences.locale +
 | |
|         '&thin_mode=' + video_data.preferences.thin_mode;
 | |
|     var xhr = new XMLHttpRequest();
 | |
|     xhr.responseType = 'json';
 | |
|     xhr.timeout = 10000;
 | |
|     xhr.open('GET', url, true);
 | |
| 
 | |
|     xhr.onreadystatechange = function () {
 | |
|         if (xhr.readyState == 4) {
 | |
|             if (xhr.status == 200) {
 | |
|                 comments.innerHTML = ' \
 | |
|                 <div> \
 | |
|                     <h3> \
 | |
|                         <a href="javascript:void(0)">[ - ]</a> \
 | |
|                         {commentsText}  \
 | |
|                     </h3> \
 | |
|                     <b> \
 | |
|                         <a href="javascript:void(0)" data-comments="reddit"> \
 | |
|                             {redditComments} \
 | |
|                         </a> \
 | |
|                     </b> \
 | |
|                 </div> \
 | |
|                 <div>{contentHtml}</div> \
 | |
|                 <hr>'.supplant({
 | |
|                     contentHtml: xhr.response.contentHtml,
 | |
|                     redditComments: video_data.reddit_comments_text,
 | |
|                     commentsText: video_data.comments_text.supplant(
 | |
|                         { commentCount: number_with_separator(xhr.response.commentCount) }
 | |
|                     )
 | |
|                 });
 | |
| 
 | |
|                 comments.children[0].children[0].children[0].onclick = toggle_comments;
 | |
|                 comments.children[0].children[1].children[0].onclick = swap_comments;
 | |
|             } else {
 | |
|                 if (video_data.params.comments[1] === 'youtube') {
 | |
|                     setTimeout(function () { get_youtube_comments(retries - 1) }, 1000);
 | |
|                 } else {
 | |
|                     comments.innerHTML = '';
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     xhr.onerror = function () {
 | |
|         comments.innerHTML =
 | |
|             '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
 | |
|         console.log('Pulling comments failed... ' + retries + '/5');
 | |
|         setInterval(function () { get_youtube_comments(retries - 1) }, 1000);
 | |
|     }
 | |
| 
 | |
|     xhr.ontimeout = function () {
 | |
|         comments.innerHTML =
 | |
|             '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
 | |
|         console.log('Pulling comments failed... ' + retries + '/5');
 | |
|         get_youtube_comments(retries - 1);
 | |
|     }
 | |
| 
 | |
|     xhr.send();
 | |
| }
 | |
| 
 | |
| function get_youtube_replies(target, load_more) {
 | |
|     var continuation = target.getAttribute('data-continuation');
 | |
| 
 | |
|     var body = target.parentNode.parentNode;
 | |
|     var fallback = body.innerHTML;
 | |
|     body.innerHTML =
 | |
|         '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
 | |
| 
 | |
|     var url = '/api/v1/comments/' + video_data.id +
 | |
|         '?format=html' +
 | |
|         '&hl=' + video_data.preferences.locale +
 | |
|         '&thin_mode=' + video_data.preferences.thin_mode +
 | |
|         '&continuation=' + continuation;
 | |
|     var xhr = new XMLHttpRequest();
 | |
|     xhr.responseType = 'json';
 | |
|     xhr.timeout = 10000;
 | |
|     xhr.open('GET', url, true);
 | |
| 
 | |
|     xhr.onreadystatechange = function () {
 | |
|         if (xhr.readyState == 4) {
 | |
|             if (xhr.status == 200) {
 | |
|                 if (load_more) {
 | |
|                     body = body.parentNode.parentNode;
 | |
|                     body.removeChild(body.lastElementChild);
 | |
|                     body.innerHTML += xhr.response.contentHtml;
 | |
|                 } else {
 | |
|                     body.removeChild(body.lastElementChild);
 | |
| 
 | |
|                     var p = document.createElement('p');
 | |
|                     var a = document.createElement('a');
 | |
|                     p.appendChild(a);
 | |
| 
 | |
|                     a.href = 'javascript:void(0)';
 | |
|                     a.onclick = hide_youtube_replies;
 | |
|                     a.setAttribute('data-sub-text', video_data.hide_replies_text);
 | |
|                     a.setAttribute('data-inner-text', video_data.show_replies_text);
 | |
|                     a.innerText = video_data.hide_replies_text;
 | |
| 
 | |
|                     var div = document.createElement('div');
 | |
|                     div.innerHTML = xhr.response.contentHtml;
 | |
| 
 | |
|                     body.appendChild(p);
 | |
|                     body.appendChild(div);
 | |
|                 }
 | |
|             } else {
 | |
|                 body.innerHTML = fallback;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     xhr.ontimeout = function () {
 | |
|         console.log('Pulling comments failed.');
 | |
|         body.innerHTML = fallback;
 | |
|     }
 | |
| 
 | |
|     xhr.send();
 | |
| }
 | |
| 
 | |
| if (video_data.play_next) {
 | |
|     player.on('ended', function () {
 | |
|         var url = new URL('https://example.com/watch?v=' + video_data.next_video);
 | |
| 
 | |
|         if (video_data.params.autoplay || video_data.params.continue_autoplay) {
 | |
|             url.searchParams.set('autoplay', '1');
 | |
|         }
 | |
| 
 | |
|         if (video_data.params.listen !== video_data.preferences.listen) {
 | |
|             url.searchParams.set('listen', video_data.params.listen);
 | |
|         }
 | |
| 
 | |
|         if (video_data.params.speed !== video_data.preferences.speed) {
 | |
|             url.searchParams.set('speed', video_data.params.speed);
 | |
|         }
 | |
| 
 | |
|         if (video_data.params.local !== video_data.preferences.local) {
 | |
|             url.searchParams.set('local', video_data.params.local);
 | |
|         }
 | |
| 
 | |
|         url.searchParams.set('continue', '1');
 | |
|         location.assign(url.pathname + url.search);
 | |
|     });
 | |
| }
 | |
| 
 | |
| if (video_data.plid) {
 | |
|     get_playlist(video_data.plid);
 | |
| }
 | |
| 
 | |
| if (video_data.params.comments[0] === 'youtube') {
 | |
|     get_youtube_comments();
 | |
| } else if (video_data.params.comments[0] === 'reddit') {
 | |
|     get_reddit_comments();
 | |
| } else if (video_data.params.comments[1] === 'youtube') {
 | |
|     get_youtube_comments();
 | |
| } else if (video_data.params.comments[1] === 'reddit') {
 | |
|     get_reddit_comments();
 | |
| } else {
 | |
|     comments = document.getElementById('comments');
 | |
|     comments.innerHTML = '';
 | |
| }
 |