diff --git a/assets/js/_helpers.js b/assets/js/_helpers.js index 448e95d1..0bd99a8c 100644 --- a/assets/js/_helpers.js +++ b/assets/js/_helpers.js @@ -39,6 +39,7 @@ if (!window.hasOwnProperty('HTMLDetailsElement') && !window.hasOwnProperty('mock } // Monstrous global variable for handy code +// Includes: clamp, xhr, storage.{get,set,remove} window.helpers = window.helpers || { /** * https://en.wikipedia.org/wiki/Clamping_(graphics) @@ -164,19 +165,20 @@ window.helpers = window.helpers || { }, options.retry_timeout); }; + // Pack retry() call into error handlers callbacks._onError = callbacks.onError; callbacks.onError = function (xhr) { if (callbacks._onError) - callbacks._onError(); + callbacks._onError(xhr); retry(); }; - callbacks._onTimeout = callbacks.onTimeout; callbacks.onTimeout = function (xhr) { if (callbacks._onTimeout) - callbacks._onTimeout(); + callbacks._onTimeout(xhr); retry(); - }; + }; + helpers._xhrRetry(method, url, options, callbacks); }, @@ -198,13 +200,22 @@ window.helpers = window.helpers || { if (localStorageIsUsable) { return { - get: function (key) { if (localStorage[key]) return JSON.parse(decodeURIComponent(localStorage[key])); }, + get: function (key) { + if (!localStorage[key]) return; + try { + return JSON.parse(decodeURIComponent(localStorage[key])); + } catch(e) { + // Erase non parsable value + helpers.storage.remove(key); + } + }, set: function (key, value) { localStorage[key] = encodeURIComponent(JSON.stringify(value)); }, remove: function (key) { localStorage.removeItem(key); } }; } - console.info('Storage: localStorage is disabled or unaccessible trying cookies'); + // 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 + '='; @@ -213,7 +224,12 @@ window.helpers = window.helpers || { if (matchedCookie) { const cookieBody = matchedCookie.replace(cookiePrefix, ''); if (cookieBody.length === 0) return; - return JSON.parse(decodeURIComponent(cookieBody)); + try { + return JSON.parse(decodeURIComponent(cookieBody)); + } catch(e) { + // Erase non parsable value + helpers.storage.remove(key); + } } }, set: function (key, value) { diff --git a/assets/js/community.js b/assets/js/community.js index 33e2e3ed..608dc971 100644 --- a/assets/js/community.js +++ b/assets/js/community.js @@ -62,7 +62,7 @@ function get_youtube_replies(target, load_more) { 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.innerText = community_data.hide_replies_text; + a.textContent = community_data.hide_replies_text; var div = document.createElement('div'); div.innerHTML = response.contentHtml; diff --git a/assets/js/handlers.js b/assets/js/handlers.js index 438832b1..29810e72 100644 --- a/assets/js/handlers.js +++ b/assets/js/handlers.js @@ -78,7 +78,7 @@ document.querySelectorAll('[data-onrange="update_volume_value"]').forEach(function (el) { function update_volume_value() { - document.getElementById('volume-value').innerText = el.value; + document.getElementById('volume-value').textContent = el.value; } el.oninput = update_volume_value; el.onchange = update_volume_value; @@ -89,7 +89,7 @@ var row = target.parentNode.parentNode.parentNode.parentNode.parentNode; row.style.display = 'none'; var count = document.getElementById('count'); - count.innerText = parseInt(count.innerText) - 1; + count.textContent--; var url = '/token_ajax?action_revoke_token=1&redirect=false' + '&referer=' + encodeURIComponent(location.href) + @@ -99,7 +99,7 @@ helpers.xhr('POST', url, {payload: payload}, { onNon200: function (xhr) { - count.innerText = parseInt(count.innerText) + 1; + count.textContent++; row.style.display = ''; } }); @@ -109,7 +109,7 @@ var row = target.parentNode.parentNode.parentNode.parentNode.parentNode; row.style.display = 'none'; var count = document.getElementById('count'); - count.innerText = parseInt(count.innerText) - 1; + count.textContent--; var url = '/subscription_ajax?action_remove_subscriptions=1&redirect=false' + '&referer=' + encodeURIComponent(location.href) + @@ -119,7 +119,7 @@ helpers.xhr('POST', url, {payload: payload}, { onNon200: function (xhr) { - count.innerText = parseInt(count.innerText) + 1; + count.textContent++; row.style.display = ''; } }); diff --git a/assets/js/notifications.js b/assets/js/notifications.js index 568f5ff6..7a30375d 100644 --- a/assets/js/notifications.js +++ b/assets/js/notifications.js @@ -1,8 +1,13 @@ 'use strict'; var notification_data = JSON.parse(document.getElementById('notification_data').textContent); +/** Boolean meaning 'some tab have stream' */ +const STORAGE_KEY_STREAM = 'stream'; +/** Number of notifications. May be increased or reset */ +const STORAGE_KEY_NOTIF_COUNT = 'notification_count'; + var notifications, delivered; -var notifications_substitution = { close: function () { } }; +var notifications_mock = { close: function () { } }; function get_subscriptions() { helpers.xhr('GET', '/api/v1/auth/subscriptions?fields=authorId', { @@ -32,92 +37,96 @@ function create_notification_stream(subscriptions) { var notification = JSON.parse(event.data); console.info('Got notification:', notification); - if (start_time < notification.published && !delivered.includes(notification.videoId)) { - if (Notification.permission === 'granted') { - var system_notification = - new Notification((notification.liveNow ? notification_data.live_now_text : notification_data.upload_text).replace('`x`', notification.author), { - body: notification.title, - icon: '/ggpht' + new URL(notification.authorThumbnails[2].url).pathname, - img: '/ggpht' + new URL(notification.authorThumbnails[4].url).pathname, - tag: notification.videoId - }); - - system_notification.onclick = function (event) { - open('/watch?v=' + event.currentTarget.tag, '_blank'); - }; - } - - delivered.push(notification.videoId); - helpers.storage.set('notification_count', (helpers.storage.get('notification_count') || 0) + 1); - var notification_ticker = document.getElementById('notification_ticker'); - - if (parseInt(helpers.storage.get('notification_count')) > 0) { - notification_ticker.innerHTML = - '' + helpers.storage.get('notification_count') + ' '; - } else { - notification_ticker.innerHTML = - ''; - } + // Ignore not actual and delivered notifications + if (start_time > notification.published || delivered.includes(notification.videoId)) return; + + delivered.push(notification.videoId); + + let notification_count = helpers.storage.get(STORAGE_KEY_NOTIF_COUNT) || 0; + notification_count++; + helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count); + + update_ticker_count(); + + // TODO: ask permission to show notifications via Notification.requestPermission + // https://developer.mozilla.org/en-US/docs/Web/API/notification + if (window.Notification && Notification.permission === 'granted') { + var notification_text = notification.liveNow ? notification_data.live_now_text : notification_data.upload_text; + notification_text = notification_text.replace('`x`', notification.author); + + var system_notification = new Notification(notification_text, { + body: notification.title, + icon: '/ggpht' + new URL(notification.authorThumbnails[2].url).pathname, + img: '/ggpht' + new URL(notification.authorThumbnails[4].url).pathname + }); + + system_notification.onclick = function (e) { + open('/watch?v=' + notification.videoId, '_blank'); + }; } }; - notifications.addEventListener('error', handle_notification_error); + notifications.addEventListener('error', function (e) { + console.warn('Something went wrong with notifications, trying to reconnect...'); + notifications = notifications_mock; + setTimeout(get_subscriptions, 1000); + }); + notifications.stream(); } -function handle_notification_error(event) { - console.warn('Something went wrong with notifications, trying to reconnect...'); - notifications = notifications_substitution; - setTimeout(get_subscriptions, 1000); -} +function update_ticker_count() { + var notification_ticker = document.getElementById('notification_ticker'); -addEventListener('load', function (e) { - var notification_count = document.getElementById('notification_count'); - if (notification_count) { - helpers.storage.set('notification_count', parseInt(notification_count.innerText)); + const notification_count = helpers.storage.get(STORAGE_KEY_STREAM); + if (notification_count > 0) { + notification_ticker.innerHTML = + '' + notification_count + ' '; } else { - helpers.storage.set('notification_count', 0); + notification_ticker.innerHTML = + ''; } +} + +function start_stream_if_needed() { + // random wait for other tabs set 'stream' flag + setTimeout(function () { + if (!helpers.storage.get(STORAGE_KEY_STREAM)) { + // if no one set 'stream', set it by yourself and start stream + helpers.storage.set(STORAGE_KEY_STREAM, true); + notifications = notifications_mock; + get_subscriptions(); + } + }, Math.random() * 1000 + 50); // [0.050 .. 1.050) second +} - if (helpers.storage.get('stream')) { - helpers.storage.remove('stream'); - } else { - setTimeout(function () { - if (!helpers.storage.get('stream')) { - notifications = notifications_substitution; - helpers.storage.set('stream', true); - get_subscriptions(); - } - }, Math.random() * 1000 + 50); - } - addEventListener('storage', function (e) { - if (e.key === 'stream' && !e.newValue) { - if (notifications) { - helpers.storage.set('stream', true); - } else { - setTimeout(function () { - if (!helpers.storage.get('stream')) { - notifications = notifications_substitution; - helpers.storage.set('stream', true); - get_subscriptions(); - } - }, Math.random() * 1000 + 50); - } - } else if (e.key === 'notification_count') { - var notification_ticker = document.getElementById('notification_ticker'); - - if (parseInt(e.newValue) > 0) { - notification_ticker.innerHTML = - '' + e.newValue + ' '; - } else { - notification_ticker.innerHTML = - ''; - } +addEventListener('storage', function (e) { + if (e.key === STORAGE_KEY_NOTIF_COUNT) + update_ticker_count(); + + // if 'stream' key was removed + if (e.key === STORAGE_KEY_STREAM && !helpers.storage.get(STORAGE_KEY_STREAM)) { + if (notifications) { + // restore it if we have active stream + helpers.storage.set(STORAGE_KEY_STREAM, true); + } else { + start_stream_if_needed(); } - }); + } +}); + +addEventListener('load', function () { + var notification_count_el = document.getElementById('notification_count'); + var notification_count = notification_count_el ? parseInt(notification_count_el.textContent) : 0; + helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count); + + if (helpers.storage.get(STORAGE_KEY_STREAM)) + helpers.storage.remove(STORAGE_KEY_STREAM); + start_stream_if_needed(); }); -addEventListener('unload', function (e) { - if (notifications) helpers.storage.remove('stream'); +addEventListener('unload', function () { + // let chance to other tabs to be a streamer via firing 'storage' event + if (notifications) helpers.storage.remove(STORAGE_KEY_STREAM); }); diff --git a/assets/js/player.js b/assets/js/player.js index d09892cb..ff9302b7 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -43,9 +43,10 @@ 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('videoplayback')) { if (!options.uri.includes('local=true')) options.uri += '?local=true'; + } return options; }; @@ -346,7 +347,7 @@ if (!video_data.params.listen && video_data.params.quality === 'dash') { targetQualityLevel = 0; break; default: - const targetHeight = parseInt(video_data.params.quality_dash, 10); + const targetHeight = parseInt(video_data.params.quality_dash); for (let i = 0; i < qualityLevels.length; i++) { if (qualityLevels[i].height <= targetHeight) targetQualityLevel = i; @@ -411,8 +412,8 @@ if (!video_data.params.listen && video_data.params.annotations) { function change_volume(delta) { const curVolume = player.volume(); - const newVolume = curVolume + delta; - helpers.clamp(newVolume, 0, 1); + let newVolume = curVolume + delta; + newVolume = helpers.clamp(newVolume, 0, 1); player.volume(newVolume); } @@ -423,8 +424,8 @@ function toggle_muted() { function skip_seconds(delta) { const duration = player.duration(); const curTime = player.currentTime(); - const newTime = curTime + delta; - helpers.clamp(newTime, 0, duration); + let newTime = curTime + delta; + newTime = helpers.clamp(newTime, 0, duration); player.currentTime(newTime); } @@ -434,20 +435,13 @@ function set_seconds_after_start(delta) { } function save_video_time(seconds) { - const videoId = video_data.id; const all_video_times = get_all_video_times(); - - all_video_times[videoId] = seconds; - + all_video_times[video_data.id] = seconds; helpers.storage.set(save_player_pos_key, all_video_times); } function get_video_time() { - const videoId = video_data.id; - const all_video_times = get_all_video_times(); - const timestamp = all_video_times[videoId]; - - return timestamp || 0; + return get_all_video_times()[video_data.id] || 0; } function get_all_video_times() { @@ -534,8 +528,8 @@ function toggle_fullscreen() { function increase_playback_rate(steps) { const maxIndex = options.playbackRates.length - 1; const curIndex = options.playbackRates.indexOf(player.playbackRate()); - const newIndex = curIndex + steps; - helpers.clamp(newIndex, 0, maxIndex); + let newIndex = curIndex + steps; + newIndex = helpers.clamp(newIndex, 0, maxIndex); player.playbackRate(options.playbackRates[newIndex]); } diff --git a/assets/js/playlist_widget.js b/assets/js/playlist_widget.js index 8f8da6d5..c92592ac 100644 --- a/assets/js/playlist_widget.js +++ b/assets/js/playlist_widget.js @@ -12,7 +12,7 @@ function add_playlist_video(target) { helpers.xhr('POST', url, {payload: payload}, { on200: function (response) { - option.innerText = '✓' + option.innerText; + option.textContent = '✓' + option.textContent; } }); } diff --git a/assets/js/watch.js b/assets/js/watch.js index 45492241..f78b9242 100644 --- a/assets/js/watch.js +++ b/assets/js/watch.js @@ -294,7 +294,7 @@ function get_youtube_replies(target, load_more, load_replies) { a.onclick = hide_youtube_replies; a.setAttribute('data-sub-text', video_data.hide_replies_text); a.setAttribute('data-inner-text', video_data.show_replies_text); - a.innerText = video_data.hide_replies_text; + a.textContent = video_data.hide_replies_text; var div = document.createElement('div'); div.innerHTML = response.contentHtml; diff --git a/assets/js/watched_widget.js b/assets/js/watched_widget.js index 497b1878..f1ac9cb4 100644 --- a/assets/js/watched_widget.js +++ b/assets/js/watched_widget.js @@ -20,14 +20,14 @@ function mark_unwatched(target) { var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode; tile.style.display = 'none'; var count = document.getElementById('count'); - count.innerText = parseInt(count.innerText) - 1; + count.textContent--; var url = '/watch_ajax?action_mark_unwatched=1&redirect=false' + '&id=' + target.getAttribute('data-id'); helpers.xhr('POST', url, {payload: payload}, { onNon200: function (xhr) { - count.innerText = parseInt(count.innerText) + 1; + count.textContent++; tile.style.display = ''; } });