feat: autoplay for related videos

Add autoplay support for related videos. Move the playlist autoplay code
into this shared script. Add the SameSite=Strict attribute to the
autoplay cookie due to Firefox soon rejecting cookies which use
SameSite=None without the secure attribute.

Closes: #50

Signed-off-by: Jesús <heckyel@hyperbola.info>
This commit is contained in:
Umimaso 2021-06-21 00:56:26 +01:00 committed by Jesús
parent ff7aae05c4
commit 2d1794889a
No known key found for this signature in database
GPG Key ID: F6EE7BC59A315766
2 changed files with 152 additions and 118 deletions

View File

@ -345,6 +345,14 @@ label[for=options-toggle-cbox] {
.side-videos { grid-area: side-videos; }
.side-videos .related-autoplay {
list-style: none;
display: grid;
grid-template-columns: repeat(2, auto);
justify-content: start;
grid-column-gap: 0.5rem;
}
/* playlist items */
.side-videos .site-playlist {
border-style: solid;

View File

@ -150,124 +150,42 @@
<!-- playlist -->
{% if playlist %}
<div class="site-playlist">
<div class="playlist-header">
<a href="{{ playlist['url'] }}" title="{{ playlist['title'] }}"><h3>{{ playlist['title'] }}</h3></a>
<ul class="playlist-metadata">
<li>Autoplay: <input type="checkbox" id="autoplay-toggle"></li>
{% if playlist['current_index'] is none %}
<li>[Error!]/{{ playlist['video_count'] }}</li>
{% else %}
<li>{{ playlist['current_index']+1 }}/{{ playlist['video_count'] }}</li>
{% endif %}
<li><a href="{{ playlist['author_url'] }}" title="{{ playlist['author'] }}">{{ playlist['author'] }}</a></li>
</ul>
</div>
<nav class="playlist-videos">
{% for info in playlist['items'] %}
{# non-lazy load for 5 videos surrounding current video #}
{# for non-js browsers or old such that IntersectionObserver doesn't work #}
{# -10 is sentinel to not load anything if there's no current_index for some reason #}
{% if (playlist.get('current_index', -10) - loop.index0)|abs is lt(5) %}
{{ common_elements.item(info, include_badges=false, lazy_load=false) }}
{% else %}
{{ common_elements.item(info, include_badges=false, lazy_load=true) }}
{% endif %}
{% endfor %}
</nav>
{% if playlist['current_index'] is not none %}
<script>
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
(function main() {
// from https://stackoverflow.com/a/6969486
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
let playability_error = {{ 'true' if playability_error else 'false' }};
let playlist_id = {{ playlist['id']|tojson }};
playlist_id = escapeRegExp(playlist_id);
// read cookies on whether to autoplay thru playlist
// pain in the ass:
// https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie
let cookieValue = document.cookie.replace(new RegExp(
'(?:(?:^|.*;\\s*)autoplay_' + playlist_id + '\\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
checkbox.addEventListener( 'change', function() {
if(this.checked) {
autoplayEnabled = 1;
document.cookie = 'autoplay_' + playlist_id + '=1';
} else {
autoplayEnabled = 0;
document.cookie = 'autoplay_' + playlist_id + '=0';
}
});
const vid = document.getElementById('js-video-player');
if(!playability_error){
if(autoplayEnabled){
vid.play();
}
}
let currentIndex = {{ playlist['current_index']|tojson }};
{% if playlist['current_index']+1 == playlist['items']|length %}
let nextVideoUrl = null;
{% else %}
let nextVideoUrl = {{ (playlist['items'][playlist['current_index']+1]['url'])|tojson }};
{% endif %}
let nextVideoDelay = 1000;
// scroll playlist to proper position
let pl = document.querySelector('.playlist-videos');
// item height + gap == 100
pl.scrollTop = 100*currentIndex;
// go to next video when video ends
// https://stackoverflow.com/a/2880950
if(nextVideoUrl){
if(playability_error){
videoEnded();
} else {
vid.addEventListener('ended', videoEnded, false);
}
function nextVideo(){
if(autoplayEnabled){
window.location.href = nextVideoUrl;
}
}
function videoEnded(e) {
window.setTimeout(nextVideo, nextVideoDelay);
}
}
}());
// @license-end
</script>
{% endif %}
{% if playlist['id'] is not none %}
<script>
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
(function main() {
<div class="site-playlist">
<div class="playlist-header">
<a href="{{ playlist['url'] }}" title="{{ playlist['title'] }}"><h3>{{ playlist['title'] }}</h3></a>
<ul class="playlist-metadata">
<li>Autoplay: <input type="checkbox" id="autoplay-toggle"></li>
{% if playlist['current_index'] is none %}
<li>[Error!]/{{ playlist['video_count'] }}</li>
{% else %}
<li>{{ playlist['current_index']+1 }}/{{ playlist['video_count'] }}</li>
{% endif %}
<li><a href="{{ playlist['author_url'] }}" title="{{ playlist['author'] }}">{{ playlist['author'] }}</a></li>
</ul>
</div>
<nav class="playlist-videos">
{% for info in playlist['items'] %}
{# non-lazy load for 5 videos surrounding current video #}
{# for non-js browsers or old such that IntersectionObserver doesn't work #}
{# -10 is sentinel to not load anything if there's no current_index for some reason #}
{% if (playlist.get('current_index', -10) - loop.index0)|abs is lt(5) %}
{{ common_elements.item(info, include_badges=false, lazy_load=false) }}
{% else %}
{{ common_elements.item(info, include_badges=false, lazy_load=true) }}
{% endif %}
{% endfor %}
</nav>
{% if playlist['id'] is not none %}
<script>
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
// lazy load playlist images
// copied almost verbatim from
// https://css-tricks.com/tips-for-rolling-your-own-lazy-loading/
// IntersectionObserver isn't supported in pre-quantum
// firefox versions, but the alternative of making it
// manually is a performance drain, so oh well
let observer = new IntersectionObserver(lazyLoad, {
var observer = new IntersectionObserver(lazyLoad, {
// where in relation to the edge of the viewport, we are observing
rootMargin: "100px",
// how much of the element needs to have intersected
@ -287,15 +205,123 @@
};
// Tell our observer to observe all img elements with a "lazy" class
let lazyImages = document.querySelectorAll('img.lazy');
var lazyImages = document.querySelectorAll('img.lazy');
lazyImages.forEach(img => {
observer.observe(img);
});
}());
// @license-end
</script>
{% endif %}
</div>
// @license-end
</script>
{% endif %}
</div>
{% elif settings.related_videos_mode != 0 %}
<li class="related-autoplay">Autoplay: <input type="checkbox" id="autoplay-toggle"></li>
{% endif %}
{% if settings.related_videos_mode != 0 or playlist %}
<script>
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
var playability_error = {{ 'true' if playability_error else 'false' }};
{% if playlist and playlist['current_index'] is not none %}
{% set isPlaylist = true %}
{% endif %}
{% if isPlaylist %}
// from https://stackoverflow.com/a/6969486
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
var playlist_id = {{ playlist['id']|tojson }};
playlist_id = escapeRegExp(playlist_id);
{% endif %}
// read cookies on whether to autoplay
// pain in the ass:
// https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie
{% if isPlaylist %}
var cookieValue = document.cookie.replace(new RegExp(
'(?:(?:^|.*;\\s*)autoplay_' + playlist_id + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1');
{% else %}
var cookieValue = document.cookie.replace(new RegExp(
'(?:(?:^|.*;\\s*)autoplay\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1');
{% endif %}
var autoplayEnabled = 0;
if(cookieValue.length === 0){
autoplayEnabled = 0;
} else {
autoplayEnabled = Number(cookieValue);
}
// check the checkbox if autoplay is on
var checkbox = document.querySelector('#autoplay-toggle');
if(autoplayEnabled){
checkbox.checked = true;
}
// listen for checkbox to turn autoplay on and off
var cookie = 'autoplay'
{% if isPlaylist %}
cookie += '_' + playlist_id;
{% endif %}
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
var vid = document.querySelector('video');
if(autoplayEnabled){
vid.play();
}
}
// determine next video url
{% if isPlaylist %}
var currentIndex = {{ playlist['current_index']|tojson }};
{% if playlist['current_index']+1 == playlist['items']|length %}
var nextVideoUrl = null;
{% else %}
var nextVideoUrl = {{ (playlist['items'][playlist['current_index']+1]['url'])|tojson }};
{% endif %}
// scroll playlist to proper position
// item height + gap == 100
var pl = document.querySelector('.playlist-videos');
pl.scrollTop = 100*currentIndex;
{% else %}
{% if related|length == 0 %}
var nextVideoUrl = null;
{% else %}
var nextVideoUrl = {{ (related[0]['url'])|tojson }};
{% endif %}
{% endif %}
var nextVideoDelay = 1000;
// go to next video when video ends
// https://stackoverflow.com/a/2880950
if (nextVideoUrl) {
if(playability_error){
videoEnded();
} else {
vid.addEventListener('ended', videoEnded, false);
}
function nextVideo(){
if(autoplayEnabled){
window.location.href = nextVideoUrl;
}
}
function videoEnded(e) {
window.setTimeout(nextVideo, nextVideoDelay);
}
}
// @license-end
</script>
{% endif %}
<!-- /playlist -->