diff --git a/assets/js/_helpers.js b/assets/js/_helpers.js index 8e18169e..30904e8e 100644 --- a/assets/js/_helpers.js +++ b/assets/js/_helpers.js @@ -1,254 +1,273 @@ -'use strict'; +"use strict"; // Contains only auxiliary methods // May be included and executed unlimited number of times without any consequences // Polyfills for IE11 -Array.prototype.find = Array.prototype.find || function (condition) { +Array.prototype.find = + Array.prototype.find || + function (condition) { return this.filter(condition)[0]; -}; + }; -Array.from = Array.from || function (source) { +Array.from = + Array.from || + function (source) { return Array.prototype.slice.call(source); -}; -NodeList.prototype.forEach = NodeList.prototype.forEach || function (callback) { + }; +NodeList.prototype.forEach = + NodeList.prototype.forEach || + function (callback) { Array.from(this).forEach(callback); -}; -String.prototype.includes = String.prototype.includes || function (searchString) { + }; +String.prototype.includes = + String.prototype.includes || + function (searchString) { return this.indexOf(searchString) >= 0; -}; -String.prototype.startsWith = String.prototype.startsWith || function (prefix) { + }; +String.prototype.startsWith = + String.prototype.startsWith || + function (prefix) { return this.substr(0, prefix.length) === prefix; -}; -Math.sign = Math.sign || function(x) { + }; +Math.sign = + Math.sign || + function (x) { x = +x; if (!x) return x; // 0 and NaN return x > 0 ? 1 : -1; -}; -if (!window.hasOwnProperty('HTMLDetailsElement') && !window.hasOwnProperty('mockHTMLDetailsElement')) { - window.mockHTMLDetailsElement = true; - const style = 'details:not([open]) > :not(summary) {display: none}'; - document.head.appendChild(document.createElement('style')).textContent = style; + }; +if ( + !window.hasOwnProperty("HTMLDetailsElement") && + !window.hasOwnProperty("mockHTMLDetailsElement") +) { + window.mockHTMLDetailsElement = true; + const style = "details:not([open]) > :not(summary) {display: none}"; + document.head.appendChild(document.createElement("style")).textContent = + style; - addEventListener('click', function (e) { - if (e.target.nodeName !== 'SUMMARY') return; - const details = e.target.parentElement; - if (details.hasAttribute('open')) - details.removeAttribute('open'); - else - details.setAttribute('open', ''); - }); + addEventListener("click", function (e) { + if (e.target.nodeName !== "SUMMARY") return; + const details = e.target.parentElement; + if (details.hasAttribute("open")) details.removeAttribute("open"); + else details.setAttribute("open", ""); + }); } // Monstrous global variable for handy code // Includes: clamp, xhr, storage.{get,set,remove} window.helpers = window.helpers || { - /** - * https://en.wikipedia.org/wiki/Clamping_(graphics) - * @param {Number} num Source number - * @param {Number} min Low border - * @param {Number} max High border - * @returns {Number} Clamped value - */ - clamp: function (num, min, max) { - if (max < min) { - var t = max; max = min; min = t; // swap max and min + /** + * https://en.wikipedia.org/wiki/Clamping_(graphics) + * @param {Number} num Source number + * @param {Number} min Low border + * @param {Number} max High border + * @returns {Number} Clamped value + */ + clamp: function (num, min, max) { + if (max < min) { + var t = max; + max = min; + min = t; // swap max and min + } + + if (max < num) return max; + if (min > num) return min; + return num; + }, + + /** @private */ + _xhr: function (method, url, options, callbacks) { + const xhr = new XMLHttpRequest(); + xhr.open(method, url); + + // Default options + xhr.responseType = "json"; + xhr.timeout = 10000; + // Default options redefining + if (options.responseType) xhr.responseType = options.responseType; + if (options.timeout) xhr.timeout = options.timeout; + + if (method === "POST") + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + + // better than onreadystatechange because of 404 codes https://stackoverflow.com/a/36182963 + xhr.onloadend = function () { + if (xhr.status === 200) { + if (callbacks.on200) { + // fix for IE11. It doesn't convert response to JSON + if (xhr.responseType === "" && typeof xhr.response === "string") + callbacks.on200(JSON.parse(xhr.response)); + else callbacks.on200(xhr.response); } + } else { + // handled by onerror + if (xhr.status === 0) return; - if (max < num) - return max; - if (min > num) - return min; - return num; - }, + if (callbacks.onNon200) callbacks.onNon200(xhr); + } + }; - /** @private */ - _xhr: function (method, url, options, callbacks) { - const xhr = new XMLHttpRequest(); - xhr.open(method, url); + xhr.ontimeout = function () { + if (callbacks.onTimeout) callbacks.onTimeout(xhr); + }; - // Default options - xhr.responseType = 'json'; - xhr.timeout = 10000; - // Default options redefining - if (options.responseType) - xhr.responseType = options.responseType; - if (options.timeout) - xhr.timeout = options.timeout; + xhr.onerror = function () { + if (callbacks.onError) callbacks.onError(xhr); + }; - if (method === 'POST') - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + if (options.payload) xhr.send(options.payload); + else xhr.send(); + }, + /** @private */ + _xhrRetry: function (method, url, options, callbacks) { + if (options.retries <= 0) { + console.warn("Failed to pull", options.entity_name); + if (callbacks.onTotalFail) callbacks.onTotalFail(); + return; + } + helpers._xhr(method, url, options, callbacks); + }, + /** + * @callback callbackXhrOn200 + * @param {Object} response - xhr.response + */ + /** + * @callback callbackXhrError + * @param {XMLHttpRequest} xhr + */ + /** + * @param {'GET'|'POST'} method - 'GET' or 'POST' + * @param {String} url - URL to send request to + * @param {Object} options - other XHR options + * @param {XMLHttpRequestBodyInit} [options.payload=null] - payload for POST-requests + * @param {'arraybuffer'|'blob'|'document'|'json'|'text'} [options.responseType=json] + * @param {Number} [options.timeout=10000] + * @param {Number} [options.retries=1] + * @param {String} [options.entity_name='unknown'] - string to log + * @param {Number} [options.retry_timeout=1000] + * @param {Object} callbacks - functions to execute on events fired + * @param {callbackXhrOn200} [callbacks.on200] + * @param {callbackXhrError} [callbacks.onNon200] + * @param {callbackXhrError} [callbacks.onTimeout] + * @param {callbackXhrError} [callbacks.onError] + * @param {callbackXhrError} [callbacks.onTotalFail] - if failed after all retries + */ + xhr: function (method, url, options, callbacks) { + if (!options.retries || options.retries <= 1) { + helpers._xhr(method, url, options, callbacks); + return; + } - // better than onreadystatechange because of 404 codes https://stackoverflow.com/a/36182963 - xhr.onloadend = function () { - if (xhr.status === 200) { - if (callbacks.on200) { - // fix for IE11. It doesn't convert response to JSON - if (xhr.responseType === '' && typeof(xhr.response) === 'string') - callbacks.on200(JSON.parse(xhr.response)); - else - callbacks.on200(xhr.response); - } - } else { - // handled by onerror - if (xhr.status === 0) return; - - if (callbacks.onNon200) - callbacks.onNon200(xhr); - } - }; - - xhr.ontimeout = function () { - if (callbacks.onTimeout) - callbacks.onTimeout(xhr); - }; - - xhr.onerror = function () { - if (callbacks.onError) - callbacks.onError(xhr); - }; - - if (options.payload) - xhr.send(options.payload); - else - xhr.send(); - }, - /** @private */ - _xhrRetry: function(method, url, options, callbacks) { - if (options.retries <= 0) { - console.warn('Failed to pull', options.entity_name); - if (callbacks.onTotalFail) - callbacks.onTotalFail(); - return; - } - helpers._xhr(method, url, options, callbacks); - }, - /** - * @callback callbackXhrOn200 - * @param {Object} response - xhr.response - */ - /** - * @callback callbackXhrError - * @param {XMLHttpRequest} xhr - */ - /** - * @param {'GET'|'POST'} method - 'GET' or 'POST' - * @param {String} url - URL to send request to - * @param {Object} options - other XHR options - * @param {XMLHttpRequestBodyInit} [options.payload=null] - payload for POST-requests - * @param {'arraybuffer'|'blob'|'document'|'json'|'text'} [options.responseType=json] - * @param {Number} [options.timeout=10000] - * @param {Number} [options.retries=1] - * @param {String} [options.entity_name='unknown'] - string to log - * @param {Number} [options.retry_timeout=1000] - * @param {Object} callbacks - functions to execute on events fired - * @param {callbackXhrOn200} [callbacks.on200] - * @param {callbackXhrError} [callbacks.onNon200] - * @param {callbackXhrError} [callbacks.onTimeout] - * @param {callbackXhrError} [callbacks.onError] - * @param {callbackXhrError} [callbacks.onTotalFail] - if failed after all retries - */ - xhr: function(method, url, options, callbacks) { - if (!options.retries || options.retries <= 1) { - helpers._xhr(method, url, options, callbacks); - return; - } - - if (!options.entity_name) options.entity_name = 'unknown'; - if (!options.retry_timeout) options.retry_timeout = 1000; - const retries_total = options.retries; - let currentTry = 1; - - const retry = function () { - console.warn('Pulling ' + options.entity_name + ' failed... ' + (currentTry++) + '/' + retries_total); - setTimeout(function () { - options.retries--; - helpers._xhrRetry(method, url, options, callbacks); - }, options.retry_timeout); - }; - - // Pack retry() call into error handlers - callbacks._onError = callbacks.onError; - callbacks.onError = function (xhr) { - if (callbacks._onError) - callbacks._onError(xhr); - retry(); - }; - callbacks._onTimeout = callbacks.onTimeout; - callbacks.onTimeout = function (xhr) { - if (callbacks._onTimeout) - callbacks._onTimeout(xhr); - retry(); - }; + if (!options.entity_name) options.entity_name = "unknown"; + if (!options.retry_timeout) options.retry_timeout = 1000; + const retries_total = options.retries; + let currentTry = 1; + const retry = function () { + console.warn( + "Pulling " + + options.entity_name + + " failed... " + + currentTry++ + + "/" + + retries_total, + ); + setTimeout(function () { + options.retries--; helpers._xhrRetry(method, url, options, callbacks); - }, + }, options.retry_timeout); + }; - /** - * @typedef {Object} invidiousStorage - * @property {(key:String) => Object} get - * @property {(key:String, value:Object)} set - * @property {(key:String)} remove - */ + // Pack retry() call into error handlers + callbacks._onError = callbacks.onError; + callbacks.onError = function (xhr) { + if (callbacks._onError) callbacks._onError(xhr); + retry(); + }; + callbacks._onTimeout = callbacks.onTimeout; + callbacks.onTimeout = function (xhr) { + if (callbacks._onTimeout) callbacks._onTimeout(xhr); + retry(); + }; - /** - * Universal storage, stores and returns JS objects. Uses inside localStorage or cookies - * @type {invidiousStorage} - */ - storage: (function () { - // access to localStorage throws exception in Tor Browser, so try is needed - let localStorageIsUsable = false; - try{localStorageIsUsable = !!localStorage.setItem;}catch(e){} + helpers._xhrRetry(method, url, options, callbacks); + }, - if (localStorageIsUsable) { - return { - get: function (key) { - let storageItem = localStorage.getItem(key) - if (!storageItem) return; - try { - return JSON.parse(decodeURIComponent(storageItem)); - } catch(e) { - // Erase non parsable value - helpers.storage.remove(key); - } - }, - set: function (key, value) { - let encoded_value = encodeURIComponent(JSON.stringify(value)) - localStorage.setItem(key, encoded_value); - }, - remove: function (key) { localStorage.removeItem(key); } - }; + /** + * @typedef {Object} invidiousStorage + * @property {(key:String) => Object} get + * @property {(key:String, value:Object)} set + * @property {(key:String)} remove + */ + + /** + * Universal storage, stores and returns JS objects. Uses inside localStorage or cookies + * @type {invidiousStorage} + */ + storage: (function () { + // access to localStorage throws exception in Tor Browser, so try is needed + let localStorageIsUsable = false; + try { + localStorageIsUsable = !!localStorage.setItem; + } catch (e) {} + + if (localStorageIsUsable) { + return { + get: function (key) { + let storageItem = localStorage.getItem(key); + if (!storageItem) return; + try { + return JSON.parse(decodeURIComponent(storageItem)); + } catch (e) { + // Erase non parsable value + helpers.storage.remove(key); + } + }, + set: function (key, value) { + let encoded_value = encodeURIComponent(JSON.stringify(value)); + localStorage.setItem(key, encoded_value); + }, + remove: function (key) { + localStorage.removeItem(key); + }, + }; + } + + // TODO: fire 'storage' event for cookies + console.info( + "Storage: localStorage is disabled or unaccessible. Cookies used as fallback", + ); + return { + get: function (key) { + const cookiePrefix = key + "="; + function findCallback(cookie) { + return cookie.startsWith(cookiePrefix); } + const matchedCookie = document.cookie.split("; ").find(findCallback); + if (matchedCookie) { + const cookieBody = matchedCookie.replace(cookiePrefix, ""); + if (cookieBody.length === 0) return; + try { + return JSON.parse(decodeURIComponent(cookieBody)); + } catch (e) { + // Erase non parsable value + helpers.storage.remove(key); + } + } + }, + set: function (key, value) { + const cookie_data = encodeURIComponent(JSON.stringify(value)); - // TODO: fire 'storage' event for cookies - console.info('Storage: localStorage is disabled or unaccessible. Cookies used as fallback'); - return { - get: function (key) { - const cookiePrefix = key + '='; - function findCallback(cookie) {return cookie.startsWith(cookiePrefix);} - const matchedCookie = document.cookie.split('; ').find(findCallback); - if (matchedCookie) { - const cookieBody = matchedCookie.replace(cookiePrefix, ''); - if (cookieBody.length === 0) return; - try { - return JSON.parse(decodeURIComponent(cookieBody)); - } catch(e) { - // Erase non parsable value - helpers.storage.remove(key); - } - } - }, - set: function (key, value) { - const cookie_data = encodeURIComponent(JSON.stringify(value)); + // Set expiration in 2 year + const date = new Date(); + date.setFullYear(date.getFullYear() + 2); - // Set expiration in 2 year - const date = new Date(); - date.setFullYear(date.getFullYear()+2); - - document.cookie = key + '=' + cookie_data + '; expires=' + date.toGMTString(); - }, - remove: function (key) { - document.cookie = key + '=; Max-Age=0'; - } - }; - })() + document.cookie = + key + "=" + cookie_data + "; expires=" + date.toGMTString(); + }, + remove: function (key) { + document.cookie = key + "=; Max-Age=0"; + }, + }; + })(), }; diff --git a/assets/js/comments.js b/assets/js/comments.js index 57c99f52..7c68ae54 100644 --- a/assets/js/comments.js +++ b/assets/js/comments.js @@ -1,81 +1,91 @@ -var video_data = JSON.parse(document.getElementById('video_data').textContent); +var video_data = JSON.parse(document.getElementById("video_data").textContent); -var spinnerHTML = '
'; -var spinnerHTMLwithHR = spinnerHTML + '
'; +var spinnerHTML = + '
'; +var spinnerHTMLwithHR = spinnerHTML + "
"; 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; - }); + return this.replace(/{([^{}]*)}/g, function (a, b) { + var r = o[b]; + return typeof r === "string" || typeof r === "number" ? r : a; + }); }; function toggle_comments(event) { - const target = event.target; - const comments = document.querySelector(".comments"); - if (comments.style.display === 'none') { - target.textContent = '−'; - comments.style.display = ''; - } else { - target.textContent = '+'; - comments.style.display = 'none'; - } + const target = event.target; + const comments = document.querySelector(".comments"); + if (comments.style.display === "none") { + target.textContent = "−"; + comments.style.display = ""; + } else { + target.textContent = "+"; + comments.style.display = "none"; + } } function hide_youtube_replies(event) { - var target = event.target; + var target = event.target; - var sub_text = target.getAttribute('data-inner-text'); - var inner_text = target.getAttribute('data-sub-text'); + var sub_text = target.getAttribute("data-inner-text"); + var inner_text = target.getAttribute("data-sub-text"); - var body = target.parentNode.parentNode.children[1]; - body.style.display = 'none'; + var body = target.parentNode.parentNode.children[1]; + body.style.display = "none"; - target.textContent = sub_text; - target.onclick = show_youtube_replies; - target.setAttribute('data-inner-text', inner_text); - target.setAttribute('data-sub-text', sub_text); + target.textContent = 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; - console.log(target); + var target = event.target; + console.log(target); - var sub_text = target.getAttribute('data-inner-text'); - var inner_text = target.getAttribute('data-sub-text'); + var sub_text = target.getAttribute("data-inner-text"); + var inner_text = target.getAttribute("data-sub-text"); - var body = target.parentNode.parentNode.children[1]; - body.style.display = ''; + var body = target.parentNode.parentNode.children[1]; + body.style.display = ""; - target.textContent = sub_text; - target.onclick = hide_youtube_replies; - target.setAttribute('data-inner-text', inner_text); - target.setAttribute('data-sub-text', sub_text); + target.textContent = sub_text; + target.onclick = hide_youtube_replies; + target.setAttribute("data-inner-text", inner_text); + target.setAttribute("data-sub-text", sub_text); } function get_youtube_comments() { - var comments = document.getElementById('comments'); + var comments = document.getElementById("comments"); - var fallback = comments.innerHTML; - comments.innerHTML = spinnerHTML; + var fallback = comments.innerHTML; + comments.innerHTML = spinnerHTML; - var baseUrl = video_data.base_url || '/api/v1/comments/'+ video_data.id - var url = baseUrl + - '?format=html' + - '&hl=' + video_data.preferences.locale + - '&thin_mode=' + video_data.preferences.thin_mode; + var baseUrl = video_data.base_url || "/api/v1/comments/" + video_data.id; + var url = + baseUrl + + "?format=html" + + "&hl=" + + video_data.preferences.locale + + "&thin_mode=" + + video_data.preferences.thin_mode; - if (video_data.ucid) { - url += '&ucid=' + video_data.ucid - } + if (video_data.ucid) { + url += "&ucid=" + video_data.ucid; + } - var onNon200 = function (xhr) { comments.innerHTML = fallback; }; - if (video_data.params.comments[1] === 'youtube') - onNon200 = function (xhr) {}; + var onNon200 = function (xhr) { + comments.innerHTML = fallback; + }; + if (video_data.params.comments[1] === "youtube") onNon200 = function (xhr) {}; - helpers.xhr('GET', url, {retries: 5, entity_name: 'comments'}, { - on200: function (response) { - var commentInnerHtml = ' \ + helpers.xhr( + "GET", + url, + { retries: 5, entity_name: "comments" }, + { + on200: function (response) { + var commentInnerHtml = + ' \ \ -
{contentHtml}
' - commentInnerHtml = commentInnerHtml.supplant({ - contentHtml: response.contentHtml, - redditComments: video_data.reddit_comments_text, - commentsText: video_data.comments_text.supplant({ - // toLocaleString correctly splits number with local thousands separator. e.g.: - // '1,234,567.89' for user with English locale - // '1 234 567,89' for user with Russian locale - // '1.234.567,89' for user with Portuguese locale - commentCount: response.commentCount.toLocaleString() - }) - }); - comments.innerHTML = commentInnerHtml; - document.getElementById("toggle-comments").onclick = toggle_comments; - if (video_data.support_reddit) { - comments.children[1].children[1].onclick = swap_comments; - } - }, - onNon200: onNon200, // declared above - onError: function (xhr) { - comments.innerHTML = spinnerHTML; - }, - onTimeout: function (xhr) { - comments.innerHTML = spinnerHTML; +
{contentHtml}
'; + commentInnerHtml = commentInnerHtml.supplant({ + contentHtml: response.contentHtml, + redditComments: video_data.reddit_comments_text, + commentsText: video_data.comments_text.supplant({ + // toLocaleString correctly splits number with local thousands separator. e.g.: + // '1,234,567.89' for user with English locale + // '1 234 567,89' for user with Russian locale + // '1.234.567,89' for user with Portuguese locale + commentCount: response.commentCount.toLocaleString(), + }), + }); + comments.innerHTML = commentInnerHtml; + document.getElementById("toggle-comments").onclick = toggle_comments; + if (video_data.support_reddit) { + comments.children[1].children[1].onclick = swap_comments; } - }); + }, + onNon200: onNon200, // declared above + onError: function (xhr) { + comments.innerHTML = spinnerHTML; + }, + onTimeout: function (xhr) { + comments.innerHTML = spinnerHTML; + }, + }, + ); } function get_youtube_replies(target, load_more, load_replies) { - var continuation = target.getAttribute('data-continuation'); + var continuation = target.getAttribute("data-continuation"); - var body = target.parentNode; - var fallback = body.innerHTML; - body.innerHTML = spinnerHTML; - var baseUrl = video_data.base_url || '/api/v1/comments/'+ video_data.id - var url = baseUrl + - '?format=html' + - '&hl=' + video_data.preferences.locale + - '&thin_mode=' + video_data.preferences.thin_mode + - '&continuation=' + continuation; + var body = target.parentNode; + var fallback = body.innerHTML; + body.innerHTML = spinnerHTML; + var baseUrl = video_data.base_url || "/api/v1/comments/" + video_data.id; + var url = + baseUrl + + "?format=html" + + "&hl=" + + video_data.preferences.locale + + "&thin_mode=" + + video_data.preferences.thin_mode + + "&continuation=" + + continuation; - if (video_data.ucid) { - url += '&ucid=' + video_data.ucid - } - if (load_replies) url += '&action=action_get_comment_replies'; + if (video_data.ucid) { + url += "&ucid=" + video_data.ucid; + } + if (load_replies) url += "&action=action_get_comment_replies"; - helpers.xhr('GET', url, {}, { - on200: function (response) { - if (load_more) { - body = body.parentNode; - body.removeChild(body.lastElementChild); - body.insertAdjacentHTML('beforeend', response.contentHtml); - } else { - body.removeChild(body.lastElementChild); + helpers.xhr( + "GET", + url, + {}, + { + on200: function (response) { + if (load_more) { + body = body.parentNode; + body.removeChild(body.lastElementChild); + body.insertAdjacentHTML("beforeend", response.contentHtml); + } else { + body.removeChild(body.lastElementChild); - var div = document.createElement('div'); - var button = document.createElement('button'); - div.appendChild(button); + var div = document.createElement("div"); + var button = document.createElement("button"); + div.appendChild(button); - button.onclick = hide_youtube_replies; - button.setAttribute('data-sub-text', video_data.hide_replies_text); - button.setAttribute('data-inner-text', video_data.show_replies_text); - button.textContent = video_data.hide_replies_text; + button.onclick = hide_youtube_replies; + button.setAttribute("data-sub-text", video_data.hide_replies_text); + button.setAttribute("data-inner-text", video_data.show_replies_text); + button.textContent = video_data.hide_replies_text; - var div = document.createElement('div'); - div.innerHTML = response.contentHtml; + var div = document.createElement("div"); + div.innerHTML = response.contentHtml; - body.appendChild(div); - } - }, - onNon200: function (xhr) { - body.innerHTML = fallback; - }, - onTimeout: function (xhr) { - console.warn('Pulling comments failed'); - body.innerHTML = fallback; + body.appendChild(div); } - }); + }, + onNon200: function (xhr) { + body.innerHTML = fallback; + }, + onTimeout: function (xhr) { + console.warn("Pulling comments failed"); + body.innerHTML = fallback; + }, + }, + ); } diff --git a/assets/js/community.js b/assets/js/community.js index 32fe4ebc..ef8be78b 100644 --- a/assets/js/community.js +++ b/assets/js/community.js @@ -1,82 +1,94 @@ -'use strict'; -var community_data = JSON.parse(document.getElementById('community_data').textContent); +"use strict"; +var community_data = JSON.parse( + document.getElementById("community_data").textContent, +); function hide_youtube_replies(event) { - var target = event.target; + var target = event.target; - var sub_text = target.getAttribute('data-inner-text'); - var inner_text = target.getAttribute('data-sub-text'); + var sub_text = target.getAttribute("data-inner-text"); + var inner_text = target.getAttribute("data-sub-text"); - var body = target.parentNode.parentNode.children[1]; - body.style.display = 'none'; + var 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); + 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; + var target = event.target; - var sub_text = target.getAttribute('data-inner-text'); - var inner_text = target.getAttribute('data-sub-text'); + var sub_text = target.getAttribute("data-inner-text"); + var inner_text = target.getAttribute("data-sub-text"); - var body = target.parentNode.parentNode.children[1]; - body.style.display = ''; + var 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); + target.innerHTML = sub_text; + target.onclick = hide_youtube_replies; + target.setAttribute("data-inner-text", inner_text); + target.setAttribute("data-sub-text", sub_text); } function get_youtube_replies(target, load_more) { - var continuation = target.getAttribute('data-continuation'); + var continuation = target.getAttribute("data-continuation"); - var body = target.parentNode.parentNode; - var fallback = body.innerHTML; - body.innerHTML = - '

'; + var body = target.parentNode.parentNode; + var fallback = body.innerHTML; + body.innerHTML = + '

'; - var url = '/api/v1/channels/comments/' + community_data.ucid + - '?format=html' + - '&hl=' + community_data.preferences.locale + - '&thin_mode=' + community_data.preferences.thin_mode + - '&continuation=' + continuation; + var url = + "/api/v1/channels/comments/" + + community_data.ucid + + "?format=html" + + "&hl=" + + community_data.preferences.locale + + "&thin_mode=" + + community_data.preferences.thin_mode + + "&continuation=" + + continuation; - helpers.xhr('GET', url, {}, { - on200: function (response) { - if (load_more) { - body = body.parentNode.parentNode; - body.removeChild(body.lastElementChild); - body.innerHTML += response.contentHtml; - } else { - body.removeChild(body.lastElementChild); + helpers.xhr( + "GET", + url, + {}, + { + on200: function (response) { + if (load_more) { + body = body.parentNode.parentNode; + body.removeChild(body.lastElementChild); + body.innerHTML += response.contentHtml; + } else { + body.removeChild(body.lastElementChild); - var p = document.createElement('p'); - var a = document.createElement('a'); - p.appendChild(a); + 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', community_data.hide_replies_text); - a.setAttribute('data-inner-text', community_data.show_replies_text); - a.textContent = community_data.hide_replies_text; + a.href = "javascript:void(0)"; + a.onclick = hide_youtube_replies; + a.setAttribute("data-sub-text", community_data.hide_replies_text); + a.setAttribute("data-inner-text", community_data.show_replies_text); + a.textContent = community_data.hide_replies_text; - var div = document.createElement('div'); - div.innerHTML = response.contentHtml; + var div = document.createElement("div"); + div.innerHTML = response.contentHtml; - body.appendChild(p); - body.appendChild(div); - } - }, - onNon200: function (xhr) { - body.innerHTML = fallback; - }, - onTimeout: function (xhr) { - console.warn('Pulling comments failed'); - body.innerHTML = fallback; + body.appendChild(p); + body.appendChild(div); } - }); + }, + onNon200: function (xhr) { + body.innerHTML = fallback; + }, + onTimeout: function (xhr) { + console.warn("Pulling comments failed"); + body.innerHTML = fallback; + }, + }, + ); } diff --git a/assets/js/embed.js b/assets/js/embed.js index b11b5e5a..763f0ca8 100644 --- a/assets/js/embed.js +++ b/assets/js/embed.js @@ -1,64 +1,79 @@ -'use strict'; -var video_data = JSON.parse(document.getElementById('video_data').textContent); +"use strict"; +var video_data = JSON.parse(document.getElementById("video_data").textContent); function get_playlist(plid) { - var plid_url; - if (plid.startsWith('RD')) { - plid_url = '/api/v1/mixes/' + plid + - '?continuation=' + video_data.id + - '&format=html&hl=' + video_data.preferences.locale; - } else { - plid_url = '/api/v1/playlists/' + plid + - '?index=' + video_data.index + - '&continuation' + video_data.id + - '&format=html&hl=' + video_data.preferences.locale; - } + var plid_url; + if (plid.startsWith("RD")) { + plid_url = + "/api/v1/mixes/" + + plid + + "?continuation=" + + video_data.id + + "&format=html&hl=" + + video_data.preferences.locale; + } else { + plid_url = + "/api/v1/playlists/" + + plid + + "?index=" + + video_data.index + + "&continuation" + + video_data.id + + "&format=html&hl=" + + video_data.preferences.locale; + } - helpers.xhr('GET', plid_url, {retries: 5, entity_name: 'playlist'}, { - on200: function (response) { - if (!response.nextVideo) - return; + helpers.xhr( + "GET", + plid_url, + { retries: 5, entity_name: "playlist" }, + { + on200: function (response) { + if (!response.nextVideo) return; - player.on('ended', function () { - var url = new URL('https://example.com/embed/' + response.nextVideo); + player.on("ended", function () { + var url = new URL("https://example.com/embed/" + response.nextVideo); - url.searchParams.set('list', plid); - if (!plid.startsWith('RD')) - url.searchParams.set('index', response.index); - 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); + if (!plid.startsWith("RD")) + url.searchParams.set("index", response.index); + 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); - location.assign(url.pathname + url.search); - }); - } - }); + location.assign(url.pathname + url.search); + }); + }, + }, + ); } -addEventListener('load', function (e) { - if (video_data.plid) { - get_playlist(video_data.plid); - } else if (video_data.video_series) { - player.on('ended', function () { - var url = new URL('https://example.com/embed/' + video_data.video_series.shift()); +addEventListener("load", function (e) { + if (video_data.plid) { + get_playlist(video_data.plid); + } else if (video_data.video_series) { + player.on("ended", function () { + var url = new URL( + "https://example.com/embed/" + video_data.video_series.shift(), + ); - 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); - if (video_data.video_series.length !== 0) - url.searchParams.set('playlist', video_data.video_series.join(',')); + 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); + if (video_data.video_series.length !== 0) + url.searchParams.set("playlist", video_data.video_series.join(",")); - location.assign(url.pathname + url.search); - }); - } + location.assign(url.pathname + url.search); + }); + } }); diff --git a/assets/js/handlers.js b/assets/js/handlers.js index 51de08f6..efd0e1cf 100644 --- a/assets/js/handlers.js +++ b/assets/js/handlers.js @@ -1,149 +1,220 @@ -'use strict'; +"use strict"; (function () { - var video_player = document.getElementById('player_html5_api'); - if (video_player) { - video_player.onmouseenter = function () { video_player['data-title'] = video_player['title']; video_player['title'] = ''; }; - video_player.onmouseleave = function () { video_player['title'] = video_player['data-title']; video_player['data-title'] = ''; }; - video_player.oncontextmenu = function () { video_player['title'] = video_player['data-title']; }; + var video_player = document.getElementById("player_html5_api"); + if (video_player) { + video_player.onmouseenter = function () { + video_player["data-title"] = video_player["title"]; + video_player["title"] = ""; + }; + video_player.onmouseleave = function () { + video_player["title"] = video_player["data-title"]; + video_player["data-title"] = ""; + }; + video_player.oncontextmenu = function () { + video_player["title"] = video_player["data-title"]; + }; + } + + // For dynamically inserted elements + addEventListener("click", function (e) { + if (!e || !e.target) return; + + var t = e.target; + var handler_name = t.getAttribute("data-onclick"); + + switch (handler_name) { + case "jump_to_time": + e.preventDefault(); + var time = t.getAttribute("data-jump-time"); + player.currentTime(time); + break; + case "get_youtube_replies": + var load_more = t.getAttribute("data-load-more") !== null; + var load_replies = t.getAttribute("data-load-replies") !== null; + get_youtube_replies(t, load_more, load_replies); + break; + case "toggle_parent": + e.preventDefault(); + toggle_parent(t); + break; + default: + break; + } + }); + + document + .querySelectorAll('[data-mouse="switch_classes"]') + .forEach(function (el) { + var classes = el.getAttribute("data-switch-classes").split(","); + var classOnEnter = classes[0]; + var classOnLeave = classes[1]; + function toggle_classes(toAdd, toRemove) { + el.classList.add(toAdd); + el.classList.remove(toRemove); + } + el.onmouseenter = function () { + toggle_classes(classOnEnter, classOnLeave); + }; + el.onmouseleave = function () { + toggle_classes(classOnLeave, classOnEnter); + }; + }); + + document + .querySelectorAll('[data-onsubmit="return_false"]') + .forEach(function (el) { + el.onsubmit = function () { + return false; + }; + }); + + document + .querySelectorAll('[data-onclick="mark_watched"]') + .forEach(function (el) { + el.onclick = function () { + mark_watched(el); + }; + }); + document + .querySelectorAll('[data-onclick="mark_unwatched"]') + .forEach(function (el) { + el.onclick = function () { + mark_unwatched(el); + }; + }); + document + .querySelectorAll('[data-onclick="add_playlist_video"]') + .forEach(function (el) { + el.onclick = function (e) { + add_playlist_video(e); + }; + }); + document + .querySelectorAll('[data-onclick="add_playlist_item"]') + .forEach(function (el) { + el.onclick = function (e) { + add_playlist_item(e); + }; + }); + document + .querySelectorAll('[data-onclick="remove_playlist_item"]') + .forEach(function (el) { + el.onclick = function (e) { + remove_playlist_item(e); + }; + }); + document + .querySelectorAll('[data-onclick="revoke_token"]') + .forEach(function (el) { + el.onclick = function () { + revoke_token(el); + }; + }); + document + .querySelectorAll('[data-onclick="remove_subscription"]') + .forEach(function (el) { + el.onclick = function () { + remove_subscription(el); + }; + }); + document + .querySelectorAll('[data-onclick="notification_requestPermission"]') + .forEach(function (el) { + el.onclick = function () { + Notification.requestPermission(); + }; + }); + + document + .querySelectorAll('[data-onrange="update_volume_value"]') + .forEach(function (el) { + function update_volume_value() { + document.getElementById("volume-value").textContent = el.value; + } + el.oninput = update_volume_value; + el.onchange = update_volume_value; + }); + + function revoke_token(target) { + var row = target.parentNode.parentNode.parentNode.parentNode.parentNode; + row.style.display = "none"; + var count = document.getElementById("count"); + count.textContent--; + + var url = + "/token_ajax?action=revoke_token&redirect=false" + + "&referer=" + + encodeURIComponent(location.href) + + "&session=" + + target.getAttribute("data-session"); + + var payload = + "csrf_token=" + + target.parentNode.querySelector('input[name="csrf_token"]').value; + + helpers.xhr( + "POST", + url, + { payload: payload }, + { + onNon200: function (xhr) { + count.textContent++; + row.style.display = ""; + }, + }, + ); + } + + function remove_subscription(target) { + var row = target.parentNode.parentNode.parentNode.parentNode.parentNode; + row.style.display = "none"; + var count = document.getElementById("count"); + count.textContent--; + + var url = + "/subscription_ajax?action=remove_subscriptions&redirect=false" + + "&referer=" + + encodeURIComponent(location.href) + + "&c=" + + target.getAttribute("data-ucid"); + + var payload = + "csrf_token=" + + target.parentNode.querySelector('input[name="csrf_token"]').value; + + helpers.xhr( + "POST", + url, + { payload: payload }, + { + onNon200: function (xhr) { + count.textContent++; + row.style.display = ""; + }, + }, + ); + } + + // Handle keypresses + addEventListener("keydown", function (event) { + // Ignore modifier keys + if (event.ctrlKey || event.metaKey) return; + + // Ignore shortcuts if any text input is focused + let focused_tag = document.activeElement.tagName.toLowerCase(); + const allowed = /^(button|checkbox|file|radio|submit)$/; + + if (focused_tag === "textarea") return; + if (focused_tag === "input") { + let focused_type = document.activeElement.type.toLowerCase(); + if (!allowed.test(focused_type)) return; } - // For dynamically inserted elements - addEventListener('click', function (e) { - if (!e || !e.target) return; - - var t = e.target; - var handler_name = t.getAttribute('data-onclick'); - - switch (handler_name) { - case 'jump_to_time': - e.preventDefault(); - var time = t.getAttribute('data-jump-time'); - player.currentTime(time); - break; - case 'get_youtube_replies': - var load_more = t.getAttribute('data-load-more') !== null; - var load_replies = t.getAttribute('data-load-replies') !== null; - get_youtube_replies(t, load_more, load_replies); - break; - case 'toggle_parent': - e.preventDefault(); - toggle_parent(t); - break; - default: - break; - } - }); - - document.querySelectorAll('[data-mouse="switch_classes"]').forEach(function (el) { - var classes = el.getAttribute('data-switch-classes').split(','); - var classOnEnter = classes[0]; - var classOnLeave = classes[1]; - function toggle_classes(toAdd, toRemove) { - el.classList.add(toAdd); - el.classList.remove(toRemove); - } - el.onmouseenter = function () { toggle_classes(classOnEnter, classOnLeave); }; - el.onmouseleave = function () { toggle_classes(classOnLeave, classOnEnter); }; - }); - - document.querySelectorAll('[data-onsubmit="return_false"]').forEach(function (el) { - el.onsubmit = function () { return false; }; - }); - - document.querySelectorAll('[data-onclick="mark_watched"]').forEach(function (el) { - el.onclick = function () { mark_watched(el); }; - }); - document.querySelectorAll('[data-onclick="mark_unwatched"]').forEach(function (el) { - el.onclick = function () { mark_unwatched(el); }; - }); - document.querySelectorAll('[data-onclick="add_playlist_video"]').forEach(function (el) { - el.onclick = function (e) { add_playlist_video(e); }; - }); - document.querySelectorAll('[data-onclick="add_playlist_item"]').forEach(function (el) { - el.onclick = function (e) { add_playlist_item(e); }; - }); - document.querySelectorAll('[data-onclick="remove_playlist_item"]').forEach(function (el) { - el.onclick = function (e) { remove_playlist_item(e); }; - }); - document.querySelectorAll('[data-onclick="revoke_token"]').forEach(function (el) { - el.onclick = function () { revoke_token(el); }; - }); - document.querySelectorAll('[data-onclick="remove_subscription"]').forEach(function (el) { - el.onclick = function () { remove_subscription(el); }; - }); - document.querySelectorAll('[data-onclick="notification_requestPermission"]').forEach(function (el) { - el.onclick = function () { Notification.requestPermission(); }; - }); - - document.querySelectorAll('[data-onrange="update_volume_value"]').forEach(function (el) { - function update_volume_value() { - document.getElementById('volume-value').textContent = el.value; - } - el.oninput = update_volume_value; - el.onchange = update_volume_value; - }); - - - function revoke_token(target) { - var row = target.parentNode.parentNode.parentNode.parentNode.parentNode; - row.style.display = 'none'; - var count = document.getElementById('count'); - count.textContent--; - - var url = '/token_ajax?action=revoke_token&redirect=false' + - '&referer=' + encodeURIComponent(location.href) + - '&session=' + target.getAttribute('data-session'); - - var payload = 'csrf_token=' + target.parentNode.querySelector('input[name="csrf_token"]').value; - - helpers.xhr('POST', url, {payload: payload}, { - onNon200: function (xhr) { - count.textContent++; - row.style.display = ''; - } - }); + // Focus search bar on '/' + if (event.key === "/") { + document.getElementById("searchbox").focus(); + event.preventDefault(); } - - function remove_subscription(target) { - var row = target.parentNode.parentNode.parentNode.parentNode.parentNode; - row.style.display = 'none'; - var count = document.getElementById('count'); - count.textContent--; - - var url = '/subscription_ajax?action=remove_subscriptions&redirect=false' + - '&referer=' + encodeURIComponent(location.href) + - '&c=' + target.getAttribute('data-ucid'); - - var payload = 'csrf_token=' + target.parentNode.querySelector('input[name="csrf_token"]').value; - - helpers.xhr('POST', url, {payload: payload}, { - onNon200: function (xhr) { - count.textContent++; - row.style.display = ''; - } - }); - } - - // Handle keypresses - addEventListener('keydown', function (event) { - // Ignore modifier keys - if (event.ctrlKey || event.metaKey) return; - - // Ignore shortcuts if any text input is focused - let focused_tag = document.activeElement.tagName.toLowerCase(); - const allowed = /^(button|checkbox|file|radio|submit)$/; - - if (focused_tag === 'textarea') return; - if (focused_tag === 'input') { - let focused_type = document.activeElement.type.toLowerCase(); - if (!allowed.test(focused_type)) return; - } - - // Focus search bar on '/' - if (event.key === '/') { - document.getElementById('searchbox').focus(); - event.preventDefault(); - } - }); + }); })(); diff --git a/assets/js/notifications.js b/assets/js/notifications.js index 2c8552c8..0732d28d 100644 --- a/assets/js/notifications.js +++ b/assets/js/notifications.js @@ -31,7 +31,11 @@ async function get_subscriptions_call() { } // Start the retry mechanism -const get_subscriptions = exponential_backoff(get_subscriptions_call, 100, 1000); +const get_subscriptions = exponential_backoff( + get_subscriptions_call, + 100, + 1000, +); function create_notification_stream(subscriptions) { // sse.js can't be replaced to EventSource in place as it lack support of payload and headers @@ -96,7 +100,6 @@ function create_notification_stream(subscriptions) { "Something went wrong with notifications, trying to reconnect...", ); notifications = notifications_mock; - }); notifications.stream(); @@ -177,7 +180,7 @@ function exponential_backoff( return function tryFunction() { fn() .then((response) => { - attempt = 0; + attempt = 0; }) .catch((error) => { if (attempt < maxRetries) { @@ -193,5 +196,5 @@ function exponential_backoff( console.log("Max retries reached. Operation failed:", error); } }); - } + }; } diff --git a/assets/js/pagination.js b/assets/js/pagination.js index 2e560a34..9d67b839 100644 --- a/assets/js/pagination.js +++ b/assets/js/pagination.js @@ -1,93 +1,103 @@ -'use strict'; +"use strict"; -const CURRENT_CONTINUATION = (new URL(document.location)).searchParams.get("continuation"); +const CURRENT_CONTINUATION = new URL(document.location).searchParams.get( + "continuation", +); const CONT_CACHE_KEY = `continuation_cache_${encodeURIComponent(window.location.pathname)}`; -function get_data(){ - return JSON.parse(sessionStorage.getItem(CONT_CACHE_KEY)) || []; +function get_data() { + return JSON.parse(sessionStorage.getItem(CONT_CACHE_KEY)) || []; } -function save_data(){ - const prev_data = get_data(); - prev_data.push(CURRENT_CONTINUATION); +function save_data() { + const prev_data = get_data(); + prev_data.push(CURRENT_CONTINUATION); - sessionStorage.setItem(CONT_CACHE_KEY, JSON.stringify(prev_data)); + sessionStorage.setItem(CONT_CACHE_KEY, JSON.stringify(prev_data)); } -function button_press(){ - let prev_data = get_data(); - if (!prev_data.length) return null; +function button_press() { + let prev_data = get_data(); + if (!prev_data.length) return null; - // Sanity check. Nowhere should the current continuation token exist in the cache - // but it can happen when using the browser's back feature. As such we'd need to travel - // back to the point where the current continuation token first appears in order to - // account for the rewind. - const conflict_at = prev_data.indexOf(CURRENT_CONTINUATION); - if (conflict_at != -1) { - prev_data.length = conflict_at; - } + // Sanity check. Nowhere should the current continuation token exist in the cache + // but it can happen when using the browser's back feature. As such we'd need to travel + // back to the point where the current continuation token first appears in order to + // account for the rewind. + const conflict_at = prev_data.indexOf(CURRENT_CONTINUATION); + if (conflict_at != -1) { + prev_data.length = conflict_at; + } - const prev_ctoken = prev_data.pop(); - - // On the first page, the stored continuation token is null. - if (prev_ctoken === null) { - sessionStorage.removeItem(CONT_CACHE_KEY); - let url = set_continuation(); - window.location.href = url; - - return; - } - - sessionStorage.setItem(CONT_CACHE_KEY, JSON.stringify(prev_data)); - let url = set_continuation(prev_ctoken); + const prev_ctoken = prev_data.pop(); + // On the first page, the stored continuation token is null. + if (prev_ctoken === null) { + sessionStorage.removeItem(CONT_CACHE_KEY); + let url = set_continuation(); window.location.href = url; -}; + + return; + } + + sessionStorage.setItem(CONT_CACHE_KEY, JSON.stringify(prev_data)); + let url = set_continuation(prev_ctoken); + + window.location.href = url; +} // Method to set the current page's continuation token // Removes the continuation parameter when a continuation token is not given -function set_continuation(prev_ctoken = null){ - let url = window.location.href.split('?')[0]; - let params = window.location.href.split('?')[1]; - let url_params = new URLSearchParams(params); +function set_continuation(prev_ctoken = null) { + let url = window.location.href.split("?")[0]; + let params = window.location.href.split("?")[1]; + let url_params = new URLSearchParams(params); - if (prev_ctoken) { - url_params.set("continuation", prev_ctoken); - } else { - url_params.delete('continuation'); - }; + if (prev_ctoken) { + url_params.set("continuation", prev_ctoken); + } else { + url_params.delete("continuation"); + } - if(Array.from(url_params).length > 0){ - return `${url}?${url_params.toString()}`; - } else { - return url; - } + if (Array.from(url_params).length > 0) { + return `${url}?${url_params.toString()}`; + } else { + return url; + } } -addEventListener('DOMContentLoaded', function(){ - const pagination_data = JSON.parse(document.getElementById('pagination-data').textContent); - const next_page_containers = document.getElementsByClassName("page-next-container"); +addEventListener("DOMContentLoaded", function () { + const pagination_data = JSON.parse( + document.getElementById("pagination-data").textContent, + ); + const next_page_containers = document.getElementsByClassName( + "page-next-container", + ); - for (let container of next_page_containers){ - const next_page_button = container.getElementsByClassName("pure-button") + for (let container of next_page_containers) { + const next_page_button = container.getElementsByClassName("pure-button"); - // exists? - if (next_page_button.length > 0){ - next_page_button[0].addEventListener("click", save_data); - } + // exists? + if (next_page_button.length > 0) { + next_page_button[0].addEventListener("click", save_data); } + } - // Only add previous page buttons when not on the first page - if (CURRENT_CONTINUATION) { - const prev_page_containers = document.getElementsByClassName("page-prev-container") + // Only add previous page buttons when not on the first page + if (CURRENT_CONTINUATION) { + const prev_page_containers = document.getElementsByClassName( + "page-prev-container", + ); - for (let container of prev_page_containers) { - if (pagination_data.is_rtl) { - container.innerHTML = `` - } else { - container.innerHTML = `` - } - container.getElementsByClassName("pure-button")[0].addEventListener("click", button_press); - } + for (let container of prev_page_containers) { + if (pagination_data.is_rtl) { + container.innerHTML = ``; + } else { + container.innerHTML = ``; + } + container + .getElementsByClassName("pure-button")[0] + .addEventListener("click", button_press); } + } }); diff --git a/assets/js/player.js b/assets/js/player.js index f32c9b56..ed74ed85 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -1,98 +1,99 @@ -'use strict'; -var player_data = JSON.parse(document.getElementById('player_data').textContent); -var video_data = JSON.parse(document.getElementById('video_data').textContent); +"use strict"; +var player_data = JSON.parse( + document.getElementById("player_data").textContent, +); +var video_data = JSON.parse(document.getElementById("video_data").textContent); 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' - ] + 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, }, - html5: { - preloadTextTracks: false, - vhs: { - overrideNative: true - } - } + }, }; if (player_data.aspect_ratio) { - options.aspectRatio = 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; +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'; +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; +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; }; -var player = videojs('player', options); +var player = videojs("player", options); -player.on('error', function () { - if (video_data.params.quality === 'dash') return; +player.on("error", function () { + 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 + 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) { + setTimeout(function () { + console.warn("An error occurred in the player, reloading..."); - 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) { - 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(); - // After load() all parameters are reset. Save them - var currentTime = player.currentTime(); - var playbackRate = player.playbackRate(); - var paused = player.paused(); + player.load(); - player.load(); + if (currentTime > 0.5) currentTime -= 0.5; - if (currentTime > 0.5) currentTime -= 0.5; - - player.currentTime(currentTime); - player.playbackRate(playbackRate); - if (!paused) player.play(); - }, 5000); - } + player.currentTime(currentTime); + player.playbackRate(playbackRate); + if (!paused) player.play(); + }, 5000); + } }); -if (video_data.params.quality === 'dash') { - player.reloadSourceOnError({ - errorInterval: 10 - }); +if (video_data.params.quality === "dash") { + player.reloadSourceOnError({ + errorInterval: 10, + }); } /** @@ -103,14 +104,12 @@ if (video_data.params.quality === 'dash') { * @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; + 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; } /** @@ -125,148 +124,182 @@ 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; +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 + // 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); - } + 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); + } - // Invidious links + 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); + } - let domain = window.location.origin; + // Invidious links - 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); - } + 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); + } }); - var shareOptions = { - socials: ['fbFeed', 'tw', 'reddit', 'email'], + 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 ""; - } + 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/')) { - var overlay_content = '

' + player_data.title + '

'; - player.overlay({ - overlays: [ - { start: 'loadstart', content: overlay_content, end: 'playing', align: 'top'}, - { start: 'pause', content: overlay_content, end: 'playing', align: 'top'} - ] - }); +if (location.pathname.startsWith("/embed/")) { + var overlay_content = + '

' + + player_data.title + + "

"; + 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; } + try { + document.createEvent("TouchEvent"); + return true; + } catch (e) { + return false; + } } if (isMobile()) { - player.mobileUi({ touchControls: { seekSeconds: 5 * player.playbackRate() } }); + player.mobileUi({ + touchControls: { seekSeconds: 5 * player.playbackRate() }, + }); - var buttons = ['playToggle', 'volumePanel', 'captionsButton']; + 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'); + 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);}); + // 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);}); + // 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); + 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); + // 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); + // 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); - } - }); + 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'}); - } + 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' }]; + 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' }); - } + 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.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.currentTime(video_data.params.video_start); } player.volume(video_data.params.volume / 100); @@ -279,11 +312,12 @@ player.playbackRate(video_data.params.speed); * @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; + var cookiePrefix = name + "="; + var matchedCookie = document.cookie.split(";").find(function (item) { + return item.includes(cookiePrefix); + }); + if (matchedCookie) return matchedCookie.replace(cookiePrefix, ""); + return null; } /** @@ -293,502 +327,596 @@ function getCookieValue(name) { * @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 volumeValue = newVolume !== null ? newVolume : video_data.params.volume; + var speedValue = newSpeed !== null ? newSpeed : video_data.params.speed; - var cookieValue = getCookieValue('PREFS'); - var cookieData; + 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 })); - } + 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); + // 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; + 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; + // 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;" : ""; + var secure = location.protocol.startsWith("https") ? " Secure;" : ""; - document.cookie = 'PREFS=' + cookieData + '; SameSite=Lax; path=/; domain=' + - domainUsed + '; expires=' + date.toGMTString() + ';' + secure; + document.cookie = + "PREFS=" + + cookieData + + "; SameSite=Lax; path=/; domain=" + + domainUsed + + "; expires=" + + date.toGMTString() + + ";" + + secure; - video_data.params.volume = volumeValue; - video_data.params.speed = speedValue; + 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("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("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); - } +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.premiere_timestamp && + Math.round(new Date() / 1000) < video_data.premiere_timestamp +) { + player.getChild("bigPlayButton").hide(); } if (video_data.params.save_player_pos) { - const url = new URL(location); - const hasTimeParam = url.searchParams.has('t'); - const rememberedTime = get_video_time(); - let lastUpdated = 0; + const url = new URL(location); + const hasTimeParam = url.searchParams.has("t"); + const rememberedTime = get_video_time(); + let lastUpdated = 0; - if(!hasTimeParam) { - if (rememberedTime >= video_data.length_seconds - 20) - set_seconds_after_start(0); - else - set_seconds_after_start(rememberedTime); + if (!hasTimeParam) { + if (rememberedTime >= video_data.length_seconds - 20) + set_seconds_after_start(0); + else set_seconds_after_start(rememberedTime); + } + + 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; } - - 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(); + }); +} else remove_all_video_times(); if (video_data.params.autoplay) { - var bpb = player.getChild('bigPlayButton'); - bpb.hide(); + var bpb = player.getChild("bigPlayButton"); + bpb.hide(); - player.ready(function () { - new Promise(function (resolve, reject) { - setTimeout(function () {resolve(1);}, 1); - }).then(function (result) { - var promise = player.play(); + player.ready(function () { + new Promise(function (resolve, reject) { + setTimeout(function () { + resolve(1); + }, 1); + }).then(function (result) { + var promise = player.play(); - if (promise !== undefined) { - promise.then(function () { - }).catch(function (error) { - bpb.show(); - }); - } - }); + if (promise !== undefined) { + promise + .then(function () {}) + .catch(function (error) { + bpb.show(); + }); + } }); + }); } -if (!video_data.params.listen && video_data.params.quality === 'dash') { - player.httpSourceSelector(); +if (!video_data.params.listen && video_data.params.quality === "dash") { + player.httpSourceSelector(); - if (video_data.params.quality_dash !== 'auto') { - player.ready(function () { - player.on('loadedmetadata', function () { - 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); - }); - }); + if (video_data.params.quality_dash !== "auto") { + player.ready(function () { + player.on("loadedmetadata", function () { + 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; }); - } + }); + }); + } } player.vttThumbnails({ - src: '/api/v1/storyboards/' + video_data.id + '?height=90', - showTimestamp: true + src: "/api/v1/storyboards/" + video_data.id + "?height=90", + showTimestamp: true, }); // Enable annotations if (!video_data.params.listen && video_data.params.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); + 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; - } + 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 }); - } - } - }); + 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, + }); + } + }, + }, + ); + }); } function change_volume(delta) { - const curVolume = player.volume(); - let newVolume = curVolume + delta; - newVolume = helpers.clamp(newVolume, 0, 1); - player.volume(newVolume); + const curVolume = player.volume(); + let newVolume = curVolume + delta; + newVolume = helpers.clamp(newVolume, 0, 1); + player.volume(newVolume); } function toggle_muted() { - player.muted(!player.muted()); + player.muted(!player.muted()); } function skip_seconds(delta) { - const duration = player.duration(); - const curTime = player.currentTime(); - let newTime = curTime + delta; - newTime = helpers.clamp(newTime, 0, duration); - player.currentTime(newTime); + const duration = player.duration(); + const curTime = player.currentTime(); + let newTime = curTime + delta; + newTime = helpers.clamp(newTime, 0, duration); + player.currentTime(newTime); } function set_seconds_after_start(delta) { - const start = video_data.params.video_start; - player.currentTime(start + delta); + const start = video_data.params.video_start; + player.currentTime(start + delta); } function save_video_time(seconds) { - const all_video_times = get_all_video_times(); - all_video_times[video_data.id] = seconds; - helpers.storage.set(save_player_pos_key, all_video_times); + const all_video_times = get_all_video_times(); + all_video_times[video_data.id] = seconds; + helpers.storage.set(save_player_pos_key, all_video_times); } function get_video_time() { - return get_all_video_times()[video_data.id] || 0; + return get_all_video_times()[video_data.id] || 0; } function get_all_video_times() { - return helpers.storage.get(save_player_pos_key) || {}; + return helpers.storage.get(save_player_pos_key) || {}; } function remove_all_video_times() { - helpers.storage.remove(save_player_pos_key); + helpers.storage.remove(save_player_pos_key); } function set_time_percent(percent) { - const duration = player.duration(); - const newTime = duration * (percent / 100); - player.currentTime(newTime); + const duration = player.duration(); + const newTime = duration * (percent / 100); + player.currentTime(newTime); } -function play() { player.play(); } -function pause() { player.pause(); } -function stop() { player.pause(); player.currentTime(0); } -function toggle_play() { player.paused() ? play() : pause(); } +function play() { + player.play(); +} +function pause() { + player.pause(); +} +function stop() { + player.pause(); + player.currentTime(0); +} +function toggle_play() { + player.paused() ? play() : pause(); +} const toggle_captions = (function () { - let toggledTrack = null; + let toggledTrack = null; - function bindChange(onOrOff) { - player.textTracks()[onOrOff]('change', function (e) { - toggledTrack = null; - }); + function bindChange(onOrOff) { + player.textTracks()[onOrOff]("change", function (e) { + toggledTrack = null; + }); + } + + // Wrapper function to ignore our own emitted events and only listen + // to events emitted by Video.js on click on the captions menu items. + function setMode(track, mode) { + bindChange("off"); + track.mode = mode; + setTimeout(function () { + bindChange("on"); + }, 0); + } + + bindChange("on"); + return function () { + if (toggledTrack !== null) { + if (toggledTrack.mode !== "showing") { + setMode(toggledTrack, "showing"); + } else { + setMode(toggledTrack, "disabled"); + } + toggledTrack = null; + return; } - // Wrapper function to ignore our own emitted events and only listen - // to events emitted by Video.js on click on the captions menu items. - function setMode(track, mode) { - bindChange('off'); - track.mode = mode; - setTimeout(function () { - bindChange('on'); - }, 0); + // Used as a fallback if no captions are currently active. + // TODO: Make this more intelligent by e.g. relying on browser language. + let fallbackCaptionsTrack = null; + + const tracks = player.textTracks(); + for (let i = 0; i < tracks.length; i++) { + const track = tracks[i]; + if (track.kind !== "captions") continue; + + if (fallbackCaptionsTrack === null) { + fallbackCaptionsTrack = track; + } + if (track.mode === "showing") { + setMode(track, "disabled"); + toggledTrack = track; + return; + } } - bindChange('on'); - return function () { - if (toggledTrack !== null) { - if (toggledTrack.mode !== 'showing') { - setMode(toggledTrack, 'showing'); - } else { - setMode(toggledTrack, 'disabled'); - } - toggledTrack = null; - return; - } - - // Used as a fallback if no captions are currently active. - // TODO: Make this more intelligent by e.g. relying on browser language. - let fallbackCaptionsTrack = null; - - const tracks = player.textTracks(); - for (let i = 0; i < tracks.length; i++) { - const track = tracks[i]; - if (track.kind !== 'captions') continue; - - if (fallbackCaptionsTrack === null) { - fallbackCaptionsTrack = track; - } - if (track.mode === 'showing') { - setMode(track, 'disabled'); - toggledTrack = track; - return; - } - } - - // Fallback if no captions are currently active. - if (fallbackCaptionsTrack !== null) { - setMode(fallbackCaptionsTrack, 'showing'); - toggledTrack = fallbackCaptionsTrack; - } - }; + // Fallback if no captions are currently active. + if (fallbackCaptionsTrack !== null) { + setMode(fallbackCaptionsTrack, "showing"); + toggledTrack = fallbackCaptionsTrack; + } + }; })(); function toggle_fullscreen() { - player.isFullscreen() ? player.exitFullscreen() : player.requestFullscreen(); + player.isFullscreen() ? player.exitFullscreen() : player.requestFullscreen(); } function increase_playback_rate(steps) { - const maxIndex = options.playbackRates.length - 1; - const curIndex = options.playbackRates.indexOf(player.playbackRate()); - let newIndex = curIndex + steps; - newIndex = helpers.clamp(newIndex, 0, maxIndex); - player.playbackRate(options.playbackRates[newIndex]); + const maxIndex = options.playbackRates.length - 1; + const curIndex = options.playbackRates.indexOf(player.playbackRate()); + let newIndex = curIndex + steps; + newIndex = helpers.clamp(newIndex, 0, maxIndex); + player.playbackRate(options.playbackRates[newIndex]); } -addEventListener('keydown', function (e) { - if (e.target.tagName.toLowerCase() === 'input') { - // Ignore input when focus is on certain elements, e.g. form fields. - return; +addEventListener( + "keydown", + function (e) { + if (e.target.tagName.toLowerCase() === "input") { + // Ignore input when focus is on certain elements, e.g. form fields. + return; } // See https://github.com/ctd1500/videojs-hotkeys/blob/bb4a158b2e214ccab87c2e7b95f42bc45c6bfd87/videojs.hotkeys.js#L310-L313 - const isPlayerFocused = false - || e.target === document.querySelector('.video-js') - || e.target === document.querySelector('.vjs-tech') - || e.target === document.querySelector('.iframeblocker') - || e.target === document.querySelector('.vjs-control-bar') - ; + const isPlayerFocused = + false || + e.target === document.querySelector(".video-js") || + e.target === document.querySelector(".vjs-tech") || + e.target === document.querySelector(".iframeblocker") || + e.target === document.querySelector(".vjs-control-bar"); let action = null; const code = e.keyCode; const decoratedKey = - e.key - + (e.altKey ? '+alt' : '') - + (e.ctrlKey ? '+ctrl' : '') - + (e.metaKey ? '+meta' : '') - ; + e.key + + (e.altKey ? "+alt" : "") + + (e.ctrlKey ? "+ctrl" : "") + + (e.metaKey ? "+meta" : ""); switch (decoratedKey) { - case ' ': - case 'k': - case 'MediaPlayPause': - action = toggle_play; - break; + case " ": + case "k": + case "MediaPlayPause": + action = toggle_play; + break; - case 'MediaPlay': action = play; break; - case 'MediaPause': action = pause; break; - case 'MediaStop': action = stop; break; + case "MediaPlay": + action = play; + break; + case "MediaPause": + action = pause; + break; + case "MediaStop": + action = stop; + break; - case 'ArrowUp': - if (isPlayerFocused) action = change_volume.bind(this, 0.1); - break; - case 'ArrowDown': - if (isPlayerFocused) action = change_volume.bind(this, -0.1); - break; + case "ArrowUp": + if (isPlayerFocused) action = change_volume.bind(this, 0.1); + break; + case "ArrowDown": + if (isPlayerFocused) action = change_volume.bind(this, -0.1); + break; - case 'm': - action = toggle_muted; - break; + case "m": + action = toggle_muted; + break; - case 'ArrowRight': - case 'MediaFastForward': - action = skip_seconds.bind(this, 5 * player.playbackRate()); - break; - case 'ArrowLeft': - case 'MediaTrackPrevious': - action = skip_seconds.bind(this, -5 * player.playbackRate()); - break; - case 'l': - action = skip_seconds.bind(this, 10 * player.playbackRate()); - break; - case 'j': - action = skip_seconds.bind(this, -10 * player.playbackRate()); - break; + case "ArrowRight": + case "MediaFastForward": + action = skip_seconds.bind(this, 5 * player.playbackRate()); + break; + case "ArrowLeft": + case "MediaTrackPrevious": + action = skip_seconds.bind(this, -5 * player.playbackRate()); + break; + case "l": + action = skip_seconds.bind(this, 10 * player.playbackRate()); + break; + case "j": + action = skip_seconds.bind(this, -10 * player.playbackRate()); + break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - // Ignore numpad numbers - if (code > 57) break; + case "0": + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + case "8": + case "9": + // Ignore numpad numbers + if (code > 57) break; - const percent = (code - 48) * 10; - action = set_time_percent.bind(this, percent); - break; + const percent = (code - 48) * 10; + action = set_time_percent.bind(this, percent); + break; - case 'c': action = toggle_captions; break; - case 'f': action = toggle_fullscreen; break; + case "c": + action = toggle_captions; + break; + case "f": + action = toggle_fullscreen; + break; - case 'N': - case 'MediaTrackNext': - action = next_video; - break; - case 'P': - case 'MediaTrackPrevious': - // TODO: Add support to play back previous video. - break; + case "N": + case "MediaTrackNext": + action = next_video; + break; + case "P": + case "MediaTrackPrevious": + // TODO: Add support to play back previous video. + break; - // TODO: More precise step. Now FPS is taken equal to 29.97 - // Common FPS: https://forum.videohelp.com/threads/81868#post323588 - // Possible solution is new HTMLVideoElement.requestVideoFrameCallback() https://wicg.github.io/video-rvfc/ - case ',': action = function () { pause(); skip_seconds(-1/29.97); }; break; - case '.': action = function () { pause(); skip_seconds( 1/29.97); }; break; + // TODO: More precise step. Now FPS is taken equal to 29.97 + // Common FPS: https://forum.videohelp.com/threads/81868#post323588 + // Possible solution is new HTMLVideoElement.requestVideoFrameCallback() https://wicg.github.io/video-rvfc/ + case ",": + action = function () { + pause(); + skip_seconds(-1 / 29.97); + }; + break; + case ".": + action = function () { + pause(); + skip_seconds(1 / 29.97); + }; + break; - case '>': action = increase_playback_rate.bind(this, 1); break; - case '<': action = increase_playback_rate.bind(this, -1); break; + case ">": + action = increase_playback_rate.bind(this, 1); + break; + case "<": + action = increase_playback_rate.bind(this, -1); + break; - default: - console.info('Unhandled key down event: %s:', decoratedKey, e); - break; + default: + console.info("Unhandled key down event: %s:", decoratedKey, e); + break; } if (action) { - e.preventDefault(); - action(); + e.preventDefault(); + action(); } -}, false); + }, + false, +); // Add support for controlling the player volume by scrolling over it. Adapted from // https://github.com/ctd1500/videojs-hotkeys/blob/bb4a158b2e214ccab87c2e7b95f42bc45c6bfd87/videojs.hotkeys.js#L292-L328 (function () { - const pEl = document.getElementById('player'); + const pEl = document.getElementById("player"); - var volumeHover = false; - var volumeSelector = pEl.querySelector('.vjs-volume-menu-button') || pEl.querySelector('.vjs-volume-panel'); - if (volumeSelector !== null) { - volumeSelector.onmouseover = function () { volumeHover = true; }; - volumeSelector.onmouseout = function () { volumeHover = false; }; - } + var volumeHover = false; + var volumeSelector = + pEl.querySelector(".vjs-volume-menu-button") || + pEl.querySelector(".vjs-volume-panel"); + if (volumeSelector !== null) { + volumeSelector.onmouseover = function () { + volumeHover = true; + }; + volumeSelector.onmouseout = function () { + volumeHover = false; + }; + } - function mouseScroll(event) { - // When controls are disabled, hotkeys will be disabled as well - if (!player.controls() || !volumeHover) return; + function mouseScroll(event) { + // When controls are disabled, hotkeys will be disabled as well + if (!player.controls() || !volumeHover) return; - event.preventDefault(); - var wheelMove = event.wheelDelta || -event.detail; - var volumeSign = Math.sign(wheelMove); + event.preventDefault(); + var wheelMove = event.wheelDelta || -event.detail; + var volumeSign = Math.sign(wheelMove); - change_volume(volumeSign * 0.05); // decrease/increase by 5% - } + change_volume(volumeSign * 0.05); // decrease/increase by 5% + } - player.on('mousewheel', mouseScroll); - player.on('DOMMouseScroll', mouseScroll); -}()); + player.on("mousewheel", mouseScroll); + player.on("DOMMouseScroll", mouseScroll); +})(); // Since videojs-share can sometimes be blocked, we defer it until last if (player.share) player.share(shareOptions); // show the preferred caption by default if (player_data.preferred_caption_found) { - player.ready(function () { - if (!video_data.params.listen && video_data.params.quality === 'dash') { - // play.textTracks()[0] on DASH mode is showing some debug messages - player.textTracks()[1].mode = 'showing'; - } else { - player.textTracks()[0].mode = 'showing'; - } - }); + player.ready(function () { + if (!video_data.params.listen && video_data.params.quality === "dash") { + // play.textTracks()[0] on DASH mode is showing some debug messages + player.textTracks()[1].mode = "showing"; + } else { + player.textTracks()[0].mode = "showing"; + } + }); } // Safari audio double duration fix -if (navigator.vendor === 'Apple Computer, Inc.' && video_data.params.listen) { - player.on('loadedmetadata', function () { - player.on('timeupdate', function () { - if (player.remainingTime() < player.duration() / 2 && player.remainingTime() >= 2) { - player.currentTime(player.duration() - 1); - } - }); +if (navigator.vendor === "Apple Computer, Inc." && video_data.params.listen) { + player.on("loadedmetadata", function () { + player.on("timeupdate", function () { + if ( + player.remainingTime() < player.duration() / 2 && + player.remainingTime() >= 2 + ) { + player.currentTime(player.duration() - 1); + } }); + }); } // Safari screen timeout on looped video playback fix -if (navigator.vendor === 'Apple Computer, Inc.' && !video_data.params.listen && video_data.params.video_loop) { - player.loop(false); - player.ready(function () { - player.on('ended', function () { - player.currentTime(0); - player.play(); - }); +if ( + navigator.vendor === "Apple Computer, Inc." && + !video_data.params.listen && + video_data.params.video_loop +) { + player.loop(false); + player.ready(function () { + player.on("ended", function () { + player.currentTime(0); + player.play(); }); + }); } // Watch on Invidious link -if (location.pathname.startsWith('/embed/')) { - const Button = videojs.getComponent('Button'); - let watch_on_invidious_button = new Button(player); +if (location.pathname.startsWith("/embed/")) { + const Button = videojs.getComponent("Button"); + let watch_on_invidious_button = new Button(player); - // Create hyperlink for current instance - var redirect_element = document.createElement('a'); - redirect_element.setAttribute('href', location.pathname.replace('/embed/', '/watch?v=')); - redirect_element.appendChild(document.createTextNode('Invidious')); + // Create hyperlink for current instance + var redirect_element = document.createElement("a"); + redirect_element.setAttribute( + "href", + location.pathname.replace("/embed/", "/watch?v="), + ); + redirect_element.appendChild(document.createTextNode("Invidious")); - watch_on_invidious_button.el().appendChild(redirect_element); - watch_on_invidious_button.addClass('watch-on-invidious'); + watch_on_invidious_button.el().appendChild(redirect_element); + watch_on_invidious_button.addClass("watch-on-invidious"); - var cb = player.getChild('ControlBar'); - cb.addChild(watch_on_invidious_button); + var cb = player.getChild("ControlBar"); + cb.addChild(watch_on_invidious_button); } -addEventListener('DOMContentLoaded', function () { - // Save time during redirection on another instance - const changeInstanceLink = document.querySelector('#watch-on-another-invidious-instance > a'); - if (changeInstanceLink) changeInstanceLink.addEventListener('click', function () { - changeInstanceLink.href = addCurrentTimeToURL(changeInstanceLink.href); +addEventListener("DOMContentLoaded", function () { + // Save time during redirection on another instance + const changeInstanceLink = document.querySelector( + "#watch-on-another-invidious-instance > a", + ); + if (changeInstanceLink) + changeInstanceLink.addEventListener("click", function () { + changeInstanceLink.href = addCurrentTimeToURL(changeInstanceLink.href); }); }); diff --git a/assets/js/playlist_widget.js b/assets/js/playlist_widget.js index d40ae985..7001061b 100644 --- a/assets/js/playlist_widget.js +++ b/assets/js/playlist_widget.js @@ -1,55 +1,83 @@ -'use strict'; -var playlist_data = JSON.parse(document.getElementById('playlist_data').textContent); -var payload = 'csrf_token=' + playlist_data.csrf_token; +"use strict"; +var playlist_data = JSON.parse( + document.getElementById("playlist_data").textContent, +); +var payload = "csrf_token=" + playlist_data.csrf_token; function add_playlist_video(event) { - const target = event.target; - var select = document.querySelector("#playlists"); - var option = select.children[select.selectedIndex]; + const target = event.target; + var select = document.querySelector("#playlists"); + var option = select.children[select.selectedIndex]; - var url = '/playlist_ajax?action=add_video&redirect=false' + - '&video_id=' + target.getAttribute('data-id') + - '&playlist_id=' + option.getAttribute('data-plid'); + var url = + "/playlist_ajax?action=add_video&redirect=false" + + "&video_id=" + + target.getAttribute("data-id") + + "&playlist_id=" + + option.getAttribute("data-plid"); - helpers.xhr('POST', url, {payload: payload}, { - on200: function (response) { - option.textContent = '✓ ' + option.textContent; - } - }); + helpers.xhr( + "POST", + url, + { payload: payload }, + { + on200: function (response) { + option.textContent = "✓ " + option.textContent; + }, + }, + ); } function add_playlist_item(event) { - event.preventDefault(); - const target = event.target; - const video_id = target.getAttribute('data-id'); - var card = document.querySelector(`#video-card-${video_id}`); - card.classList.add("hide"); + event.preventDefault(); + const target = event.target; + const video_id = target.getAttribute("data-id"); + var card = document.querySelector(`#video-card-${video_id}`); + card.classList.add("hide"); - var url = '/playlist_ajax?action=add_video&redirect=false' + - '&video_id=' + target.getAttribute('data-id') + - '&playlist_id=' + target.getAttribute('data-plid'); + var url = + "/playlist_ajax?action=add_video&redirect=false" + + "&video_id=" + + target.getAttribute("data-id") + + "&playlist_id=" + + target.getAttribute("data-plid"); - helpers.xhr('POST', url, {payload: payload}, { - onNon200: function (xhr) { - card.classList.remove("hide"); - } - }); + helpers.xhr( + "POST", + url, + { payload: payload }, + { + onNon200: function (xhr) { + card.classList.remove("hide"); + }, + }, + ); } function remove_playlist_item(event) { - event.preventDefault(); - const target = event.target; - const video_index = target.getAttribute('data-index'); - const card = document.querySelector(`.video-card [data-index="${video_index}"]`) - card.classList.add("hide"); + event.preventDefault(); + const target = event.target; + const video_index = target.getAttribute("data-index"); + const card = document.querySelector( + `.video-card [data-index="${video_index}"]`, + ); + card.classList.add("hide"); - var url = '/playlist_ajax?action=remove_video&redirect=false' + - '&set_video_id=' + target.getAttribute('data-index') + - '&playlist_id=' + target.getAttribute('data-plid'); + var url = + "/playlist_ajax?action=remove_video&redirect=false" + + "&set_video_id=" + + target.getAttribute("data-index") + + "&playlist_id=" + + target.getAttribute("data-plid"); - helpers.xhr('POST', url, {payload: payload}, { - onNon200: function (xhr) { - card.classList.remove("hide"); - } - }); + helpers.xhr( + "POST", + url, + { payload: payload }, + { + onNon200: function (xhr) { + card.classList.remove("hide"); + }, + }, + ); } diff --git a/assets/js/post.js b/assets/js/post.js index fcbc9155..ba09acbb 100644 --- a/assets/js/post.js +++ b/assets/js/post.js @@ -1,3 +1,3 @@ -addEventListener('load', function (e) { - get_youtube_comments(); +addEventListener("load", function (e) { + get_youtube_comments(); }); diff --git a/assets/js/silvermine-videojs-quality-selector.min.js b/assets/js/silvermine-videojs-quality-selector.min.js index 1877047d..b80bc733 100644 --- a/assets/js/silvermine-videojs-quality-selector.min.js +++ b/assets/js/silvermine-videojs-quality-selector.min.js @@ -1,4 +1,1549 @@ /*! @silvermine/videojs-quality-selector 2022-04-13 v1.1.2-43-gaa06e72-dirty */ -!function u(o,c,a){function l(e,n){if(!c[e]){if(!o[e]){var t="function"==typeof require&&require;if(!n&&t)return t(e,!0);if(s)return s(e,!0);var r=new Error("Cannot find module '"+e+"'");throw r.code="MODULE_NOT_FOUND",r}var i=c[e]={exports:{}};o[e][0].call(i.exports,function(n){return l(o[e][1][n]||n)},i,i.exports,u,o,c,a)}return c[e].exports}for(var s="function"==typeof require&&require,n=0;n":">",'"':""","'":"'","`":"`"},W=h.invert(P);h.escape=D(P),h.unescape=D(W),h.result=function(n,e,t){h.isArray(e)||(e=[e]);var r=e.length;if(!r)return h.isFunction(t)?t.call(n):t;for(var i=0;i/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};function Y(n){return"\\"+K[n]}var z=/(.)^/,K={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},G=/\\|'|\r|\n|\u2028|\u2029/g;h.template=function(u,n,e){!n&&e&&(n=e),n=h.defaults({},n,h.templateSettings);var t,r=RegExp([(n.escape||z).source,(n.interpolate||z).source,(n.evaluate||z).source].join("|")+"|$","g"),o=0,c="__p+='";u.replace(r,function(n,e,t,r,i){return c+=u.slice(o,i).replace(G,Y),o=i+n.length,e?c+="'+\n((__t=("+e+"))==null?'':_.escape(__t))+\n'":t?c+="'+\n((__t=("+t+"))==null?'':__t)+\n'":r&&(c+="';\n"+r+"\n__p+='"),n}),c+="';\n",n.variable||(c="with(obj||{}){\n"+c+"}\n"),c="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+c+"return __p;\n";try{t=new Function(n.variable||"obj","_",c)}catch(n){throw n.source=c,n}function i(n){return t.call(this,n,h)}var a=n.variable||"obj";return i.source="function("+a+"){\n"+c+"}",i},h.chain=function(n){var e=h(n);return e._chain=!0,e};function H(n,e){return n._chain?h(e).chain():e}h.mixin=function(t){return h.each(h.functions(t),function(n){var e=h[n]=t[n];h.prototype[n]=function(){var n=[this._wrapped];return i.apply(n,arguments),H(this,e.apply(h,n))}}),h},h.mixin(h),h.each(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=r[e];h.prototype[e]=function(){var n=this._wrapped;return t.apply(n,arguments),"shift"!==e&&"splice"!==e||0!==n.length||delete n[0],H(this,n)}}),h.each(["concat","join","slice"],function(n){var e=r[n];h.prototype[n]=function(){return H(this,e.apply(this._wrapped,arguments))}}),h.prototype.value=function(){return this._wrapped},h.prototype.valueOf=h.prototype.toJSON=h.prototype.value,h.prototype.toString=function(){return String(this._wrapped)},"function"==typeof define&&define.amd&&define("underscore",[],function(){return h})}()}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],3:[function(n,e,t){"use strict";var i=n("underscore"),u=n("../events");e.exports=function(n){var r=n.getComponent("MenuItem");return n.extend(r,{constructor:function(n,e){var t=e.source;if(!i.isObject(t))throw new Error('was not provided a "source" object, but rather: '+typeof t);e=i.extend({selectable:!0,label:t.label},e),r.call(this,n,e),this.source=t},handleClick:function(n){r.prototype.handleClick.call(this,n),this.player().trigger(u.QUALITY_REQUESTED,this.source)}})}},{"../events":5,underscore:2}],4:[function(n,e,t){"use strict";var i=n("underscore"),u=n("../events"),o=n("./QualityOption"),c="vjs-quality-changing";e.exports=function(n){var e,r=n.getComponent("MenuButton"),t=o(n);return e=n.extend(r,{constructor:function(t,n){r.call(this,t,n),t.on(u.QUALITY_REQUESTED,function(n,e){this.setSelectedSource(e),t.addClass(c),t.one("loadeddata",function(){t.removeClass(c)})}.bind(this)),t.on(u.PLAYER_SOURCES_CHANGED,function(){this.update()}.bind(this)),t.on(u.QUALITY_SELECTED,function(n,e){this.setSelectedSource(e)}.bind(this)),t.one("ready",function(){this.selectedSrc=t.src(),this.update()}.bind(this)),this.controlText("Open quality selector menu")},setSelectedSource:function(n){var e=n?n.src:void 0;this.selectedSrc!==e&&(this.selectedSrc=e,i.each(this.items,function(n){n.selected(n.source.src===e)}))},createItems:function(){var e=this.player(),n=e.currentSources();return n=n.filter(function(n){return null==n.hidequalityoption}),i.map(n,function(n){return new t(e,{source:n,selected:n.src===this.selectedSrc})}.bind(this))},buildWrapperCSSClass:function(){return"vjs-quality-selector "+r.prototype.buildWrapperCSSClass.call(this)}}),n.registerComponent("QualitySelector",e),e}},{"../events":5,"./QualityOption":3,underscore:2}],5:[function(n,e,t){"use strict";e.exports={QUALITY_REQUESTED:"qualityRequested",QUALITY_SELECTED:"qualitySelected",PLAYER_SOURCES_CHANGED:"playerSourcesChanged"}},{}],6:[function(n,e,t){"use strict";var c=n("underscore"),r=n("./events"),i=n("./components/QualitySelector"),u=n("./middleware/SourceInterceptor"),a=n("./util/SafeSeek");e.exports=function(n){n=n||window.videojs,i(n),u(n),n.hook("setup",function(o){o.on(r.QUALITY_REQUESTED,function(n,e){var t=o.currentSources(),r=o.currentTime(),i=o.playbackRate(),u=o.paused();c.each(t,function(n){n.selected=!1}),c.findWhere(t,{src:e.src}).selected=!0,o._qualitySelectorSafeSeek&&o._qualitySelectorSafeSeek.onQualitySelectionChange(),o.src(t),o.ready(function(){o._qualitySelectorSafeSeek&&!o._qualitySelectorSafeSeek.hasFinished()||(o._qualitySelectorSafeSeek=new a(o,r),o.playbackRate(i)),u||o.play()})})})},e.exports.EVENTS=r},{"./components/QualitySelector":4,"./events":5,"./middleware/SourceInterceptor":7,"./util/SafeSeek":9,underscore:2}],7:[function(n,e,t){"use strict";var u=n("underscore"),o=n("../events");e.exports=function(n){n.use("*",function(i){return{setSource:function(n,e){var t,r=i.currentSources();i._qualitySelectorSafeSeek&&i._qualitySelectorSafeSeek.onPlayerSourcesChange(),u.isEqual(r,i._qualitySelectorPreviousSources)||(i.trigger(o.PLAYER_SOURCES_CHANGED,r),i._qualitySelectorPreviousSources=r),t=u.find(r,function(n){return!0===n.selected||"true"===n.selected||"selected"===n.selected})||n,i.trigger(o.QUALITY_SELECTED,t),e(null,t)}}})}},{"../events":5,underscore:2}],8:[function(n,e,t){"use strict";n("./index")()},{"./index":6}],9:[function(n,e,t){"use strict";var r=n("class.extend");e.exports=r.extend({init:function(n,e){this._player=n,this._seekToTime=e,this._hasFinished=!1,this._keepThisInstanceWhenPlayerSourcesChange=!1,this._seekWhenSafe()},_seekWhenSafe:function(){this._player.readyState()<3?(this._seekFn=this._seek.bind(this),this._player.one("canplay",this._seekFn)):this._seek()},onPlayerSourcesChange:function(){this._keepThisInstanceWhenPlayerSourcesChange?this._keepThisInstanceWhenPlayerSourcesChange=!1:this.cancel()},onQualitySelectionChange:function(){this.hasFinished()||(this._keepThisInstanceWhenPlayerSourcesChange=!0)},_seek:function(){this._player.currentTime(this._seekToTime),this._keepThisInstanceWhenPlayerSourcesChange=!1,this._hasFinished=!0},hasFinished:function(){return this._hasFinished},cancel:function(){this._player.off("canplay",this._seekFn),this._keepThisInstanceWhenPlayerSourcesChange=!1,this._hasFinished=!0}})},{"class.extend":1}]},{},[8]); -//# sourceMappingURL=silvermine-videojs-quality-selector.min.js.map \ No newline at end of file +!(function u(o, c, a) { + function l(e, n) { + if (!c[e]) { + if (!o[e]) { + var t = "function" == typeof require && require; + if (!n && t) return t(e, !0); + if (s) return s(e, !0); + var r = new Error("Cannot find module '" + e + "'"); + throw ((r.code = "MODULE_NOT_FOUND"), r); + } + var i = (c[e] = { exports: {} }); + o[e][0].call( + i.exports, + function (n) { + return l(o[e][1][n] || n); + }, + i, + i.exports, + u, + o, + c, + a, + ); + } + return c[e].exports; + } + for ( + var s = "function" == typeof require && require, n = 0; + n < a.length; + n++ + ) + l(a[n]); + return l; +})( + { + 1: [ + function (n, e, t) { + !(function () { + var u = !1, + o = /xyz/.test(function () { + xyz; + }) + ? /\b_super\b/ + : /.*/; + (this.Class = function () {}), + (Class.extend = function (n) { + var i = this.prototype; + u = !0; + var e = new this(); + for (var t in ((u = !1), n)) + e[t] = + "function" == typeof n[t] && + "function" == typeof i[t] && + o.test(n[t]) + ? (function (t, r) { + return function () { + var n = this._super; + this._super = i[t]; + var e = r.apply(this, arguments); + return (this._super = n), e; + }; + })(t, n[t]) + : n[t]; + function r() { + !u && this.init && this.init.apply(this, arguments); + } + return ( + (((r.prototype = e).constructor = r).extend = arguments.callee), + r + ); + }), + (e.exports = Class); + })(); + }, + {}, + ], + 2: [ + function (n, J, $) { + (function (V) { + !(function () { + function t() {} + var n = + ("object" == typeof self && self.self === self && self) || + ("object" == typeof V && V.global === V && V) || + this || + {}, + e = n._, + r = Array.prototype, + o = Object.prototype, + f = "undefined" != typeof Symbol ? Symbol.prototype : null, + i = r.push, + a = r.slice, + p = o.toString, + u = o.hasOwnProperty, + c = Array.isArray, + l = Object.keys, + s = Object.create, + h = function (n) { + return n instanceof h + ? n + : this instanceof h + ? void (this._wrapped = n) + : new h(n); + }; + void 0 === $ || $.nodeType + ? (n._ = h) + : (void 0 !== J && + !J.nodeType && + J.exports && + ($ = J.exports = h), + ($._ = h)), + (h.VERSION = "1.9.1"); + function d(i, u, n) { + if (void 0 === u) return i; + switch (null == n ? 3 : n) { + case 1: + return function (n) { + return i.call(u, n); + }; + case 3: + return function (n, e, t) { + return i.call(u, n, e, t); + }; + case 4: + return function (n, e, t, r) { + return i.call(u, n, e, t, r); + }; + } + return function () { + return i.apply(u, arguments); + }; + } + function y(n, e, t) { + return h.iteratee !== v + ? h.iteratee(n, e) + : null == n + ? h.identity + : h.isFunction(n) + ? d(n, e, t) + : h.isObject(n) && !h.isArray(n) + ? h.matcher(n) + : h.property(n); + } + var v; + h.iteratee = v = function (n, e) { + return y(n, e, 1 / 0); + }; + function g(i, u) { + return ( + (u = null == u ? i.length - 1 : +u), + function () { + for ( + var n = Math.max(arguments.length - u, 0), + e = Array(n), + t = 0; + t < n; + t++ + ) + e[t] = arguments[t + u]; + switch (u) { + case 0: + return i.call(this, e); + case 1: + return i.call(this, arguments[0], e); + case 2: + return i.call(this, arguments[0], arguments[1], e); + } + var r = Array(u + 1); + for (t = 0; t < u; t++) r[t] = arguments[t]; + return (r[u] = e), i.apply(this, r); + } + ); + } + function S(n) { + if (!h.isObject(n)) return {}; + if (s) return s(n); + t.prototype = n; + var e = new t(); + return (t.prototype = null), e; + } + function m(e) { + return function (n) { + return null == n ? void 0 : n[e]; + }; + } + function _(n, e) { + return null != n && u.call(n, e); + } + function b(n, e) { + for (var t = e.length, r = 0; r < t; r++) { + if (null == n) return; + n = n[e[r]]; + } + return t ? n : void 0; + } + function x(n) { + var e = j(n); + return "number" == typeof e && 0 <= e && e <= k; + } + var k = Math.pow(2, 53) - 1, + j = m("length"); + (h.each = h.forEach = + function (n, e, t) { + var r, i; + if (((e = d(e, t)), x(n))) + for (r = 0, i = n.length; r < i; r++) e(n[r], r, n); + else { + var u = h.keys(n); + for (r = 0, i = u.length; r < i; r++) e(n[u[r]], u[r], n); + } + return n; + }), + (h.map = h.collect = + function (n, e, t) { + e = y(e, t); + for ( + var r = !x(n) && h.keys(n), + i = (r || n).length, + u = Array(i), + o = 0; + o < i; + o++ + ) { + var c = r ? r[o] : o; + u[o] = e(n[c], c, n); + } + return u; + }); + function E(a) { + return function (n, e, t, r) { + var i = 3 <= arguments.length; + return (function (n, e, t, r) { + var i = !x(n) && h.keys(n), + u = (i || n).length, + o = 0 < a ? 0 : u - 1; + for ( + r || ((t = n[i ? i[o] : o]), (o += a)); + 0 <= o && o < u; + o += a + ) { + var c = i ? i[o] : o; + t = e(t, n[c], c, n); + } + return t; + })(n, d(e, r, 4), t, i); + }; + } + (h.reduce = h.foldl = h.inject = E(1)), + (h.reduceRight = h.foldr = E(-1)), + (h.find = h.detect = + function (n, e, t) { + var r = (x(n) ? h.findIndex : h.findKey)(n, e, t); + if (void 0 !== r && -1 !== r) return n[r]; + }), + (h.filter = h.select = + function (n, r, e) { + var i = []; + return ( + (r = y(r, e)), + h.each(n, function (n, e, t) { + r(n, e, t) && i.push(n); + }), + i + ); + }), + (h.reject = function (n, e, t) { + return h.filter(n, h.negate(y(e)), t); + }), + (h.every = h.all = + function (n, e, t) { + e = y(e, t); + for ( + var r = !x(n) && h.keys(n), i = (r || n).length, u = 0; + u < i; + u++ + ) { + var o = r ? r[u] : u; + if (!e(n[o], o, n)) return !1; + } + return !0; + }), + (h.some = h.any = + function (n, e, t) { + e = y(e, t); + for ( + var r = !x(n) && h.keys(n), i = (r || n).length, u = 0; + u < i; + u++ + ) { + var o = r ? r[u] : u; + if (e(n[o], o, n)) return !0; + } + return !1; + }), + (h.contains = + h.includes = + h.include = + function (n, e, t, r) { + return ( + x(n) || (n = h.values(n)), + ("number" == typeof t && !r) || (t = 0), + 0 <= h.indexOf(n, e, t) + ); + }), + (h.invoke = g(function (n, t, r) { + var i, u; + return ( + h.isFunction(t) + ? (u = t) + : h.isArray(t) && + ((i = t.slice(0, -1)), (t = t[t.length - 1])), + h.map(n, function (n) { + var e = u; + if (!e) { + if ((i && i.length && (n = b(n, i)), null == n)) return; + e = n[t]; + } + return null == e ? e : e.apply(n, r); + }) + ); + })), + (h.pluck = function (n, e) { + return h.map(n, h.property(e)); + }), + (h.where = function (n, e) { + return h.filter(n, h.matcher(e)); + }), + (h.findWhere = function (n, e) { + return h.find(n, h.matcher(e)); + }), + (h.max = function (n, r, e) { + var t, + i, + u = -1 / 0, + o = -1 / 0; + if ( + null == r || + ("number" == typeof r && "object" != typeof n[0] && null != n) + ) + for ( + var c = 0, a = (n = x(n) ? n : h.values(n)).length; + c < a; + c++ + ) + null != (t = n[c]) && u < t && (u = t); + else + (r = y(r, e)), + h.each(n, function (n, e, t) { + (i = r(n, e, t)), + (o < i || (i === -1 / 0 && u === -1 / 0)) && + ((u = n), (o = i)); + }); + return u; + }), + (h.min = function (n, r, e) { + var t, + i, + u = 1 / 0, + o = 1 / 0; + if ( + null == r || + ("number" == typeof r && "object" != typeof n[0] && null != n) + ) + for ( + var c = 0, a = (n = x(n) ? n : h.values(n)).length; + c < a; + c++ + ) + null != (t = n[c]) && t < u && (u = t); + else + (r = y(r, e)), + h.each(n, function (n, e, t) { + ((i = r(n, e, t)) < o || (i === 1 / 0 && u === 1 / 0)) && + ((u = n), (o = i)); + }); + return u; + }), + (h.shuffle = function (n) { + return h.sample(n, 1 / 0); + }), + (h.sample = function (n, e, t) { + if (null == e || t) + return x(n) || (n = h.values(n)), n[h.random(n.length - 1)]; + var r = x(n) ? h.clone(n) : h.values(n), + i = j(r); + e = Math.max(Math.min(e, i), 0); + for (var u = i - 1, o = 0; o < e; o++) { + var c = h.random(o, u), + a = r[o]; + (r[o] = r[c]), (r[c] = a); + } + return r.slice(0, e); + }), + (h.sortBy = function (n, r, e) { + var i = 0; + return ( + (r = y(r, e)), + h.pluck( + h + .map(n, function (n, e, t) { + return { value: n, index: i++, criteria: r(n, e, t) }; + }) + .sort(function (n, e) { + var t = n.criteria, + r = e.criteria; + if (t !== r) { + if (r < t || void 0 === t) return 1; + if (t < r || void 0 === r) return -1; + } + return n.index - e.index; + }), + "value", + ) + ); + }); + function w(o, e) { + return function (r, i, n) { + var u = e ? [[], []] : {}; + return ( + (i = y(i, n)), + h.each(r, function (n, e) { + var t = i(n, e, r); + o(u, n, t); + }), + u + ); + }; + } + (h.groupBy = w(function (n, e, t) { + _(n, t) ? n[t].push(e) : (n[t] = [e]); + })), + (h.indexBy = w(function (n, e, t) { + n[t] = e; + })), + (h.countBy = w(function (n, e, t) { + _(n, t) ? n[t]++ : (n[t] = 1); + })); + var A = + /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g; + (h.toArray = function (n) { + return n + ? h.isArray(n) + ? a.call(n) + : h.isString(n) + ? n.match(A) + : x(n) + ? h.map(n, h.identity) + : h.values(n) + : []; + }), + (h.size = function (n) { + return null == n ? 0 : x(n) ? n.length : h.keys(n).length; + }), + (h.partition = w(function (n, e, t) { + n[t ? 0 : 1].push(e); + }, !0)), + (h.first = + h.head = + h.take = + function (n, e, t) { + return null == n || n.length < 1 + ? null == e + ? void 0 + : [] + : null == e || t + ? n[0] + : h.initial(n, n.length - e); + }), + (h.initial = function (n, e, t) { + return a.call( + n, + 0, + Math.max(0, n.length - (null == e || t ? 1 : e)), + ); + }), + (h.last = function (n, e, t) { + return null == n || n.length < 1 + ? null == e + ? void 0 + : [] + : null == e || t + ? n[n.length - 1] + : h.rest(n, Math.max(0, n.length - e)); + }), + (h.rest = + h.tail = + h.drop = + function (n, e, t) { + return a.call(n, null == e || t ? 1 : e); + }), + (h.compact = function (n) { + return h.filter(n, Boolean); + }); + var T = function (n, e, t, r) { + for (var i = (r = r || []).length, u = 0, o = j(n); u < o; u++) { + var c = n[u]; + if (x(c) && (h.isArray(c) || h.isArguments(c))) + if (e) for (var a = 0, l = c.length; a < l; ) r[i++] = c[a++]; + else T(c, e, t, r), (i = r.length); + else t || (r[i++] = c); + } + return r; + }; + (h.flatten = function (n, e) { + return T(n, e, !1); + }), + (h.without = g(function (n, e) { + return h.difference(n, e); + })), + (h.uniq = h.unique = + function (n, e, t, r) { + h.isBoolean(e) || ((r = t), (t = e), (e = !1)), + null != t && (t = y(t, r)); + for (var i = [], u = [], o = 0, c = j(n); o < c; o++) { + var a = n[o], + l = t ? t(a, o, n) : a; + e && !t + ? ((o && u === l) || i.push(a), (u = l)) + : t + ? h.contains(u, l) || (u.push(l), i.push(a)) + : h.contains(i, a) || i.push(a); + } + return i; + }), + (h.union = g(function (n) { + return h.uniq(T(n, !0, !0)); + })), + (h.intersection = function (n) { + for ( + var e = [], t = arguments.length, r = 0, i = j(n); + r < i; + r++ + ) { + var u = n[r]; + if (!h.contains(e, u)) { + var o; + for (o = 1; o < t && h.contains(arguments[o], u); o++); + o === t && e.push(u); + } + } + return e; + }), + (h.difference = g(function (n, e) { + return ( + (e = T(e, !0, !0)), + h.filter(n, function (n) { + return !h.contains(e, n); + }) + ); + })), + (h.unzip = function (n) { + for ( + var e = (n && h.max(n, j).length) || 0, t = Array(e), r = 0; + r < e; + r++ + ) + t[r] = h.pluck(n, r); + return t; + }), + (h.zip = g(h.unzip)), + (h.object = function (n, e) { + for (var t = {}, r = 0, i = j(n); r < i; r++) + e ? (t[n[r]] = e[r]) : (t[n[r][0]] = n[r][1]); + return t; + }); + function O(u) { + return function (n, e, t) { + e = y(e, t); + for ( + var r = j(n), i = 0 < u ? 0 : r - 1; + 0 <= i && i < r; + i += u + ) + if (e(n[i], i, n)) return i; + return -1; + }; + } + (h.findIndex = O(1)), + (h.findLastIndex = O(-1)), + (h.sortedIndex = function (n, e, t, r) { + for (var i = (t = y(t, r, 1))(e), u = 0, o = j(n); u < o; ) { + var c = Math.floor((u + o) / 2); + t(n[c]) < i ? (u = c + 1) : (o = c); + } + return u; + }); + function C(u, o, c) { + return function (n, e, t) { + var r = 0, + i = j(n); + if ("number" == typeof t) + 0 < u + ? (r = 0 <= t ? t : Math.max(t + i, r)) + : (i = 0 <= t ? Math.min(t + 1, i) : t + i + 1); + else if (c && t && i) return n[(t = c(n, e))] === e ? t : -1; + if (e != e) + return 0 <= (t = o(a.call(n, r, i), h.isNaN)) ? t + r : -1; + for (t = 0 < u ? r : i - 1; 0 <= t && t < i; t += u) + if (n[t] === e) return t; + return -1; + }; + } + (h.indexOf = C(1, h.findIndex, h.sortedIndex)), + (h.lastIndexOf = C(-1, h.findLastIndex)), + (h.range = function (n, e, t) { + null == e && ((e = n || 0), (n = 0)), + (t = t || (e < n ? -1 : 1)); + for ( + var r = Math.max(Math.ceil((e - n) / t), 0), + i = Array(r), + u = 0; + u < r; + u++, n += t + ) + i[u] = n; + return i; + }), + (h.chunk = function (n, e) { + if (null == e || e < 1) return []; + for (var t = [], r = 0, i = n.length; r < i; ) + t.push(a.call(n, r, (r += e))); + return t; + }); + function I(n, e, t, r, i) { + if (!(r instanceof e)) return n.apply(t, i); + var u = S(n.prototype), + o = n.apply(u, i); + return h.isObject(o) ? o : u; + } + (h.bind = g(function (e, t, r) { + if (!h.isFunction(e)) + throw new TypeError("Bind must be called on a function"); + var i = g(function (n) { + return I(e, i, t, this, r.concat(n)); + }); + return i; + })), + (h.partial = g(function (i, u) { + var o = h.partial.placeholder, + c = function () { + for ( + var n = 0, e = u.length, t = Array(e), r = 0; + r < e; + r++ + ) + t[r] = u[r] === o ? arguments[n++] : u[r]; + for (; n < arguments.length; ) t.push(arguments[n++]); + return I(i, c, this, this, t); + }; + return c; + })), + ((h.partial.placeholder = h).bindAll = g(function (n, e) { + var t = (e = T(e, !1, !1)).length; + if (t < 1) + throw new Error("bindAll must be passed function names"); + for (; t--; ) { + var r = e[t]; + n[r] = h.bind(n[r], n); + } + })), + (h.memoize = function (r, i) { + var u = function (n) { + var e = u.cache, + t = "" + (i ? i.apply(this, arguments) : n); + return _(e, t) || (e[t] = r.apply(this, arguments)), e[t]; + }; + return (u.cache = {}), u; + }), + (h.delay = g(function (n, e, t) { + return setTimeout(function () { + return n.apply(null, t); + }, e); + })), + (h.defer = h.partial(h.delay, h, 1)), + (h.throttle = function (t, r, i) { + var u, + o, + c, + a, + l = 0; + i = i || {}; + function s() { + (l = !1 === i.leading ? 0 : h.now()), + (u = null), + (a = t.apply(o, c)), + u || (o = c = null); + } + function n() { + var n = h.now(); + l || !1 !== i.leading || (l = n); + var e = r - (n - l); + return ( + (o = this), + (c = arguments), + e <= 0 || r < e + ? (u && (clearTimeout(u), (u = null)), + (l = n), + (a = t.apply(o, c)), + u || (o = c = null)) + : u || !1 === i.trailing || (u = setTimeout(s, e)), + a + ); + } + return ( + (n.cancel = function () { + clearTimeout(u), (l = 0), (u = o = c = null); + }), + n + ); + }), + (h.debounce = function (t, r, i) { + function u(n, e) { + (o = null), e && (c = t.apply(n, e)); + } + var o, + c, + n = g(function (n) { + if ((o && clearTimeout(o), i)) { + var e = !o; + (o = setTimeout(u, r)), e && (c = t.apply(this, n)); + } else o = h.delay(u, r, this, n); + return c; + }); + return ( + (n.cancel = function () { + clearTimeout(o), (o = null); + }), + n + ); + }), + (h.wrap = function (n, e) { + return h.partial(e, n); + }), + (h.negate = function (n) { + return function () { + return !n.apply(this, arguments); + }; + }), + (h.compose = function () { + var t = arguments, + r = t.length - 1; + return function () { + for (var n = r, e = t[r].apply(this, arguments); n--; ) + e = t[n].call(this, e); + return e; + }; + }), + (h.after = function (n, e) { + return function () { + if (--n < 1) return e.apply(this, arguments); + }; + }), + (h.before = function (n, e) { + var t; + return function () { + return ( + 0 < --n && (t = e.apply(this, arguments)), + n <= 1 && (e = null), + t + ); + }; + }), + (h.once = h.partial(h.before, 2)), + (h.restArguments = g); + function F(n, e) { + var t = M.length, + r = n.constructor, + i = (h.isFunction(r) && r.prototype) || o, + u = "constructor"; + for (_(n, u) && !h.contains(e, u) && e.push(u); t--; ) + (u = M[t]) in n && + n[u] !== i[u] && + !h.contains(e, u) && + e.push(u); + } + var q = !{ toString: null }.propertyIsEnumerable("toString"), + M = [ + "valueOf", + "isPrototypeOf", + "toString", + "propertyIsEnumerable", + "hasOwnProperty", + "toLocaleString", + ]; + (h.keys = function (n) { + if (!h.isObject(n)) return []; + if (l) return l(n); + var e = []; + for (var t in n) _(n, t) && e.push(t); + return q && F(n, e), e; + }), + (h.allKeys = function (n) { + if (!h.isObject(n)) return []; + var e = []; + for (var t in n) e.push(t); + return q && F(n, e), e; + }), + (h.values = function (n) { + for ( + var e = h.keys(n), t = e.length, r = Array(t), i = 0; + i < t; + i++ + ) + r[i] = n[e[i]]; + return r; + }), + (h.mapObject = function (n, e, t) { + e = y(e, t); + for ( + var r = h.keys(n), i = r.length, u = {}, o = 0; + o < i; + o++ + ) { + var c = r[o]; + u[c] = e(n[c], c, n); + } + return u; + }), + (h.pairs = function (n) { + for ( + var e = h.keys(n), t = e.length, r = Array(t), i = 0; + i < t; + i++ + ) + r[i] = [e[i], n[e[i]]]; + return r; + }), + (h.invert = function (n) { + for (var e = {}, t = h.keys(n), r = 0, i = t.length; r < i; r++) + e[n[t[r]]] = t[r]; + return e; + }), + (h.functions = h.methods = + function (n) { + var e = []; + for (var t in n) h.isFunction(n[t]) && e.push(t); + return e.sort(); + }); + function N(a, l) { + return function (n) { + var e = arguments.length; + if ((l && (n = Object(n)), e < 2 || null == n)) return n; + for (var t = 1; t < e; t++) + for ( + var r = arguments[t], i = a(r), u = i.length, o = 0; + o < u; + o++ + ) { + var c = i[o]; + (l && void 0 !== n[c]) || (n[c] = r[c]); + } + return n; + }; + } + (h.extend = N(h.allKeys)), + (h.extendOwn = h.assign = N(h.keys)), + (h.findKey = function (n, e, t) { + e = y(e, t); + for (var r, i = h.keys(n), u = 0, o = i.length; u < o; u++) + if (e(n[(r = i[u])], r, n)) return r; + }); + function R(n, e, t) { + return e in t; + } + var Q, L; + (h.pick = g(function (n, e) { + var t = {}, + r = e[0]; + if (null == n) return t; + h.isFunction(r) + ? (1 < e.length && (r = d(r, e[1])), (e = h.allKeys(n))) + : ((r = R), (e = T(e, !1, !1)), (n = Object(n))); + for (var i = 0, u = e.length; i < u; i++) { + var o = e[i], + c = n[o]; + r(c, o, n) && (t[o] = c); + } + return t; + })), + (h.omit = g(function (n, t) { + var e, + r = t[0]; + return ( + h.isFunction(r) + ? ((r = h.negate(r)), 1 < t.length && (e = t[1])) + : ((t = h.map(T(t, !1, !1), String)), + (r = function (n, e) { + return !h.contains(t, e); + })), + h.pick(n, r, e) + ); + })), + (h.defaults = N(h.allKeys, !0)), + (h.create = function (n, e) { + var t = S(n); + return e && h.extendOwn(t, e), t; + }), + (h.clone = function (n) { + return h.isObject(n) + ? h.isArray(n) + ? n.slice() + : h.extend({}, n) + : n; + }), + (h.tap = function (n, e) { + return e(n), n; + }), + (h.isMatch = function (n, e) { + var t = h.keys(e), + r = t.length; + if (null == n) return !r; + for (var i = Object(n), u = 0; u < r; u++) { + var o = t[u]; + if (e[o] !== i[o] || !(o in i)) return !1; + } + return !0; + }), + (Q = function (n, e, t, r) { + if (n === e) return 0 !== n || 1 / n == 1 / e; + if (null == n || null == e) return !1; + if (n != n) return e != e; + var i = typeof n; + return ( + ("function" == i || "object" == i || "object" == typeof e) && + L(n, e, t, r) + ); + }), + (L = function (n, e, t, r) { + n instanceof h && (n = n._wrapped), + e instanceof h && (e = e._wrapped); + var i = p.call(n); + if (i !== p.call(e)) return !1; + switch (i) { + case "[object RegExp]": + case "[object String]": + return "" + n == "" + e; + case "[object Number]": + return +n != +n + ? +e != +e + : 0 == +n + ? 1 / +n == 1 / e + : +n == +e; + case "[object Date]": + case "[object Boolean]": + return +n == +e; + case "[object Symbol]": + return f.valueOf.call(n) === f.valueOf.call(e); + } + var u = "[object Array]" === i; + if (!u) { + if ("object" != typeof n || "object" != typeof e) return !1; + var o = n.constructor, + c = e.constructor; + if ( + o !== c && + !( + h.isFunction(o) && + o instanceof o && + h.isFunction(c) && + c instanceof c + ) && + "constructor" in n && + "constructor" in e + ) + return !1; + } + r = r || []; + for (var a = (t = t || []).length; a--; ) + if (t[a] === n) return r[a] === e; + if ((t.push(n), r.push(e), u)) { + if ((a = n.length) !== e.length) return !1; + for (; a--; ) if (!Q(n[a], e[a], t, r)) return !1; + } else { + var l, + s = h.keys(n); + if (((a = s.length), h.keys(e).length !== a)) return !1; + for (; a--; ) + if (((l = s[a]), !_(e, l) || !Q(n[l], e[l], t, r))) + return !1; + } + return t.pop(), r.pop(), !0; + }), + (h.isEqual = function (n, e) { + return Q(n, e); + }), + (h.isEmpty = function (n) { + return ( + null == n || + (x(n) && (h.isArray(n) || h.isString(n) || h.isArguments(n)) + ? 0 === n.length + : 0 === h.keys(n).length) + ); + }), + (h.isElement = function (n) { + return !(!n || 1 !== n.nodeType); + }), + (h.isArray = + c || + function (n) { + return "[object Array]" === p.call(n); + }), + (h.isObject = function (n) { + var e = typeof n; + return "function" == e || ("object" == e && !!n); + }), + h.each( + [ + "Arguments", + "Function", + "String", + "Number", + "Date", + "RegExp", + "Error", + "Symbol", + "Map", + "WeakMap", + "Set", + "WeakSet", + ], + function (e) { + h["is" + e] = function (n) { + return p.call(n) === "[object " + e + "]"; + }; + }, + ), + h.isArguments(arguments) || + (h.isArguments = function (n) { + return _(n, "callee"); + }); + var U = n.document && n.document.childNodes; + "function" != typeof /./ && + "object" != typeof Int8Array && + "function" != typeof U && + (h.isFunction = function (n) { + return "function" == typeof n || !1; + }), + (h.isFinite = function (n) { + return !h.isSymbol(n) && isFinite(n) && !isNaN(parseFloat(n)); + }), + (h.isNaN = function (n) { + return h.isNumber(n) && isNaN(n); + }), + (h.isBoolean = function (n) { + return !0 === n || !1 === n || "[object Boolean]" === p.call(n); + }), + (h.isNull = function (n) { + return null === n; + }), + (h.isUndefined = function (n) { + return void 0 === n; + }), + (h.has = function (n, e) { + if (!h.isArray(e)) return _(n, e); + for (var t = e.length, r = 0; r < t; r++) { + var i = e[r]; + if (null == n || !u.call(n, i)) return !1; + n = n[i]; + } + return !!t; + }), + (h.noConflict = function () { + return (n._ = e), this; + }), + (h.identity = function (n) { + return n; + }), + (h.constant = function (n) { + return function () { + return n; + }; + }), + (h.noop = function () {}), + (h.property = function (e) { + return h.isArray(e) + ? function (n) { + return b(n, e); + } + : m(e); + }), + (h.propertyOf = function (e) { + return null == e + ? function () {} + : function (n) { + return h.isArray(n) ? b(e, n) : e[n]; + }; + }), + (h.matcher = h.matches = + function (e) { + return ( + (e = h.extendOwn({}, e)), + function (n) { + return h.isMatch(n, e); + } + ); + }), + (h.times = function (n, e, t) { + var r = Array(Math.max(0, n)); + e = d(e, t, 1); + for (var i = 0; i < n; i++) r[i] = e(i); + return r; + }), + (h.random = function (n, e) { + return ( + null == e && ((e = n), (n = 0)), + n + Math.floor(Math.random() * (e - n + 1)) + ); + }), + (h.now = + Date.now || + function () { + return new Date().getTime(); + }); + function D(e) { + function t(n) { + return e[n]; + } + var n = "(?:" + h.keys(e).join("|") + ")", + r = RegExp(n), + i = RegExp(n, "g"); + return function (n) { + return ( + (n = null == n ? "" : "" + n), r.test(n) ? n.replace(i, t) : n + ); + }; + } + var P = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`", + }, + W = h.invert(P); + (h.escape = D(P)), + (h.unescape = D(W)), + (h.result = function (n, e, t) { + h.isArray(e) || (e = [e]); + var r = e.length; + if (!r) return h.isFunction(t) ? t.call(n) : t; + for (var i = 0; i < r; i++) { + var u = null == n ? void 0 : n[e[i]]; + void 0 === u && ((u = t), (i = r)), + (n = h.isFunction(u) ? u.call(n) : u); + } + return n; + }); + var B = 0; + (h.uniqueId = function (n) { + var e = ++B + ""; + return n ? n + e : e; + }), + (h.templateSettings = { + evaluate: /<%([\s\S]+?)%>/g, + interpolate: /<%=([\s\S]+?)%>/g, + escape: /<%-([\s\S]+?)%>/g, + }); + function Y(n) { + return "\\" + K[n]; + } + var z = /(.)^/, + K = { + "'": "'", + "\\": "\\", + "\r": "r", + "\n": "n", + "\u2028": "u2028", + "\u2029": "u2029", + }, + G = /\\|'|\r|\n|\u2028|\u2029/g; + (h.template = function (u, n, e) { + !n && e && (n = e), (n = h.defaults({}, n, h.templateSettings)); + var t, + r = RegExp( + [ + (n.escape || z).source, + (n.interpolate || z).source, + (n.evaluate || z).source, + ].join("|") + "|$", + "g", + ), + o = 0, + c = "__p+='"; + u.replace(r, function (n, e, t, r, i) { + return ( + (c += u.slice(o, i).replace(G, Y)), + (o = i + n.length), + e + ? (c += + "'+\n((__t=(" + e + "))==null?'':_.escape(__t))+\n'") + : t + ? (c += "'+\n((__t=(" + t + "))==null?'':__t)+\n'") + : r && (c += "';\n" + r + "\n__p+='"), + n + ); + }), + (c += "';\n"), + n.variable || (c = "with(obj||{}){\n" + c + "}\n"), + (c = + "var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n" + + c + + "return __p;\n"); + try { + t = new Function(n.variable || "obj", "_", c); + } catch (n) { + throw ((n.source = c), n); + } + function i(n) { + return t.call(this, n, h); + } + var a = n.variable || "obj"; + return (i.source = "function(" + a + "){\n" + c + "}"), i; + }), + (h.chain = function (n) { + var e = h(n); + return (e._chain = !0), e; + }); + function H(n, e) { + return n._chain ? h(e).chain() : e; + } + (h.mixin = function (t) { + return ( + h.each(h.functions(t), function (n) { + var e = (h[n] = t[n]); + h.prototype[n] = function () { + var n = [this._wrapped]; + return i.apply(n, arguments), H(this, e.apply(h, n)); + }; + }), + h + ); + }), + h.mixin(h), + h.each( + [ + "pop", + "push", + "reverse", + "shift", + "sort", + "splice", + "unshift", + ], + function (e) { + var t = r[e]; + h.prototype[e] = function () { + var n = this._wrapped; + return ( + t.apply(n, arguments), + ("shift" !== e && "splice" !== e) || + 0 !== n.length || + delete n[0], + H(this, n) + ); + }; + }, + ), + h.each(["concat", "join", "slice"], function (n) { + var e = r[n]; + h.prototype[n] = function () { + return H(this, e.apply(this._wrapped, arguments)); + }; + }), + (h.prototype.value = function () { + return this._wrapped; + }), + (h.prototype.valueOf = h.prototype.toJSON = h.prototype.value), + (h.prototype.toString = function () { + return String(this._wrapped); + }), + "function" == typeof define && + define.amd && + define("underscore", [], function () { + return h; + }); + })(); + }).call( + this, + "undefined" != typeof global + ? global + : "undefined" != typeof self + ? self + : "undefined" != typeof window + ? window + : {}, + ); + }, + {}, + ], + 3: [ + function (n, e, t) { + "use strict"; + var i = n("underscore"), + u = n("../events"); + e.exports = function (n) { + var r = n.getComponent("MenuItem"); + return n.extend(r, { + constructor: function (n, e) { + var t = e.source; + if (!i.isObject(t)) + throw new Error( + 'was not provided a "source" object, but rather: ' + typeof t, + ); + (e = i.extend({ selectable: !0, label: t.label }, e)), + r.call(this, n, e), + (this.source = t); + }, + handleClick: function (n) { + r.prototype.handleClick.call(this, n), + this.player().trigger(u.QUALITY_REQUESTED, this.source); + }, + }); + }; + }, + { "../events": 5, underscore: 2 }, + ], + 4: [ + function (n, e, t) { + "use strict"; + var i = n("underscore"), + u = n("../events"), + o = n("./QualityOption"), + c = "vjs-quality-changing"; + e.exports = function (n) { + var e, + r = n.getComponent("MenuButton"), + t = o(n); + return ( + (e = n.extend(r, { + constructor: function (t, n) { + r.call(this, t, n), + t.on( + u.QUALITY_REQUESTED, + function (n, e) { + this.setSelectedSource(e), + t.addClass(c), + t.one("loadeddata", function () { + t.removeClass(c); + }); + }.bind(this), + ), + t.on( + u.PLAYER_SOURCES_CHANGED, + function () { + this.update(); + }.bind(this), + ), + t.on( + u.QUALITY_SELECTED, + function (n, e) { + this.setSelectedSource(e); + }.bind(this), + ), + t.one( + "ready", + function () { + (this.selectedSrc = t.src()), this.update(); + }.bind(this), + ), + this.controlText("Open quality selector menu"); + }, + setSelectedSource: function (n) { + var e = n ? n.src : void 0; + this.selectedSrc !== e && + ((this.selectedSrc = e), + i.each(this.items, function (n) { + n.selected(n.source.src === e); + })); + }, + createItems: function () { + var e = this.player(), + n = e.currentSources(); + return ( + (n = n.filter(function (n) { + return null == n.hidequalityoption; + })), + i.map( + n, + function (n) { + return new t(e, { + source: n, + selected: n.src === this.selectedSrc, + }); + }.bind(this), + ) + ); + }, + buildWrapperCSSClass: function () { + return ( + "vjs-quality-selector " + + r.prototype.buildWrapperCSSClass.call(this) + ); + }, + })), + n.registerComponent("QualitySelector", e), + e + ); + }; + }, + { "../events": 5, "./QualityOption": 3, underscore: 2 }, + ], + 5: [ + function (n, e, t) { + "use strict"; + e.exports = { + QUALITY_REQUESTED: "qualityRequested", + QUALITY_SELECTED: "qualitySelected", + PLAYER_SOURCES_CHANGED: "playerSourcesChanged", + }; + }, + {}, + ], + 6: [ + function (n, e, t) { + "use strict"; + var c = n("underscore"), + r = n("./events"), + i = n("./components/QualitySelector"), + u = n("./middleware/SourceInterceptor"), + a = n("./util/SafeSeek"); + (e.exports = function (n) { + (n = n || window.videojs), + i(n), + u(n), + n.hook("setup", function (o) { + o.on(r.QUALITY_REQUESTED, function (n, e) { + var t = o.currentSources(), + r = o.currentTime(), + i = o.playbackRate(), + u = o.paused(); + c.each(t, function (n) { + n.selected = !1; + }), + (c.findWhere(t, { src: e.src }).selected = !0), + o._qualitySelectorSafeSeek && + o._qualitySelectorSafeSeek.onQualitySelectionChange(), + o.src(t), + o.ready(function () { + (o._qualitySelectorSafeSeek && + !o._qualitySelectorSafeSeek.hasFinished()) || + ((o._qualitySelectorSafeSeek = new a(o, r)), + o.playbackRate(i)), + u || o.play(); + }); + }); + }); + }), + (e.exports.EVENTS = r); + }, + { + "./components/QualitySelector": 4, + "./events": 5, + "./middleware/SourceInterceptor": 7, + "./util/SafeSeek": 9, + underscore: 2, + }, + ], + 7: [ + function (n, e, t) { + "use strict"; + var u = n("underscore"), + o = n("../events"); + e.exports = function (n) { + n.use("*", function (i) { + return { + setSource: function (n, e) { + var t, + r = i.currentSources(); + i._qualitySelectorSafeSeek && + i._qualitySelectorSafeSeek.onPlayerSourcesChange(), + u.isEqual(r, i._qualitySelectorPreviousSources) || + (i.trigger(o.PLAYER_SOURCES_CHANGED, r), + (i._qualitySelectorPreviousSources = r)), + (t = + u.find(r, function (n) { + return ( + !0 === n.selected || + "true" === n.selected || + "selected" === n.selected + ); + }) || n), + i.trigger(o.QUALITY_SELECTED, t), + e(null, t); + }, + }; + }); + }; + }, + { "../events": 5, underscore: 2 }, + ], + 8: [ + function (n, e, t) { + "use strict"; + n("./index")(); + }, + { "./index": 6 }, + ], + 9: [ + function (n, e, t) { + "use strict"; + var r = n("class.extend"); + e.exports = r.extend({ + init: function (n, e) { + (this._player = n), + (this._seekToTime = e), + (this._hasFinished = !1), + (this._keepThisInstanceWhenPlayerSourcesChange = !1), + this._seekWhenSafe(); + }, + _seekWhenSafe: function () { + this._player.readyState() < 3 + ? ((this._seekFn = this._seek.bind(this)), + this._player.one("canplay", this._seekFn)) + : this._seek(); + }, + onPlayerSourcesChange: function () { + this._keepThisInstanceWhenPlayerSourcesChange + ? (this._keepThisInstanceWhenPlayerSourcesChange = !1) + : this.cancel(); + }, + onQualitySelectionChange: function () { + this.hasFinished() || + (this._keepThisInstanceWhenPlayerSourcesChange = !0); + }, + _seek: function () { + this._player.currentTime(this._seekToTime), + (this._keepThisInstanceWhenPlayerSourcesChange = !1), + (this._hasFinished = !0); + }, + hasFinished: function () { + return this._hasFinished; + }, + cancel: function () { + this._player.off("canplay", this._seekFn), + (this._keepThisInstanceWhenPlayerSourcesChange = !1), + (this._hasFinished = !0); + }, + }); + }, + { "class.extend": 1 }, + ], + }, + {}, + [8], +); +//# sourceMappingURL=silvermine-videojs-quality-selector.min.js.map diff --git a/assets/js/sse.js b/assets/js/sse.js index 4f7320b3..4b29f460 100644 --- a/assets/js/sse.js +++ b/assets/js/sse.js @@ -17,18 +17,18 @@ var SSE = function (url, options) { options = options || {}; this.headers = options.headers || {}; - this.payload = options.payload !== undefined ? options.payload : ''; - this.method = options.method || (this.payload && 'POST' || 'GET'); + this.payload = options.payload !== undefined ? options.payload : ""; + this.method = options.method || (this.payload && "POST") || "GET"; - this.FIELD_SEPARATOR = ':'; + this.FIELD_SEPARATOR = ":"; this.listeners = {}; this.xhr = null; this.readyState = this.INITIALIZING; this.progress = 0; - this.chunk = ''; + this.chunk = ""; - this.addEventListener = function(type, listener) { + this.addEventListener = function (type, listener) { if (this.listeners[type] === undefined) { this.listeners[type] = []; } @@ -38,13 +38,13 @@ var SSE = function (url, options) { } }; - this.removeEventListener = function(type, listener) { + this.removeEventListener = function (type, listener) { if (this.listeners[type] === undefined) { return; } var filtered = []; - this.listeners[type].forEach(function(element) { + this.listeners[type].forEach(function (element) { if (element !== listener) { filtered.push(element); } @@ -56,14 +56,14 @@ var SSE = function (url, options) { } }; - this.dispatchEvent = function(e) { + this.dispatchEvent = function (e) { if (!e) { return true; } e.source = this; - var onHandler = 'on' + e.type; + var onHandler = "on" + e.type; if (this.hasOwnProperty(onHandler)) { this[onHandler].call(this, e); if (e.defaultPrevented) { @@ -72,7 +72,7 @@ var SSE = function (url, options) { } if (this.listeners[e.type]) { - return this.listeners[e.type].every(function(callback) { + return this.listeners[e.type].every(function (callback) { callback(e); return !e.defaultPrevented; }); @@ -82,78 +82,82 @@ var SSE = function (url, options) { }; this._setReadyState = function (state) { - var event = new CustomEvent('readystatechange'); + var event = new CustomEvent("readystatechange"); event.readyState = state; this.readyState = state; this.dispatchEvent(event); }; - this._onStreamFailure = function(e) { - this.dispatchEvent(new CustomEvent('error')); + this._onStreamFailure = function (e) { + this.dispatchEvent(new CustomEvent("error")); this.close(); - } + }; - this._onStreamProgress = function(e) { + this._onStreamProgress = function (e) { if (this.xhr.status !== 200 && this.readyState !== this.CLOSED) { this._onStreamFailure(e); return; } if (this.readyState == this.CONNECTING) { - this.dispatchEvent(new CustomEvent('open')); + this.dispatchEvent(new CustomEvent("open")); this._setReadyState(this.OPEN); } var data = this.xhr.responseText.substring(this.progress); this.progress += data.length; - data.split(/(\r\n|\r|\n){2}/g).forEach(function(part) { - if (part.trim().length === 0) { - this.dispatchEvent(this._parseEventChunk(this.chunk.trim())); - this.chunk = ''; - } else { - this.chunk += part; - } - }.bind(this)); + data.split(/(\r\n|\r|\n){2}/g).forEach( + function (part) { + if (part.trim().length === 0) { + this.dispatchEvent(this._parseEventChunk(this.chunk.trim())); + this.chunk = ""; + } else { + this.chunk += part; + } + }.bind(this), + ); }; - this._onStreamLoaded = function(e) { + this._onStreamLoaded = function (e) { this._onStreamProgress(e); // Parse the last chunk. this.dispatchEvent(this._parseEventChunk(this.chunk)); - this.chunk = ''; + this.chunk = ""; }; /** * Parse a received SSE event chunk into a constructed event object. */ - this._parseEventChunk = function(chunk) { + this._parseEventChunk = function (chunk) { if (!chunk || chunk.length === 0) { return null; } - var e = {'id': null, 'retry': null, 'data': '', 'event': 'message'}; - chunk.split(/\n|\r\n|\r/).forEach(function(line) { - line = line.trimRight(); - var index = line.indexOf(this.FIELD_SEPARATOR); - if (index <= 0) { - // Line was either empty, or started with a separator and is a comment. - // Either way, ignore. - return; - } + var e = { id: null, retry: null, data: "", event: "message" }; + chunk.split(/\n|\r\n|\r/).forEach( + function (line) { + line = line.trimRight(); + var index = line.indexOf(this.FIELD_SEPARATOR); + if (index <= 0) { + // Line was either empty, or started with a separator and is a comment. + // Either way, ignore. + return; + } - var field = line.substring(0, index); - if (!(field in e)) { - return; - } + var field = line.substring(0, index); + if (!(field in e)) { + return; + } - var value = line.substring(index + 1).trimLeft(); - if (field === 'data') { - e[field] += value; - } else { - e[field] = value; - } - }.bind(this)); + var value = line.substring(index + 1).trimLeft(); + if (field === "data") { + e[field] += value; + } else { + e[field] = value; + } + }.bind(this), + ); var event = new CustomEvent(e.event); event.data = e.data; @@ -161,21 +165,24 @@ var SSE = function (url, options) { return event; }; - this._checkStreamClosed = function() { + this._checkStreamClosed = function () { if (this.xhr.readyState === XMLHttpRequest.DONE) { this._setReadyState(this.CLOSED); } }; - this.stream = function() { + this.stream = function () { this._setReadyState(this.CONNECTING); this.xhr = new XMLHttpRequest(); - this.xhr.addEventListener('progress', this._onStreamProgress.bind(this)); - this.xhr.addEventListener('load', this._onStreamLoaded.bind(this)); - this.xhr.addEventListener('readystatechange', this._checkStreamClosed.bind(this)); - this.xhr.addEventListener('error', this._onStreamFailure.bind(this)); - this.xhr.addEventListener('abort', this._onStreamFailure.bind(this)); + this.xhr.addEventListener("progress", this._onStreamProgress.bind(this)); + this.xhr.addEventListener("load", this._onStreamLoaded.bind(this)); + this.xhr.addEventListener( + "readystatechange", + this._checkStreamClosed.bind(this), + ); + this.xhr.addEventListener("error", this._onStreamFailure.bind(this)); + this.xhr.addEventListener("abort", this._onStreamFailure.bind(this)); this.xhr.open(this.method, this.url); for (var header in this.headers) { this.xhr.setRequestHeader(header, this.headers[header]); @@ -183,7 +190,7 @@ var SSE = function (url, options) { this.xhr.send(this.payload); }; - this.close = function() { + this.close = function () { if (this.readyState === this.CLOSED) { return; } @@ -195,6 +202,6 @@ var SSE = function (url, options) { }; // Export our SSE module for npm.js -if (typeof exports !== 'undefined') { +if (typeof exports !== "undefined") { exports.SSE = SSE; } diff --git a/assets/js/subscribe_widget.js b/assets/js/subscribe_widget.js index d0a2c975..be934187 100644 --- a/assets/js/subscribe_widget.js +++ b/assets/js/subscribe_widget.js @@ -1,62 +1,80 @@ -'use strict'; -var subscribe_data = JSON.parse(document.getElementById('subscribe_data').textContent); -var payload = 'csrf_token=' + subscribe_data.csrf_token; +"use strict"; +var subscribe_data = JSON.parse( + document.getElementById("subscribe_data").textContent, +); +var payload = "csrf_token=" + subscribe_data.csrf_token; -var subscribe_button = document.getElementById('subscribe'); +var subscribe_button = document.getElementById("subscribe"); -if (subscribe_button.getAttribute('data-type') === 'subscribe') { - subscribe_button.onclick = subscribe; +if (subscribe_button.getAttribute("data-type") === "subscribe") { + subscribe_button.onclick = subscribe; } else { - subscribe_button.onclick = unsubscribe; + subscribe_button.onclick = unsubscribe; } function toggleSubscribeButton() { - subscribe_button.classList.remove("primary"); - subscribe_button.classList.remove("secondary"); - subscribe_button.classList.remove("unsubscribe"); - subscribe_button.classList.remove("subscribe"); + subscribe_button.classList.remove("primary"); + subscribe_button.classList.remove("secondary"); + subscribe_button.classList.remove("unsubscribe"); + subscribe_button.classList.remove("subscribe"); - if (subscribe_button.getAttribute('data-type') === 'subscribe') { - subscribe_button.textContent = subscribe_data.unsubscribe_text + ' | ' + subscribe_data.sub_count_text; - subscribe_button.onclick = unsubscribe; - subscribe_button.classList.add("secondary"); - subscribe_button.classList.add("unsubscribe"); - } else { - subscribe_button.textContent = subscribe_data.subscribe_text + ' | ' + subscribe_data.sub_count_text; - subscribe_button.onclick = subscribe; - subscribe_button.classList.add("primary"); - subscribe_button.classList.add("subscribe"); - } + if (subscribe_button.getAttribute("data-type") === "subscribe") { + subscribe_button.textContent = + subscribe_data.unsubscribe_text + " | " + subscribe_data.sub_count_text; + subscribe_button.onclick = unsubscribe; + subscribe_button.classList.add("secondary"); + subscribe_button.classList.add("unsubscribe"); + } else { + subscribe_button.textContent = + subscribe_data.subscribe_text + " | " + subscribe_data.sub_count_text; + subscribe_button.onclick = subscribe; + subscribe_button.classList.add("primary"); + subscribe_button.classList.add("subscribe"); + } } function subscribe(e) { - e.preventDefault(); - var fallback = subscribe_button.textContent; - toggleSubscribeButton(); + e.preventDefault(); + var fallback = subscribe_button.textContent; + toggleSubscribeButton(); - var url = '/subscription_ajax?action=create_subscription_to_channel&redirect=false' + - '&c=' + subscribe_data.ucid; + var url = + "/subscription_ajax?action=create_subscription_to_channel&redirect=false" + + "&c=" + + subscribe_data.ucid; - helpers.xhr('POST', url, {payload: payload, retries: 5, entity_name: 'subscribe request'}, { - onNon200: function (xhr) { - subscribe_button.onclick = subscribe; - subscribe_button.textContent = fallback; - } - }); + helpers.xhr( + "POST", + url, + { payload: payload, retries: 5, entity_name: "subscribe request" }, + { + onNon200: function (xhr) { + subscribe_button.onclick = subscribe; + subscribe_button.textContent = fallback; + }, + }, + ); } function unsubscribe(e) { - e.preventDefault(); - var fallback = subscribe_button.textContent; - toggleSubscribeButton(); + e.preventDefault(); + var fallback = subscribe_button.textContent; + toggleSubscribeButton(); - var url = '/subscription_ajax?action=remove_subscriptions&redirect=false' + - '&c=' + subscribe_data.ucid; + var url = + "/subscription_ajax?action=remove_subscriptions&redirect=false" + + "&c=" + + subscribe_data.ucid; - helpers.xhr('POST', url, {payload: payload, retries: 5, entity_name: 'unsubscribe request'}, { - onNon200: function (xhr) { - subscribe_button.onclick = unsubscribe; - subscribe_button.textContent = fallback; - } - }); + helpers.xhr( + "POST", + url, + { payload: payload, retries: 5, entity_name: "unsubscribe request" }, + { + onNon200: function (xhr) { + subscribe_button.onclick = unsubscribe; + subscribe_button.textContent = fallback; + }, + }, + ); } diff --git a/assets/js/themes.js b/assets/js/themes.js index 545d2c18..50dc2dfd 100644 --- a/assets/js/themes.js +++ b/assets/js/themes.js @@ -1,46 +1,46 @@ -'use strict'; -var toggle_theme = document.getElementById('toggle_theme'); +"use strict"; +var toggle_theme = document.getElementById("toggle_theme"); -const STORAGE_KEY_THEME = 'dark_mode'; -const THEME_DARK = 'dark'; -const THEME_LIGHT = 'light'; +const STORAGE_KEY_THEME = "dark_mode"; +const THEME_DARK = "dark"; +const THEME_LIGHT = "light"; // TODO: theme state controlled by system -toggle_theme.addEventListener('click', function (e) { - e.preventDefault(); - const isDarkTheme = helpers.storage.get(STORAGE_KEY_THEME) === THEME_DARK; - const newTheme = isDarkTheme ? THEME_LIGHT : THEME_DARK; - setTheme(newTheme); - helpers.storage.set(STORAGE_KEY_THEME, newTheme); - helpers.xhr('GET', '/toggle_theme?redirect=false', {}, {}); +toggle_theme.addEventListener("click", function (e) { + e.preventDefault(); + const isDarkTheme = helpers.storage.get(STORAGE_KEY_THEME) === THEME_DARK; + const newTheme = isDarkTheme ? THEME_LIGHT : THEME_DARK; + setTheme(newTheme); + helpers.storage.set(STORAGE_KEY_THEME, newTheme); + helpers.xhr("GET", "/toggle_theme?redirect=false", {}, {}); }); /** @param {THEME_DARK|THEME_LIGHT} theme */ function setTheme(theme) { - // By default body element has .no-theme class that uses OS theme via CSS @media rules - // It rewrites using hard className below - if (theme === THEME_DARK) { - toggle_theme.children[0].className = 'icon ion-ios-sunny'; - document.body.className = 'dark-theme'; - } else if (theme === THEME_LIGHT) { - toggle_theme.children[0].className = 'icon ion-ios-moon'; - document.body.className = 'light-theme'; - } else { - document.body.className = 'no-theme'; - } + // By default body element has .no-theme class that uses OS theme via CSS @media rules + // It rewrites using hard className below + if (theme === THEME_DARK) { + toggle_theme.children[0].className = "icon ion-ios-sunny"; + document.body.className = "dark-theme"; + } else if (theme === THEME_LIGHT) { + toggle_theme.children[0].className = "icon ion-ios-moon"; + document.body.className = "light-theme"; + } else { + document.body.className = "no-theme"; + } } // Handles theme change event caused by other tab -addEventListener('storage', function (e) { - if (e.key === STORAGE_KEY_THEME) - setTheme(helpers.storage.get(STORAGE_KEY_THEME)); +addEventListener("storage", function (e) { + if (e.key === STORAGE_KEY_THEME) + setTheme(helpers.storage.get(STORAGE_KEY_THEME)); }); // Set theme from preferences on page load -addEventListener('DOMContentLoaded', function () { - const prefTheme = document.getElementById('dark_mode_pref').textContent; - if (prefTheme) { - setTheme(prefTheme); - helpers.storage.set(STORAGE_KEY_THEME, prefTheme); - } +addEventListener("DOMContentLoaded", function () { + const prefTheme = document.getElementById("dark_mode_pref").textContent; + if (prefTheme) { + setTheme(prefTheme); + helpers.storage.set(STORAGE_KEY_THEME, prefTheme); + } }); diff --git a/assets/js/videojs-youtube-annotations.min.js b/assets/js/videojs-youtube-annotations.min.js index c93e14e8..1943744d 100644 --- a/assets/js/videojs-youtube-annotations.min.js +++ b/assets/js/videojs-youtube-annotations.min.js @@ -1 +1,936 @@ -class AnnotationParser{static get defaultAppearanceAttributes(){return{bgColor:16777215,bgOpacity:.8,fgColor:0,textSize:3.15}}static get attributeMap(){return{type:"tp",style:"s",x:"x",y:"y",width:"w",height:"h",sx:"sx",sy:"sy",timeStart:"ts",timeEnd:"te",text:"t",actionType:"at",actionUrl:"au",actionUrlTarget:"aut",actionSeconds:"as",bgOpacity:"bgo",bgColor:"bgc",fgColor:"fgc",textSize:"txsz"}}deserializeAnnotation(serializedAnnotation){const map=this.constructor.attributeMap;const attributes=serializedAnnotation.split(",");const annotation={};for(const attribute of attributes){const[key,value]=attribute.split("=");const mappedKey=this.getKeyByValue(map,key);let finalValue="";if(["text","actionType","actionUrl","actionUrlTarget","type","style"].indexOf(mappedKey)>-1){finalValue=decodeURIComponent(value)}else{finalValue=parseFloat(value,10)}annotation[mappedKey]=finalValue}return annotation}serializeAnnotation(annotation){const map=this.constructor.attributeMap;let serialized="";for(const key in annotation){const mappedKey=map[key];if(["text","actionType","actionUrl","actionUrlTarget"].indexOf(key)>-1&&mappedKey&&annotation.hasOwnProperty(key)){let text=encodeURIComponent(annotation[key]);serialized+=`${mappedKey}=${text},`}else if(["text","actionType","actionUrl","actionUrlTarget"].indexOf("key")===-1&&mappedKey&&annotation.hasOwnProperty(key)){serialized+=`${mappedKey}=${annotation[key]},`}}return serialized.substring(0,serialized.length-1)}deserializeAnnotationList(serializedAnnotationString){const serializedAnnotations=serializedAnnotationString.split(";");serializedAnnotations.length=serializedAnnotations.length-1;const annotations=[];for(const annotation of serializedAnnotations){annotations.push(this.deserializeAnnotation(annotation))}return annotations}serializeAnnotationList(annotations){let serialized="";for(const annotation of annotations){serialized+=this.serializeAnnotation(annotation)+";"}return serialized}xmlToDom(xml){const parser=new DOMParser;const dom=parser.parseFromString(xml,"application/xml");return dom}getAnnotationsFromXml(xml){const dom=this.xmlToDom(xml);return dom.getElementsByTagName("annotation")}parseYoutubeAnnotationList(annotationElements){const annotations=[];for(const el of annotationElements){const parsedAnnotation=this.parseYoutubeAnnotation(el);if(parsedAnnotation)annotations.push(parsedAnnotation)}return annotations}parseYoutubeAnnotation(annotationElement){const base=annotationElement;const attributes=this.getAttributesFromBase(base);if(!attributes.type||attributes.type==="pause")return null;const text=this.getTextFromBase(base);const action=this.getActionFromBase(base);const backgroundShape=this.getBackgroundShapeFromBase(base);if(!backgroundShape)return null;const timeStart=backgroundShape.timeRange.start;const timeEnd=backgroundShape.timeRange.end;if(isNaN(timeStart)||isNaN(timeEnd)||timeStart===null||timeEnd===null){return null}const appearance=this.getAppearanceFromBase(base);let annotation={type:attributes.type,x:backgroundShape.x,y:backgroundShape.y,width:backgroundShape.width,height:backgroundShape.height,timeStart:timeStart,timeEnd:timeEnd};if(attributes.style)annotation.style=attributes.style;if(text)annotation.text=text;if(action)annotation=Object.assign(action,annotation);if(appearance)annotation=Object.assign(appearance,annotation);if(backgroundShape.hasOwnProperty("sx"))annotation.sx=backgroundShape.sx;if(backgroundShape.hasOwnProperty("sy"))annotation.sy=backgroundShape.sy;return annotation}getBackgroundShapeFromBase(base){const movingRegion=base.getElementsByTagName("movingRegion")[0];if(!movingRegion)return null;const regionType=movingRegion.getAttribute("type");const regions=movingRegion.getElementsByTagName(`${regionType}Region`);const timeRange=this.extractRegionTime(regions);const shape={type:regionType,x:parseFloat(regions[0].getAttribute("x"),10),y:parseFloat(regions[0].getAttribute("y"),10),width:parseFloat(regions[0].getAttribute("w"),10),height:parseFloat(regions[0].getAttribute("h"),10),timeRange:timeRange};const sx=regions[0].getAttribute("sx");const sy=regions[0].getAttribute("sy");if(sx)shape.sx=parseFloat(sx,10);if(sy)shape.sy=parseFloat(sy,10);return shape}getAttributesFromBase(base){const attributes={};attributes.type=base.getAttribute("type");attributes.style=base.getAttribute("style");return attributes}getTextFromBase(base){const textElement=base.getElementsByTagName("TEXT")[0];if(textElement)return textElement.textContent}getActionFromBase(base){const actionElement=base.getElementsByTagName("action")[0];if(!actionElement)return null;const typeAttr=actionElement.getAttribute("type");const urlElement=actionElement.getElementsByTagName("url")[0];if(!urlElement)return null;const actionUrlTarget=urlElement.getAttribute("target");const href=urlElement.getAttribute("value");if(href.startsWith("https://www.youtube.com/")){const url=new URL(href);const srcVid=url.searchParams.get("src_vid");const toVid=url.searchParams.get("v");return this.linkOrTimestamp(url,srcVid,toVid,actionUrlTarget)}}linkOrTimestamp(url,srcVid,toVid,actionUrlTarget){if(srcVid&&toVid&&srcVid===toVid){let seconds=0;const hash=url.hash;if(hash&&hash.startsWith("#t=")){const timeString=url.hash.split("#t=")[1];seconds=this.timeStringToSeconds(timeString)}return{actionType:"time",actionSeconds:seconds}}else{return{actionType:"url",actionUrl:url.href,actionUrlTarget:actionUrlTarget}}}getAppearanceFromBase(base){const appearanceElement=base.getElementsByTagName("appearance")[0];const styles=this.constructor.defaultAppearanceAttributes;if(appearanceElement){const bgOpacity=appearanceElement.getAttribute("bgAlpha");const bgColor=appearanceElement.getAttribute("bgColor");const fgColor=appearanceElement.getAttribute("fgColor");const textSize=appearanceElement.getAttribute("textSize");if(bgOpacity)styles.bgOpacity=parseFloat(bgOpacity,10);if(bgColor)styles.bgColor=parseInt(bgColor,10);if(fgColor)styles.fgColor=parseInt(fgColor,10);if(textSize)styles.textSize=parseFloat(textSize,10)}return styles}extractRegionTime(regions){let timeStart=regions[0].getAttribute("t");timeStart=this.hmsToSeconds(timeStart);let timeEnd=regions[regions.length-1].getAttribute("t");timeEnd=this.hmsToSeconds(timeEnd);return{start:timeStart,end:timeEnd}}hmsToSeconds(hms){let p=hms.split(":");let s=0;let m=1;while(p.length>0){s+=m*parseFloat(p.pop(),10);m*=60}return s}timeStringToSeconds(time){let seconds=0;const h=time.split("h");const m=(h[1]||time).split("m");const s=(m[1]||time).split("s");if(h[0]&&h.length===2)seconds+=parseInt(h[0],10)*60*60;if(m[0]&&m.length===2)seconds+=parseInt(m[0],10)*60;if(s[0]&&s.length===2)seconds+=parseInt(s[0],10);return seconds}getKeyByValue(obj,value){for(const key in obj){if(obj.hasOwnProperty(key)){if(obj[key]===value){return key}}}}}class AnnotationRenderer{constructor(annotations,container,playerOptions,updateInterval=1e3){if(!annotations)throw new Error("Annotation objects must be provided");if(!container)throw new Error("An element to contain the annotations must be provided");if(playerOptions&&playerOptions.getVideoTime&&playerOptions.seekTo){this.playerOptions=playerOptions}else{console.info("AnnotationRenderer is running without a player. The update method will need to be called manually.")}this.annotations=annotations;this.container=container;this.annotationsContainer=document.createElement("div");this.annotationsContainer.classList.add("__cxt-ar-annotations-container__");this.annotationsContainer.setAttribute("data-layer","4");this.annotationsContainer.addEventListener("click",e=>{this.annotationClickHandler(e)});this.container.prepend(this.annotationsContainer);this.createAnnotationElements();this.updateAllAnnotationSizes();window.addEventListener("DOMContentLoaded",e=>{this.updateAllAnnotationSizes()});this.updateInterval=updateInterval;this.updateIntervalId=null}changeAnnotationData(annotations){this.stop();this.removeAnnotationElements();this.annotations=annotations;this.createAnnotationElements();this.start()}createAnnotationElements(){for(const annotation of this.annotations){const el=document.createElement("div");el.classList.add("__cxt-ar-annotation__");annotation.__element=el;el.__annotation=annotation;const closeButton=this.createCloseElement();closeButton.addEventListener("click",e=>{el.setAttribute("hidden","");el.setAttribute("data-ar-closed","");if(el.__annotation.__speechBubble){const speechBubble=el.__annotation.__speechBubble;speechBubble.style.display="none"}});el.append(closeButton);if(annotation.text){const textNode=document.createElement("span");textNode.textContent=annotation.text;el.append(textNode);el.setAttribute("data-ar-has-text","")}if(annotation.style==="speech"){const containerDimensions=this.container.getBoundingClientRect();const speechX=this.percentToPixels(containerDimensions.width,annotation.x);const speechY=this.percentToPixels(containerDimensions.height,annotation.y);const speechWidth=this.percentToPixels(containerDimensions.width,annotation.width);const speechHeight=this.percentToPixels(containerDimensions.height,annotation.height);const speechPointX=this.percentToPixels(containerDimensions.width,annotation.sx);const speechPointY=this.percentToPixels(containerDimensions.height,annotation.sy);const bubbleColor=this.getFinalAnnotationColor(annotation,false);const bubble=this.createSvgSpeechBubble(speechX,speechY,speechWidth,speechHeight,speechPointX,speechPointY,bubbleColor,annotation.__element);bubble.style.display="none";bubble.style.overflow="visible";el.style.pointerEvents="none";bubble.__annotationEl=el;annotation.__speechBubble=bubble;const path=bubble.getElementsByTagName("path")[0];path.addEventListener("mouseover",()=>{closeButton.style.display="block";closeButton.style.cursor="pointer";path.setAttribute("fill",this.getFinalAnnotationColor(annotation,true))});path.addEventListener("mouseout",e=>{if(!e.relatedTarget.classList.contains("__cxt-ar-annotation-close__")){closeButton.style.display="none";closeButton.style.cursor="default";path.setAttribute("fill",this.getFinalAnnotationColor(annotation,false))}});closeButton.addEventListener("mouseleave",()=>{closeButton.style.display="none";path.style.cursor="default";closeButton.style.cursor="default";path.setAttribute("fill",this.getFinalAnnotationColor(annotation,false))});el.prepend(bubble)}else if(annotation.type==="highlight"){el.style.backgroundColor="";el.style.border=`2.5px solid ${this.getFinalAnnotationColor(annotation,false)}`;if(annotation.actionType==="url")el.style.cursor="pointer"}else if(annotation.style!=="title"){el.style.backgroundColor=this.getFinalAnnotationColor(annotation);el.addEventListener("mouseenter",()=>{el.style.backgroundColor=this.getFinalAnnotationColor(annotation,true)});el.addEventListener("mouseleave",()=>{el.style.backgroundColor=this.getFinalAnnotationColor(annotation,false)});if(annotation.actionType==="url")el.style.cursor="pointer"}el.style.color=`#${this.decimalToHex(annotation.fgColor)}`;el.setAttribute("data-ar-type",annotation.type);el.setAttribute("hidden","");this.annotationsContainer.append(el)}}createCloseElement(){const svg=document.createElementNS("http://www.w3.org/2000/svg","svg");svg.setAttribute("viewBox","0 0 100 100");svg.classList.add("__cxt-ar-annotation-close__");const path=document.createElementNS(svg.namespaceURI,"path");path.setAttribute("d","M25 25 L 75 75 M 75 25 L 25 75");path.setAttribute("stroke","#bbb");path.setAttribute("stroke-width",10);path.setAttribute("x",5);path.setAttribute("y",5);const circle=document.createElementNS(svg.namespaceURI,"circle");circle.setAttribute("cx",50);circle.setAttribute("cy",50);circle.setAttribute("r",50);svg.append(circle,path);return svg}createSvgSpeechBubble(x,y,width,height,pointX,pointY,color="white",element,svg){const horizontalBaseStartMultiplier=.17379070765180116;const horizontalBaseEndMultiplier=.14896346370154384;const verticalBaseStartMultiplier=.12;const verticalBaseEndMultiplier=.3;let path;if(!svg){svg=document.createElementNS("http://www.w3.org/2000/svg","svg");svg.classList.add("__cxt-ar-annotation-speech-bubble__");path=document.createElementNS("http://www.w3.org/2000/svg","path");path.setAttribute("fill",color);svg.append(path)}else{path=svg.children[0]}svg.style.position="absolute";svg.setAttribute("width","100%");svg.setAttribute("height","100%");svg.style.left="0";svg.style.top="0";let positionStart;let baseStartX=0;let baseStartY=0;let baseEndX=0;let baseEndY=0;let pointFinalX=pointX;let pointFinalY=pointY;let commentRectPath;const pospad=20;let textWidth=0;let textHeight=0;let textX=0;let textY=0;let textElement;let closeElement;if(element){textElement=element.getElementsByTagName("span")[0];closeElement=element.getElementsByClassName("__cxt-ar-annotation-close__")[0]}if(pointX>x+width-width/2&&pointY>y+height){positionStart="br";baseStartX=width-width*horizontalBaseStartMultiplier*2;baseEndX=baseStartX+width*horizontalBaseEndMultiplier;baseStartY=height;baseEndY=height;pointFinalX=pointX-x;pointFinalY=pointY-y;element.style.height=pointY-y;commentRectPath=`L${width} ${height} L${width} 0 L0 0 L0 ${baseStartY} L${baseStartX} ${baseStartY}`;if(textElement){textWidth=width;textHeight=height;textX=0;textY=0}}else if(pointXy+height){positionStart="bl";baseStartX=width*horizontalBaseStartMultiplier;baseEndX=baseStartX+width*horizontalBaseEndMultiplier;baseStartY=height;baseEndY=height;pointFinalX=pointX-x;pointFinalY=pointY-y;element.style.height=`${pointY-y}px`;commentRectPath=`L${width} ${height} L${width} 0 L0 0 L0 ${baseStartY} L${baseStartX} ${baseStartY}`;if(textElement){textWidth=width;textHeight=height;textX=0;textY=0}}else if(pointX>x+width-width/2&&pointYx+width&&pointY>y-pospad&&pointYy&&pointY=start&&videoTimeend)){el.setAttribute("hidden","");if(annotation.style==="speech"&&annotation.__speechBubble){annotation.__speechBubble.style.display="none"}}}}start(){if(!this.playerOptions)throw new Error("playerOptions must be provided to use the start method");const videoTime=this.playerOptions.getVideoTime();if(!this.updateIntervalId){this.update(videoTime);this.updateIntervalId=setInterval(()=>{const videoTime=this.playerOptions.getVideoTime();this.update(videoTime);window.dispatchEvent(new CustomEvent("__ar_renderer_start"))},this.updateInterval)}}stop(){if(!this.playerOptions)throw new Error("playerOptions must be provided to use the stop method");const videoTime=this.playerOptions.getVideoTime();if(this.updateIntervalId){this.update(videoTime);clearInterval(this.updateIntervalId);this.updateIntervalId=null;window.dispatchEvent(new CustomEvent("__ar_renderer_stop"))}}updateAnnotationTextSize(annotation,containerHeight){if(annotation.textSize){const textSize=annotation.textSize/100*containerHeight;annotation.__element.style.fontSize=`${textSize}px`}}updateTextSize(){const containerHeight=this.container.getBoundingClientRect().height;for(const annotation of this.annotations){this.updateAnnotationTextSize(annotation,containerHeight)}}updateCloseSize(containerHeight){if(!containerHeight)containerHeight=this.container.getBoundingClientRect().height;const multiplier=.0423;this.annotationsContainer.style.setProperty("--annotation-close-size",`${containerHeight*multiplier}px`)}updateAnnotationDimensions(annotations,videoWidth,videoHeight){const playerWidth=this.container.getBoundingClientRect().width;const playerHeight=this.container.getBoundingClientRect().height;const widthDivider=playerWidth/videoWidth;const heightDivider=playerHeight/videoHeight;let scaledVideoWidth=playerWidth;let scaledVideoHeight=playerHeight;if(widthDivider%1!==0||heightDivider%1!==0){if(widthDivider>heightDivider){scaledVideoWidth=playerHeight/videoHeight*videoWidth;scaledVideoHeight=playerHeight}else if(heightDivider>widthDivider){scaledVideoWidth=playerWidth;scaledVideoHeight=playerWidth/videoWidth*videoHeight}}const verticalBlackBarWidth=(playerWidth-scaledVideoWidth)/2;const horizontalBlackBarHeight=(playerHeight-scaledVideoHeight)/2;const widthOffsetPercent=verticalBlackBarWidth/playerWidth*100;const heightOffsetPercent=horizontalBlackBarHeight/playerHeight*100;const widthMultiplier=scaledVideoWidth/playerWidth;const heightMultiplier=scaledVideoHeight/playerHeight;for(const annotation of annotations){const el=annotation.__element;let ax=widthOffsetPercent+annotation.x*widthMultiplier;let ay=heightOffsetPercent+annotation.y*heightMultiplier;let aw=annotation.width*widthMultiplier;let ah=annotation.height*heightMultiplier;el.style.left=`${ax}%`;el.style.top=`${ay}%`;el.style.width=`${aw}%`;el.style.height=`${ah}%`;let horizontalPadding=scaledVideoWidth*.008;let verticalPadding=scaledVideoHeight*.008;if(annotation.style==="speech"&&annotation.text){const pel=annotation.__element.getElementsByTagName("span")[0];horizontalPadding*=2;verticalPadding*=2;pel.style.paddingLeft=horizontalPadding+"px";pel.style.paddingRight=horizontalPadding+"px";pel.style.paddingBottom=verticalPadding+"px";pel.style.paddingTop=verticalPadding+"px"}else if(annotation.style!=="speech"){el.style.paddingLeft=horizontalPadding+"px";el.style.paddingRight=horizontalPadding+"px";el.style.paddingBottom=verticalPadding+"px";el.style.paddingTop=verticalPadding+"px"}if(annotation.__speechBubble){const asx=this.percentToPixels(playerWidth,ax);const asy=this.percentToPixels(playerHeight,ay);const asw=this.percentToPixels(playerWidth,aw);const ash=this.percentToPixels(playerHeight,ah);let sx=widthOffsetPercent+annotation.sx*widthMultiplier;let sy=heightOffsetPercent+annotation.sy*heightMultiplier;sx=this.percentToPixels(playerWidth,sx);sy=this.percentToPixels(playerHeight,sy);this.createSvgSpeechBubble(asx,asy,asw,ash,sx,sy,null,annotation.__element,annotation.__speechBubble)}this.updateAnnotationTextSize(annotation,scaledVideoHeight);this.updateCloseSize(scaledVideoHeight)}}updateAllAnnotationSizes(){if(this.playerOptions&&this.playerOptions.getOriginalVideoWidth&&this.playerOptions.getOriginalVideoHeight){const videoWidth=this.playerOptions.getOriginalVideoWidth();const videoHeight=this.playerOptions.getOriginalVideoHeight();this.updateAnnotationDimensions(this.annotations,videoWidth,videoHeight)}else{const playerWidth=this.container.getBoundingClientRect().width;const playerHeight=this.container.getBoundingClientRect().height;this.updateAnnotationDimensions(this.annotations,playerWidth,playerHeight)}}hideAll(){for(const annotation of this.annotations){annotation.__element.setAttribute("hidden","")}}annotationClickHandler(e){let annotationElement=e.target;if(!annotationElement.matches(".__cxt-ar-annotation__")&&!annotationElement.closest(".__cxt-ar-annotation-close__")){annotationElement=annotationElement.closest(".__cxt-ar-annotation__");if(!annotationElement)return null}let annotationData=annotationElement.__annotation;if(!annotationElement||!annotationData)return;if(annotationData.actionType==="time"){const seconds=annotationData.actionSeconds;if(this.playerOptions){this.playerOptions.seekTo(seconds);const videoTime=this.playerOptions.getVideoTime();this.update(videoTime)}window.dispatchEvent(new CustomEvent("__ar_seek_to",{detail:{seconds:seconds}}))}else if(annotationData.actionType==="url"){const data={url:annotationData.actionUrl,target:annotationData.actionUrlTarget||"current"};const timeHash=this.extractTimeHash(new URL(data.url));if(timeHash&&timeHash.hasOwnProperty("seconds")){data.seconds=timeHash.seconds}window.dispatchEvent(new CustomEvent("__ar_annotation_click",{detail:data}))}}setUpdateInterval(ms){this.updateInterval=ms;this.stop();this.start()}decimalToHex(dec){let hex=dec.toString(16);hex="000000".substr(0,6-hex.length)+hex;return hex}extractTimeHash(url){if(!url)throw new Error("A URL must be provided");const hash=url.hash;if(hash&&hash.startsWith("#t=")){const timeString=url.hash.split("#t=")[1];const seconds=this.timeStringToSeconds(timeString);return{seconds:seconds}}else{return false}}timeStringToSeconds(time){let seconds=0;const h=time.split("h");const m=(h[1]||time).split("m");const s=(m[1]||time).split("s");if(h[0]&&h.length===2)seconds+=parseInt(h[0],10)*60*60;if(m[0]&&m.length===2)seconds+=parseInt(m[0],10)*60;if(s[0]&&s.length===2)seconds+=parseInt(s[0],10);return seconds}percentToPixels(a,b){return a*b/100}}function youtubeAnnotationsPlugin(options){if(!options.annotationXml)throw new Error("Annotation data must be provided");if(!options.videoContainer)throw new Error("A video container to overlay the data on must be provided");const player=this;const xml=options.annotationXml;const parser=new AnnotationParser;const annotationElements=parser.getAnnotationsFromXml(xml);const annotations=parser.parseYoutubeAnnotationList(annotationElements);const videoContainer=options.videoContainer;const playerOptions={getVideoTime(){return player.currentTime()},seekTo(seconds){player.currentTime(seconds)},getOriginalVideoWidth(){return player.videoWidth()},getOriginalVideoHeight(){return player.videoHeight()}};raiseControls();const renderer=new AnnotationRenderer(annotations,videoContainer,playerOptions,options.updateInterval);setupEventListeners(player,renderer);renderer.start()}function setupEventListeners(player,renderer){if(!player)throw new Error("A video player must be provided");player.on("playerresize",e=>{renderer.updateAllAnnotationSizes(renderer.annotations)});player.one("loadedmetadata",e=>{renderer.updateAllAnnotationSizes(renderer.annotations)});player.on("pause",e=>{renderer.stop()});player.on("play",e=>{renderer.start()});player.on("seeking",e=>{renderer.update()});player.on("seeked",e=>{renderer.update()})}function raiseControls(){const styles=document.createElement("style");styles.textContent=`\n\t.vjs-control-bar {\n\t\tz-index: 21;\n\t}\n\t`;document.body.append(styles)} +class AnnotationParser { + static get defaultAppearanceAttributes() { + return { bgColor: 16777215, bgOpacity: 0.8, fgColor: 0, textSize: 3.15 }; + } + static get attributeMap() { + return { + type: "tp", + style: "s", + x: "x", + y: "y", + width: "w", + height: "h", + sx: "sx", + sy: "sy", + timeStart: "ts", + timeEnd: "te", + text: "t", + actionType: "at", + actionUrl: "au", + actionUrlTarget: "aut", + actionSeconds: "as", + bgOpacity: "bgo", + bgColor: "bgc", + fgColor: "fgc", + textSize: "txsz", + }; + } + deserializeAnnotation(serializedAnnotation) { + const map = this.constructor.attributeMap; + const attributes = serializedAnnotation.split(","); + const annotation = {}; + for (const attribute of attributes) { + const [key, value] = attribute.split("="); + const mappedKey = this.getKeyByValue(map, key); + let finalValue = ""; + if ( + [ + "text", + "actionType", + "actionUrl", + "actionUrlTarget", + "type", + "style", + ].indexOf(mappedKey) > -1 + ) { + finalValue = decodeURIComponent(value); + } else { + finalValue = parseFloat(value, 10); + } + annotation[mappedKey] = finalValue; + } + return annotation; + } + serializeAnnotation(annotation) { + const map = this.constructor.attributeMap; + let serialized = ""; + for (const key in annotation) { + const mappedKey = map[key]; + if ( + ["text", "actionType", "actionUrl", "actionUrlTarget"].indexOf(key) > + -1 && + mappedKey && + annotation.hasOwnProperty(key) + ) { + let text = encodeURIComponent(annotation[key]); + serialized += `${mappedKey}=${text},`; + } else if ( + ["text", "actionType", "actionUrl", "actionUrlTarget"].indexOf( + "key", + ) === -1 && + mappedKey && + annotation.hasOwnProperty(key) + ) { + serialized += `${mappedKey}=${annotation[key]},`; + } + } + return serialized.substring(0, serialized.length - 1); + } + deserializeAnnotationList(serializedAnnotationString) { + const serializedAnnotations = serializedAnnotationString.split(";"); + serializedAnnotations.length = serializedAnnotations.length - 1; + const annotations = []; + for (const annotation of serializedAnnotations) { + annotations.push(this.deserializeAnnotation(annotation)); + } + return annotations; + } + serializeAnnotationList(annotations) { + let serialized = ""; + for (const annotation of annotations) { + serialized += this.serializeAnnotation(annotation) + ";"; + } + return serialized; + } + xmlToDom(xml) { + const parser = new DOMParser(); + const dom = parser.parseFromString(xml, "application/xml"); + return dom; + } + getAnnotationsFromXml(xml) { + const dom = this.xmlToDom(xml); + return dom.getElementsByTagName("annotation"); + } + parseYoutubeAnnotationList(annotationElements) { + const annotations = []; + for (const el of annotationElements) { + const parsedAnnotation = this.parseYoutubeAnnotation(el); + if (parsedAnnotation) annotations.push(parsedAnnotation); + } + return annotations; + } + parseYoutubeAnnotation(annotationElement) { + const base = annotationElement; + const attributes = this.getAttributesFromBase(base); + if (!attributes.type || attributes.type === "pause") return null; + const text = this.getTextFromBase(base); + const action = this.getActionFromBase(base); + const backgroundShape = this.getBackgroundShapeFromBase(base); + if (!backgroundShape) return null; + const timeStart = backgroundShape.timeRange.start; + const timeEnd = backgroundShape.timeRange.end; + if ( + isNaN(timeStart) || + isNaN(timeEnd) || + timeStart === null || + timeEnd === null + ) { + return null; + } + const appearance = this.getAppearanceFromBase(base); + let annotation = { + type: attributes.type, + x: backgroundShape.x, + y: backgroundShape.y, + width: backgroundShape.width, + height: backgroundShape.height, + timeStart: timeStart, + timeEnd: timeEnd, + }; + if (attributes.style) annotation.style = attributes.style; + if (text) annotation.text = text; + if (action) annotation = Object.assign(action, annotation); + if (appearance) annotation = Object.assign(appearance, annotation); + if (backgroundShape.hasOwnProperty("sx")) + annotation.sx = backgroundShape.sx; + if (backgroundShape.hasOwnProperty("sy")) + annotation.sy = backgroundShape.sy; + return annotation; + } + getBackgroundShapeFromBase(base) { + const movingRegion = base.getElementsByTagName("movingRegion")[0]; + if (!movingRegion) return null; + const regionType = movingRegion.getAttribute("type"); + const regions = movingRegion.getElementsByTagName(`${regionType}Region`); + const timeRange = this.extractRegionTime(regions); + const shape = { + type: regionType, + x: parseFloat(regions[0].getAttribute("x"), 10), + y: parseFloat(regions[0].getAttribute("y"), 10), + width: parseFloat(regions[0].getAttribute("w"), 10), + height: parseFloat(regions[0].getAttribute("h"), 10), + timeRange: timeRange, + }; + const sx = regions[0].getAttribute("sx"); + const sy = regions[0].getAttribute("sy"); + if (sx) shape.sx = parseFloat(sx, 10); + if (sy) shape.sy = parseFloat(sy, 10); + return shape; + } + getAttributesFromBase(base) { + const attributes = {}; + attributes.type = base.getAttribute("type"); + attributes.style = base.getAttribute("style"); + return attributes; + } + getTextFromBase(base) { + const textElement = base.getElementsByTagName("TEXT")[0]; + if (textElement) return textElement.textContent; + } + getActionFromBase(base) { + const actionElement = base.getElementsByTagName("action")[0]; + if (!actionElement) return null; + const typeAttr = actionElement.getAttribute("type"); + const urlElement = actionElement.getElementsByTagName("url")[0]; + if (!urlElement) return null; + const actionUrlTarget = urlElement.getAttribute("target"); + const href = urlElement.getAttribute("value"); + if (href.startsWith("https://www.youtube.com/")) { + const url = new URL(href); + const srcVid = url.searchParams.get("src_vid"); + const toVid = url.searchParams.get("v"); + return this.linkOrTimestamp(url, srcVid, toVid, actionUrlTarget); + } + } + linkOrTimestamp(url, srcVid, toVid, actionUrlTarget) { + if (srcVid && toVid && srcVid === toVid) { + let seconds = 0; + const hash = url.hash; + if (hash && hash.startsWith("#t=")) { + const timeString = url.hash.split("#t=")[1]; + seconds = this.timeStringToSeconds(timeString); + } + return { actionType: "time", actionSeconds: seconds }; + } else { + return { + actionType: "url", + actionUrl: url.href, + actionUrlTarget: actionUrlTarget, + }; + } + } + getAppearanceFromBase(base) { + const appearanceElement = base.getElementsByTagName("appearance")[0]; + const styles = this.constructor.defaultAppearanceAttributes; + if (appearanceElement) { + const bgOpacity = appearanceElement.getAttribute("bgAlpha"); + const bgColor = appearanceElement.getAttribute("bgColor"); + const fgColor = appearanceElement.getAttribute("fgColor"); + const textSize = appearanceElement.getAttribute("textSize"); + if (bgOpacity) styles.bgOpacity = parseFloat(bgOpacity, 10); + if (bgColor) styles.bgColor = parseInt(bgColor, 10); + if (fgColor) styles.fgColor = parseInt(fgColor, 10); + if (textSize) styles.textSize = parseFloat(textSize, 10); + } + return styles; + } + extractRegionTime(regions) { + let timeStart = regions[0].getAttribute("t"); + timeStart = this.hmsToSeconds(timeStart); + let timeEnd = regions[regions.length - 1].getAttribute("t"); + timeEnd = this.hmsToSeconds(timeEnd); + return { start: timeStart, end: timeEnd }; + } + hmsToSeconds(hms) { + let p = hms.split(":"); + let s = 0; + let m = 1; + while (p.length > 0) { + s += m * parseFloat(p.pop(), 10); + m *= 60; + } + return s; + } + timeStringToSeconds(time) { + let seconds = 0; + const h = time.split("h"); + const m = (h[1] || time).split("m"); + const s = (m[1] || time).split("s"); + if (h[0] && h.length === 2) seconds += parseInt(h[0], 10) * 60 * 60; + if (m[0] && m.length === 2) seconds += parseInt(m[0], 10) * 60; + if (s[0] && s.length === 2) seconds += parseInt(s[0], 10); + return seconds; + } + getKeyByValue(obj, value) { + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + if (obj[key] === value) { + return key; + } + } + } + } +} +class AnnotationRenderer { + constructor(annotations, container, playerOptions, updateInterval = 1e3) { + if (!annotations) throw new Error("Annotation objects must be provided"); + if (!container) + throw new Error("An element to contain the annotations must be provided"); + if (playerOptions && playerOptions.getVideoTime && playerOptions.seekTo) { + this.playerOptions = playerOptions; + } else { + console.info( + "AnnotationRenderer is running without a player. The update method will need to be called manually.", + ); + } + this.annotations = annotations; + this.container = container; + this.annotationsContainer = document.createElement("div"); + this.annotationsContainer.classList.add("__cxt-ar-annotations-container__"); + this.annotationsContainer.setAttribute("data-layer", "4"); + this.annotationsContainer.addEventListener("click", (e) => { + this.annotationClickHandler(e); + }); + this.container.prepend(this.annotationsContainer); + this.createAnnotationElements(); + this.updateAllAnnotationSizes(); + window.addEventListener("DOMContentLoaded", (e) => { + this.updateAllAnnotationSizes(); + }); + this.updateInterval = updateInterval; + this.updateIntervalId = null; + } + changeAnnotationData(annotations) { + this.stop(); + this.removeAnnotationElements(); + this.annotations = annotations; + this.createAnnotationElements(); + this.start(); + } + createAnnotationElements() { + for (const annotation of this.annotations) { + const el = document.createElement("div"); + el.classList.add("__cxt-ar-annotation__"); + annotation.__element = el; + el.__annotation = annotation; + const closeButton = this.createCloseElement(); + closeButton.addEventListener("click", (e) => { + el.setAttribute("hidden", ""); + el.setAttribute("data-ar-closed", ""); + if (el.__annotation.__speechBubble) { + const speechBubble = el.__annotation.__speechBubble; + speechBubble.style.display = "none"; + } + }); + el.append(closeButton); + if (annotation.text) { + const textNode = document.createElement("span"); + textNode.textContent = annotation.text; + el.append(textNode); + el.setAttribute("data-ar-has-text", ""); + } + if (annotation.style === "speech") { + const containerDimensions = this.container.getBoundingClientRect(); + const speechX = this.percentToPixels( + containerDimensions.width, + annotation.x, + ); + const speechY = this.percentToPixels( + containerDimensions.height, + annotation.y, + ); + const speechWidth = this.percentToPixels( + containerDimensions.width, + annotation.width, + ); + const speechHeight = this.percentToPixels( + containerDimensions.height, + annotation.height, + ); + const speechPointX = this.percentToPixels( + containerDimensions.width, + annotation.sx, + ); + const speechPointY = this.percentToPixels( + containerDimensions.height, + annotation.sy, + ); + const bubbleColor = this.getFinalAnnotationColor(annotation, false); + const bubble = this.createSvgSpeechBubble( + speechX, + speechY, + speechWidth, + speechHeight, + speechPointX, + speechPointY, + bubbleColor, + annotation.__element, + ); + bubble.style.display = "none"; + bubble.style.overflow = "visible"; + el.style.pointerEvents = "none"; + bubble.__annotationEl = el; + annotation.__speechBubble = bubble; + const path = bubble.getElementsByTagName("path")[0]; + path.addEventListener("mouseover", () => { + closeButton.style.display = "block"; + closeButton.style.cursor = "pointer"; + path.setAttribute( + "fill", + this.getFinalAnnotationColor(annotation, true), + ); + }); + path.addEventListener("mouseout", (e) => { + if ( + !e.relatedTarget.classList.contains("__cxt-ar-annotation-close__") + ) { + closeButton.style.display = "none"; + closeButton.style.cursor = "default"; + path.setAttribute( + "fill", + this.getFinalAnnotationColor(annotation, false), + ); + } + }); + closeButton.addEventListener("mouseleave", () => { + closeButton.style.display = "none"; + path.style.cursor = "default"; + closeButton.style.cursor = "default"; + path.setAttribute( + "fill", + this.getFinalAnnotationColor(annotation, false), + ); + }); + el.prepend(bubble); + } else if (annotation.type === "highlight") { + el.style.backgroundColor = ""; + el.style.border = `2.5px solid ${this.getFinalAnnotationColor(annotation, false)}`; + if (annotation.actionType === "url") el.style.cursor = "pointer"; + } else if (annotation.style !== "title") { + el.style.backgroundColor = this.getFinalAnnotationColor(annotation); + el.addEventListener("mouseenter", () => { + el.style.backgroundColor = this.getFinalAnnotationColor( + annotation, + true, + ); + }); + el.addEventListener("mouseleave", () => { + el.style.backgroundColor = this.getFinalAnnotationColor( + annotation, + false, + ); + }); + if (annotation.actionType === "url") el.style.cursor = "pointer"; + } + el.style.color = `#${this.decimalToHex(annotation.fgColor)}`; + el.setAttribute("data-ar-type", annotation.type); + el.setAttribute("hidden", ""); + this.annotationsContainer.append(el); + } + } + createCloseElement() { + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttribute("viewBox", "0 0 100 100"); + svg.classList.add("__cxt-ar-annotation-close__"); + const path = document.createElementNS(svg.namespaceURI, "path"); + path.setAttribute("d", "M25 25 L 75 75 M 75 25 L 25 75"); + path.setAttribute("stroke", "#bbb"); + path.setAttribute("stroke-width", 10); + path.setAttribute("x", 5); + path.setAttribute("y", 5); + const circle = document.createElementNS(svg.namespaceURI, "circle"); + circle.setAttribute("cx", 50); + circle.setAttribute("cy", 50); + circle.setAttribute("r", 50); + svg.append(circle, path); + return svg; + } + createSvgSpeechBubble( + x, + y, + width, + height, + pointX, + pointY, + color = "white", + element, + svg, + ) { + const horizontalBaseStartMultiplier = 0.17379070765180116; + const horizontalBaseEndMultiplier = 0.14896346370154384; + const verticalBaseStartMultiplier = 0.12; + const verticalBaseEndMultiplier = 0.3; + let path; + if (!svg) { + svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.classList.add("__cxt-ar-annotation-speech-bubble__"); + path = document.createElementNS("http://www.w3.org/2000/svg", "path"); + path.setAttribute("fill", color); + svg.append(path); + } else { + path = svg.children[0]; + } + svg.style.position = "absolute"; + svg.setAttribute("width", "100%"); + svg.setAttribute("height", "100%"); + svg.style.left = "0"; + svg.style.top = "0"; + let positionStart; + let baseStartX = 0; + let baseStartY = 0; + let baseEndX = 0; + let baseEndY = 0; + let pointFinalX = pointX; + let pointFinalY = pointY; + let commentRectPath; + const pospad = 20; + let textWidth = 0; + let textHeight = 0; + let textX = 0; + let textY = 0; + let textElement; + let closeElement; + if (element) { + textElement = element.getElementsByTagName("span")[0]; + closeElement = element.getElementsByClassName( + "__cxt-ar-annotation-close__", + )[0]; + } + if (pointX > x + width - width / 2 && pointY > y + height) { + positionStart = "br"; + baseStartX = width - width * horizontalBaseStartMultiplier * 2; + baseEndX = baseStartX + width * horizontalBaseEndMultiplier; + baseStartY = height; + baseEndY = height; + pointFinalX = pointX - x; + pointFinalY = pointY - y; + element.style.height = pointY - y; + commentRectPath = `L${width} ${height} L${width} 0 L0 0 L0 ${baseStartY} L${baseStartX} ${baseStartY}`; + if (textElement) { + textWidth = width; + textHeight = height; + textX = 0; + textY = 0; + } + } else if (pointX < x + width - width / 2 && pointY > y + height) { + positionStart = "bl"; + baseStartX = width * horizontalBaseStartMultiplier; + baseEndX = baseStartX + width * horizontalBaseEndMultiplier; + baseStartY = height; + baseEndY = height; + pointFinalX = pointX - x; + pointFinalY = pointY - y; + element.style.height = `${pointY - y}px`; + commentRectPath = `L${width} ${height} L${width} 0 L0 0 L0 ${baseStartY} L${baseStartX} ${baseStartY}`; + if (textElement) { + textWidth = width; + textHeight = height; + textX = 0; + textY = 0; + } + } else if (pointX > x + width - width / 2 && pointY < y - pospad) { + positionStart = "tr"; + baseStartX = width - width * horizontalBaseStartMultiplier * 2; + baseEndX = baseStartX + width * horizontalBaseEndMultiplier; + const yOffset = y - pointY; + baseStartY = yOffset; + baseEndY = yOffset; + element.style.top = y - yOffset + "px"; + element.style.height = height + yOffset + "px"; + pointFinalX = pointX - x; + pointFinalY = 0; + commentRectPath = `L${width} ${yOffset} L${width} ${height + yOffset} L0 ${height + yOffset} L0 ${yOffset} L${baseStartX} ${baseStartY}`; + if (textElement) { + textWidth = width; + textHeight = height; + textX = 0; + textY = yOffset; + } + } else if (pointX < x + width - width / 2 && pointY < y) { + positionStart = "tl"; + baseStartX = width * horizontalBaseStartMultiplier; + baseEndX = baseStartX + width * horizontalBaseEndMultiplier; + const yOffset = y - pointY; + baseStartY = yOffset; + baseEndY = yOffset; + element.style.top = y - yOffset + "px"; + element.style.height = height + yOffset + "px"; + pointFinalX = pointX - x; + pointFinalY = 0; + commentRectPath = `L${width} ${yOffset} L${width} ${height + yOffset} L0 ${height + yOffset} L0 ${yOffset} L${baseStartX} ${baseStartY}`; + if (textElement) { + textWidth = width; + textHeight = height; + textX = 0; + textY = yOffset; + } + } else if ( + pointX > x + width && + pointY > y - pospad && + pointY < y + height - pospad + ) { + positionStart = "r"; + const xOffset = pointX - (x + width); + baseStartX = width; + baseEndX = width; + element.style.width = width + xOffset + "px"; + baseStartY = height * verticalBaseStartMultiplier; + baseEndY = baseStartY + height * verticalBaseEndMultiplier; + pointFinalX = width + xOffset; + pointFinalY = pointY - y; + commentRectPath = `L${baseStartX} ${height} L0 ${height} L0 0 L${baseStartX} 0 L${baseStartX} ${baseStartY}`; + if (textElement) { + textWidth = width; + textHeight = height; + textX = 0; + textY = 0; + } + } else if (pointX < x && pointY > y && pointY < y + height) { + positionStart = "l"; + const xOffset = x - pointX; + baseStartX = xOffset; + baseEndX = xOffset; + element.style.left = x - xOffset + "px"; + element.style.width = width + xOffset + "px"; + baseStartY = height * verticalBaseStartMultiplier; + baseEndY = baseStartY + height * verticalBaseEndMultiplier; + pointFinalX = 0; + pointFinalY = pointY - y; + commentRectPath = `L${baseStartX} ${height} L${width + baseStartX} ${height} L${width + baseStartX} 0 L${baseStartX} 0 L${baseStartX} ${baseStartY}`; + if (textElement) { + textWidth = width; + textHeight = height; + textX = xOffset; + textY = 0; + } + } else { + return svg; + } + if (textElement) { + textElement.style.left = textX + "px"; + textElement.style.top = textY + "px"; + textElement.style.width = textWidth + "px"; + textElement.style.height = textHeight + "px"; + } + if (closeElement) { + const closeSize = parseFloat( + this.annotationsContainer.style.getPropertyValue( + "--annotation-close-size", + ), + 10, + ); + if (closeSize) { + closeElement.style.left = textX + textWidth + closeSize / -1.8 + "px"; + closeElement.style.top = textY + closeSize / -1.8 + "px"; + } + } + const pathData = `M${baseStartX} ${baseStartY} L${pointFinalX} ${pointFinalY} L${baseEndX} ${baseEndY} ${commentRectPath}`; + path.setAttribute("d", pathData); + return svg; + } + getFinalAnnotationColor(annotation, hover = false) { + const alphaHex = hover + ? (230).toString(16) + : Math.floor(annotation.bgOpacity * 255).toString(16); + if (!isNaN(annotation.bgColor)) { + const bgColorHex = this.decimalToHex(annotation.bgColor); + const backgroundColor = `#${bgColorHex}${alphaHex}`; + return backgroundColor; + } + } + removeAnnotationElements() { + for (const annotation of this.annotations) { + annotation.__element.remove(); + } + } + update(videoTime) { + for (const annotation of this.annotations) { + const el = annotation.__element; + if (el.hasAttribute("data-ar-closed")) continue; + const start = annotation.timeStart; + const end = annotation.timeEnd; + if (el.hasAttribute("hidden") && videoTime >= start && videoTime < end) { + el.removeAttribute("hidden"); + if (annotation.style === "speech" && annotation.__speechBubble) { + annotation.__speechBubble.style.display = "block"; + } + } else if ( + !el.hasAttribute("hidden") && + (videoTime < start || videoTime > end) + ) { + el.setAttribute("hidden", ""); + if (annotation.style === "speech" && annotation.__speechBubble) { + annotation.__speechBubble.style.display = "none"; + } + } + } + } + start() { + if (!this.playerOptions) + throw new Error("playerOptions must be provided to use the start method"); + const videoTime = this.playerOptions.getVideoTime(); + if (!this.updateIntervalId) { + this.update(videoTime); + this.updateIntervalId = setInterval(() => { + const videoTime = this.playerOptions.getVideoTime(); + this.update(videoTime); + window.dispatchEvent(new CustomEvent("__ar_renderer_start")); + }, this.updateInterval); + } + } + stop() { + if (!this.playerOptions) + throw new Error("playerOptions must be provided to use the stop method"); + const videoTime = this.playerOptions.getVideoTime(); + if (this.updateIntervalId) { + this.update(videoTime); + clearInterval(this.updateIntervalId); + this.updateIntervalId = null; + window.dispatchEvent(new CustomEvent("__ar_renderer_stop")); + } + } + updateAnnotationTextSize(annotation, containerHeight) { + if (annotation.textSize) { + const textSize = (annotation.textSize / 100) * containerHeight; + annotation.__element.style.fontSize = `${textSize}px`; + } + } + updateTextSize() { + const containerHeight = this.container.getBoundingClientRect().height; + for (const annotation of this.annotations) { + this.updateAnnotationTextSize(annotation, containerHeight); + } + } + updateCloseSize(containerHeight) { + if (!containerHeight) + containerHeight = this.container.getBoundingClientRect().height; + const multiplier = 0.0423; + this.annotationsContainer.style.setProperty( + "--annotation-close-size", + `${containerHeight * multiplier}px`, + ); + } + updateAnnotationDimensions(annotations, videoWidth, videoHeight) { + const playerWidth = this.container.getBoundingClientRect().width; + const playerHeight = this.container.getBoundingClientRect().height; + const widthDivider = playerWidth / videoWidth; + const heightDivider = playerHeight / videoHeight; + let scaledVideoWidth = playerWidth; + let scaledVideoHeight = playerHeight; + if (widthDivider % 1 !== 0 || heightDivider % 1 !== 0) { + if (widthDivider > heightDivider) { + scaledVideoWidth = (playerHeight / videoHeight) * videoWidth; + scaledVideoHeight = playerHeight; + } else if (heightDivider > widthDivider) { + scaledVideoWidth = playerWidth; + scaledVideoHeight = (playerWidth / videoWidth) * videoHeight; + } + } + const verticalBlackBarWidth = (playerWidth - scaledVideoWidth) / 2; + const horizontalBlackBarHeight = (playerHeight - scaledVideoHeight) / 2; + const widthOffsetPercent = (verticalBlackBarWidth / playerWidth) * 100; + const heightOffsetPercent = (horizontalBlackBarHeight / playerHeight) * 100; + const widthMultiplier = scaledVideoWidth / playerWidth; + const heightMultiplier = scaledVideoHeight / playerHeight; + for (const annotation of annotations) { + const el = annotation.__element; + let ax = widthOffsetPercent + annotation.x * widthMultiplier; + let ay = heightOffsetPercent + annotation.y * heightMultiplier; + let aw = annotation.width * widthMultiplier; + let ah = annotation.height * heightMultiplier; + el.style.left = `${ax}%`; + el.style.top = `${ay}%`; + el.style.width = `${aw}%`; + el.style.height = `${ah}%`; + let horizontalPadding = scaledVideoWidth * 0.008; + let verticalPadding = scaledVideoHeight * 0.008; + if (annotation.style === "speech" && annotation.text) { + const pel = annotation.__element.getElementsByTagName("span")[0]; + horizontalPadding *= 2; + verticalPadding *= 2; + pel.style.paddingLeft = horizontalPadding + "px"; + pel.style.paddingRight = horizontalPadding + "px"; + pel.style.paddingBottom = verticalPadding + "px"; + pel.style.paddingTop = verticalPadding + "px"; + } else if (annotation.style !== "speech") { + el.style.paddingLeft = horizontalPadding + "px"; + el.style.paddingRight = horizontalPadding + "px"; + el.style.paddingBottom = verticalPadding + "px"; + el.style.paddingTop = verticalPadding + "px"; + } + if (annotation.__speechBubble) { + const asx = this.percentToPixels(playerWidth, ax); + const asy = this.percentToPixels(playerHeight, ay); + const asw = this.percentToPixels(playerWidth, aw); + const ash = this.percentToPixels(playerHeight, ah); + let sx = widthOffsetPercent + annotation.sx * widthMultiplier; + let sy = heightOffsetPercent + annotation.sy * heightMultiplier; + sx = this.percentToPixels(playerWidth, sx); + sy = this.percentToPixels(playerHeight, sy); + this.createSvgSpeechBubble( + asx, + asy, + asw, + ash, + sx, + sy, + null, + annotation.__element, + annotation.__speechBubble, + ); + } + this.updateAnnotationTextSize(annotation, scaledVideoHeight); + this.updateCloseSize(scaledVideoHeight); + } + } + updateAllAnnotationSizes() { + if ( + this.playerOptions && + this.playerOptions.getOriginalVideoWidth && + this.playerOptions.getOriginalVideoHeight + ) { + const videoWidth = this.playerOptions.getOriginalVideoWidth(); + const videoHeight = this.playerOptions.getOriginalVideoHeight(); + this.updateAnnotationDimensions( + this.annotations, + videoWidth, + videoHeight, + ); + } else { + const playerWidth = this.container.getBoundingClientRect().width; + const playerHeight = this.container.getBoundingClientRect().height; + this.updateAnnotationDimensions( + this.annotations, + playerWidth, + playerHeight, + ); + } + } + hideAll() { + for (const annotation of this.annotations) { + annotation.__element.setAttribute("hidden", ""); + } + } + annotationClickHandler(e) { + let annotationElement = e.target; + if ( + !annotationElement.matches(".__cxt-ar-annotation__") && + !annotationElement.closest(".__cxt-ar-annotation-close__") + ) { + annotationElement = annotationElement.closest(".__cxt-ar-annotation__"); + if (!annotationElement) return null; + } + let annotationData = annotationElement.__annotation; + if (!annotationElement || !annotationData) return; + if (annotationData.actionType === "time") { + const seconds = annotationData.actionSeconds; + if (this.playerOptions) { + this.playerOptions.seekTo(seconds); + const videoTime = this.playerOptions.getVideoTime(); + this.update(videoTime); + } + window.dispatchEvent( + new CustomEvent("__ar_seek_to", { detail: { seconds: seconds } }), + ); + } else if (annotationData.actionType === "url") { + const data = { + url: annotationData.actionUrl, + target: annotationData.actionUrlTarget || "current", + }; + const timeHash = this.extractTimeHash(new URL(data.url)); + if (timeHash && timeHash.hasOwnProperty("seconds")) { + data.seconds = timeHash.seconds; + } + window.dispatchEvent( + new CustomEvent("__ar_annotation_click", { detail: data }), + ); + } + } + setUpdateInterval(ms) { + this.updateInterval = ms; + this.stop(); + this.start(); + } + decimalToHex(dec) { + let hex = dec.toString(16); + hex = "000000".substr(0, 6 - hex.length) + hex; + return hex; + } + extractTimeHash(url) { + if (!url) throw new Error("A URL must be provided"); + const hash = url.hash; + if (hash && hash.startsWith("#t=")) { + const timeString = url.hash.split("#t=")[1]; + const seconds = this.timeStringToSeconds(timeString); + return { seconds: seconds }; + } else { + return false; + } + } + timeStringToSeconds(time) { + let seconds = 0; + const h = time.split("h"); + const m = (h[1] || time).split("m"); + const s = (m[1] || time).split("s"); + if (h[0] && h.length === 2) seconds += parseInt(h[0], 10) * 60 * 60; + if (m[0] && m.length === 2) seconds += parseInt(m[0], 10) * 60; + if (s[0] && s.length === 2) seconds += parseInt(s[0], 10); + return seconds; + } + percentToPixels(a, b) { + return (a * b) / 100; + } +} +function youtubeAnnotationsPlugin(options) { + if (!options.annotationXml) + throw new Error("Annotation data must be provided"); + if (!options.videoContainer) + throw new Error( + "A video container to overlay the data on must be provided", + ); + const player = this; + const xml = options.annotationXml; + const parser = new AnnotationParser(); + const annotationElements = parser.getAnnotationsFromXml(xml); + const annotations = parser.parseYoutubeAnnotationList(annotationElements); + const videoContainer = options.videoContainer; + const playerOptions = { + getVideoTime() { + return player.currentTime(); + }, + seekTo(seconds) { + player.currentTime(seconds); + }, + getOriginalVideoWidth() { + return player.videoWidth(); + }, + getOriginalVideoHeight() { + return player.videoHeight(); + }, + }; + raiseControls(); + const renderer = new AnnotationRenderer( + annotations, + videoContainer, + playerOptions, + options.updateInterval, + ); + setupEventListeners(player, renderer); + renderer.start(); +} +function setupEventListeners(player, renderer) { + if (!player) throw new Error("A video player must be provided"); + player.on("playerresize", (e) => { + renderer.updateAllAnnotationSizes(renderer.annotations); + }); + player.one("loadedmetadata", (e) => { + renderer.updateAllAnnotationSizes(renderer.annotations); + }); + player.on("pause", (e) => { + renderer.stop(); + }); + player.on("play", (e) => { + renderer.start(); + }); + player.on("seeking", (e) => { + renderer.update(); + }); + player.on("seeked", (e) => { + renderer.update(); + }); +} +function raiseControls() { + const styles = document.createElement("style"); + styles.textContent = `\n\t.vjs-control-bar {\n\t\tz-index: 21;\n\t}\n\t`; + document.body.append(styles); +} diff --git a/assets/js/watch.js b/assets/js/watch.js index 75ec99a3..d8fc61f3 100644 --- a/assets/js/watch.js +++ b/assets/js/watch.js @@ -1,135 +1,159 @@ -'use strict'; +"use strict"; function toggle_parent(target) { - var body = target.parentNode.parentNode.children[1]; - if (body.style.display === 'none') { - target.textContent = '[ − ]'; - body.style.display = ''; - } else { - target.textContent = '[ + ]'; - body.style.display = 'none'; - } + var body = target.parentNode.parentNode.children[1]; + if (body.style.display === "none") { + target.textContent = "[ − ]"; + body.style.display = ""; + } else { + target.textContent = "[ + ]"; + body.style.display = "none"; + } } function swap_comments(event) { - var source = event.target.getAttribute('data-comments'); + var source = event.target.getAttribute("data-comments"); - if (source === 'youtube') { - get_youtube_comments(); - } else if (source === 'reddit') { - get_reddit_comments(); - } + if (source === "youtube") { + get_youtube_comments(); + } else if (source === "reddit") { + get_reddit_comments(); + } } -var continue_button = document.getElementById('continue'); +var continue_button = document.getElementById("continue"); if (continue_button) { - continue_button.onclick = continue_autoplay; + continue_button.onclick = continue_autoplay; } function next_video() { - var url = new URL('https://example.com/watch?v=' + video_data.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'); + 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); + location.assign(url.pathname + url.search); } function continue_autoplay(event) { - if (event.target.checked) { - player.on('ended', next_video); - } else { - player.off('ended'); - } + if (event.target.checked) { + player.on("ended", next_video); + } else { + player.off("ended"); + } } function get_playlist(plid) { - var playlist = document.getElementById('playlist'); + var playlist = document.getElementById("playlist"); - playlist.innerHTML = spinnerHTMLwithHR; + playlist.innerHTML = spinnerHTMLwithHR; - var plid_url; - if (plid.startsWith('RD')) { - plid_url = '/api/v1/mixes/' + plid + - '?continuation=' + video_data.id + - '&format=html&hl=' + video_data.preferences.locale; - } else { - plid_url = '/api/v1/playlists/' + plid + - '?index=' + video_data.index + - '&continuation=' + video_data.id + - '&format=html&hl=' + video_data.preferences.locale; - } + var plid_url; + if (plid.startsWith("RD")) { + plid_url = + "/api/v1/mixes/" + + plid + + "?continuation=" + + video_data.id + + "&format=html&hl=" + + video_data.preferences.locale; + } else { + plid_url = + "/api/v1/playlists/" + + plid + + "?index=" + + video_data.index + + "&continuation=" + + video_data.id + + "&format=html&hl=" + + video_data.preferences.locale; + } - if (video_data.params.listen) { - plid_url += '&listen=1' - } + if (video_data.params.listen) { + plid_url += "&listen=1"; + } - helpers.xhr('GET', plid_url, {retries: 5, entity_name: 'playlist'}, { - on200: function (response) { - if (response === null) return; + helpers.xhr( + "GET", + plid_url, + { retries: 5, entity_name: "playlist" }, + { + on200: function (response) { + if (response === null) return; - playlist.innerHTML = response.playlistHtml; + playlist.innerHTML = response.playlistHtml; - if (!response.nextVideo) return; + if (!response.nextVideo) return; - var nextVideo = document.getElementById(response.nextVideo); - nextVideo.parentNode.parentNode.scrollTop = nextVideo.offsetTop; + var nextVideo = document.getElementById(response.nextVideo); + nextVideo.parentNode.parentNode.scrollTop = nextVideo.offsetTop; - player.on('ended', function () { - var url = new URL('https://example.com/watch?v=' + response.nextVideo); + player.on("ended", function () { + var url = new URL( + "https://example.com/watch?v=" + response.nextVideo, + ); - url.searchParams.set('list', plid); - if (!plid.startsWith('RD')) - url.searchParams.set('index', response.index); - 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); + if (!plid.startsWith("RD")) + url.searchParams.set("index", response.index); + 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); - location.assign(url.pathname + url.search); - }); - }, - onNon200: function (xhr) { - playlist.innerHTML = ''; - document.getElementById('continue').style.display = ''; - }, - onError: function (xhr) { - playlist.innerHTML = spinnerHTMLwithHR; - }, - onTimeout: function (xhr) { - playlist.innerHTML = spinnerHTMLwithHR; - } - }); + location.assign(url.pathname + url.search); + }); + }, + onNon200: function (xhr) { + playlist.innerHTML = ""; + document.getElementById("continue").style.display = ""; + }, + onError: function (xhr) { + playlist.innerHTML = spinnerHTMLwithHR; + }, + onTimeout: function (xhr) { + playlist.innerHTML = spinnerHTMLwithHR; + }, + }, + ); } function get_reddit_comments() { - var comments = document.getElementById('comments'); + var comments = document.getElementById("comments"); - var fallback = comments.innerHTML; - comments.innerHTML = spinnerHTML; + var fallback = comments.innerHTML; + comments.innerHTML = spinnerHTML; - var url = '/api/v1/comments/' + video_data.id + - '?source=reddit&format=html' + - '&hl=' + video_data.preferences.locale; + var url = + "/api/v1/comments/" + + video_data.id + + "?source=reddit&format=html" + + "&hl=" + + video_data.preferences.locale; - var onNon200 = function (xhr) { comments.innerHTML = fallback; }; - if (video_data.params.comments[1] === 'youtube') - onNon200 = function (xhr) {}; + var onNon200 = function (xhr) { + comments.innerHTML = fallback; + }; + if (video_data.params.comments[1] === "youtube") onNon200 = function (xhr) {}; - helpers.xhr('GET', url, {retries: 5, entity_name: ''}, { - on200: function (response) { - comments.innerHTML = ' \ + helpers.xhr( + "GET", + url, + { retries: 5, entity_name: "" }, + { + on200: function (response) { + comments.innerHTML = ' \
\

\ [ − ] \ @@ -148,52 +172,52 @@ function get_reddit_comments() {

\
{contentHtml}
\
'.supplant({ - title: response.title, - youtubeCommentsText: video_data.youtube_comments_text, - redditPermalinkText: video_data.reddit_permalink_text, - permalink: response.permalink, - contentHtml: response.contentHtml - }); + title: response.title, + youtubeCommentsText: video_data.youtube_comments_text, + redditPermalinkText: video_data.reddit_permalink_text, + permalink: response.permalink, + contentHtml: response.contentHtml, + }); - comments.children[0].children[0].children[0].onclick = toggle_comments; - comments.children[0].children[1].children[0].onclick = swap_comments; - }, - onNon200: onNon200, // declared above - }); + comments.children[0].children[0].children[0].onclick = toggle_comments; + comments.children[0].children[1].children[0].onclick = swap_comments; + }, + onNon200: onNon200, // declared above + }, + ); } if (video_data.play_next) { - player.on('ended', function () { - var url = new URL('https://example.com/watch?v=' + video_data.next_video); + 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'); + 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); - }); + location.assign(url.pathname + url.search); + }); } -addEventListener('load', function (e) { - if (video_data.plid) - get_playlist(video_data.plid); +addEventListener("load", function (e) { + 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 { - var comments = document.getElementById('comments'); - comments.innerHTML = ''; - } + 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 { + var comments = document.getElementById("comments"); + comments.innerHTML = ""; + } }); diff --git a/assets/js/watched_indicator.js b/assets/js/watched_indicator.js index e971cd80..d598e35e 100644 --- a/assets/js/watched_indicator.js +++ b/assets/js/watched_indicator.js @@ -1,24 +1,24 @@ -'use strict'; -var save_player_pos_key = 'save_player_pos'; +"use strict"; +var save_player_pos_key = "save_player_pos"; function get_all_video_times() { - return helpers.storage.get(save_player_pos_key) || {}; + return helpers.storage.get(save_player_pos_key) || {}; } -document.querySelectorAll('.watched-indicator').forEach(function (indicator) { - var watched_part = get_all_video_times()[indicator.dataset.id]; - var total = parseInt(indicator.dataset.length, 10); - if (watched_part === undefined) { - watched_part = total; - } - var percentage = Math.round((watched_part / total) * 100); +document.querySelectorAll(".watched-indicator").forEach(function (indicator) { + var watched_part = get_all_video_times()[indicator.dataset.id]; + var total = parseInt(indicator.dataset.length, 10); + if (watched_part === undefined) { + watched_part = total; + } + var percentage = Math.round((watched_part / total) * 100); - if (percentage < 5) { - percentage = 5; - } - if (percentage > 90) { - percentage = 100; - } + if (percentage < 5) { + percentage = 5; + } + if (percentage > 90) { + percentage = 100; + } - indicator.style.width = percentage + '%'; + indicator.style.width = percentage + "%"; }); diff --git a/assets/js/watched_widget.js b/assets/js/watched_widget.js index 06af62cc..4ce4ba07 100644 --- a/assets/js/watched_widget.js +++ b/assets/js/watched_widget.js @@ -1,34 +1,50 @@ -'use strict'; -var watched_data = JSON.parse(document.getElementById('watched_data').textContent); -var payload = 'csrf_token=' + watched_data.csrf_token; +"use strict"; +var watched_data = JSON.parse( + document.getElementById("watched_data").textContent, +); +var payload = "csrf_token=" + watched_data.csrf_token; function mark_watched(target) { - var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode; - tile.style.display = 'none'; + var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode; + tile.style.display = "none"; - var url = '/watch_ajax?action=mark_watched&redirect=false' + - '&id=' + target.getAttribute('data-id'); + var url = + "/watch_ajax?action=mark_watched&redirect=false" + + "&id=" + + target.getAttribute("data-id"); - helpers.xhr('POST', url, {payload: payload}, { - onNon200: function (xhr) { - tile.style.display = ''; - } - }); + helpers.xhr( + "POST", + url, + { payload: payload }, + { + onNon200: function (xhr) { + tile.style.display = ""; + }, + }, + ); } function mark_unwatched(target) { - var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode; - tile.style.display = 'none'; - var count = document.getElementById('count'); - count.textContent--; + var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode; + tile.style.display = "none"; + var count = document.getElementById("count"); + count.textContent--; - var url = '/watch_ajax?action=mark_unwatched&redirect=false' + - '&id=' + target.getAttribute('data-id'); + var url = + "/watch_ajax?action=mark_unwatched&redirect=false" + + "&id=" + + target.getAttribute("data-id"); - helpers.xhr('POST', url, {payload: payload}, { - onNon200: function (xhr) { - count.textContent++; - tile.style.display = ''; - } - }); + helpers.xhr( + "POST", + url, + { payload: payload }, + { + onNon200: function (xhr) { + count.textContent++; + tile.style.display = ""; + }, + }, + ); } diff --git a/src/invidious/frontend/pagination.cr b/src/invidious/frontend/pagination.cr index e75d38a5..cf78c4d0 100644 --- a/src/invidious/frontend/pagination.cr +++ b/src/invidious/frontend/pagination.cr @@ -89,13 +89,6 @@ module Invidious::Frontend::Pagination end def nav_ctoken(locale : String?, *, base_url : String | URI, ctoken : String?, first_page : Bool, params : URI::Params) - return String.build do |str| - if !ctoken.nil? - str << %(\n) end end end