// ==UserScript== // @name AbemaTV Auto Reload // @namespace https://greasyfork.org/ja/scripts/25598 // @version 6 // @description AbemaTV(HTML5版)閲覧中に動画が止まったとき、動画を再読み込みします。 // @include https://abema.tv/now-on-air/* // @grant GM_addStyle // @downloadURL none // ==/UserScript== (function() { 'use strict'; /* ---------- Settings ---------- */ // 変更した値はブラウザのローカルストレージに保存するので // スクリプトをバージョンアップするたびに書き換える必要はありません。 // (値が0のとき、以前に変更した値か初期値を使用します) // 動画が止まったとき、チャンネルを切り替えるまでの待ち時間(ミリ秒) // 初期値:1500 // 有効値:1000 ~ 30000 var waitingReloadChannel = 0; // 画質を変更したとき、左上に解像度を表示するかどうか // 1:表示する / 2:表示しない // 初期値:1 // 有効値:1 ~ 2 var displayInformation = 0; /* ------------------------------ */ //ページにイベントリスナーを追加 function addEventPage() { log('addEventPage'); document.addEventListener('visibilitychange', checkPageVisibility, false); document.addEventListener('keydown', checkKeyDown, false); } //動画にイベントリスナーを追加 function addEventVideo(s) { logV(['addEventVideo', s]); if (!flag.reload) { eV = returnVideo('addEventVideo2'); if (!eV) { checkChangeVideo('addEventVideo2'); return; } if (!eV.classList.contains(sid)) { eV.classList.add(sid); eV.addEventListener('error', videoError, false); eV.addEventListener('playing', videoPlaying, false); eV.addEventListener('stalled', videoStalled, false); eV.addEventListener('waiting', videoWaiting, false); if (theoplayer.player(0)) theoplayer.player(0).videoTracks.item(0).qualities.addEventListener('activequalitychanged', playerActiveQualityChanged, false); if (ls.debug) { eV.addEventListener('canplay', videoCanplay, false); eV.addEventListener('canplaythrough', videoCanplaythrough, false); eV.addEventListener('durationchange', videoDurationchange, false); eV.addEventListener('emptied', videoEmpied, false); eV.addEventListener('ended', videoEnded, false); eV.addEventListener('loadeddata', videoLoadeddata, false); eV.addEventListener('loadedmetadata', videoLoadedmetadata, false); eV.addEventListener('loadstart', videoLoadstart, false); eV.addEventListener('pause', videoPause, false); eV.addEventListener('play', videoPlay, false); eV.addEventListener('progress', videoProgress, false); eV.addEventListener('seeked', videoSeeked, false); eV.addEventListener('seeking', videoSeeking, false); eV.addEventListener('suspend', videoSuspend, false); eV.addEventListener('timeupdate', videoTimeupdate, false); } checkQuality('addEventVideo'); } } } //チャンネルを切り替える function changeChannel(s) { log(['changeChannel', s], 'debug'); flag.reloadTime = 0; flag.changeChannel = true; eNext.click(); } //番組名が変更したとき function changeProgramTitle() { log('changeProgramTitle1', 'debug'); var title = returnProgramTitle(), time = returnProgramTime(); if (title) { programTitle = title.textContent || ''; log(['returnProgramTitle2', programTitle], 'debug'); } if (time) { var t = time.textContent || ''; if (t) { programStartTime = t.slice(t.indexOf(')') + 1, t.indexOf(' ')); log(['returnProgramTitle3', programStartTime], 'debug'); } } checkPlayingVideo(eV.currentTime, 'changeProgramTitle'); } function returnProgramTime() { return document.querySelector('p[class^="styles__time"]'); } //画質を変更する function changeQuality(n) { var p = theoplayer.player(0); if (p) { var q = p.videoTracks.item(0).qualities; if (n >= 49 && n <= 53) { var num = n - 49, len = q.length; if (num > len - 1) num = len - 1; q.targetQuality = q.getQualityByID(num); ls.videoQuality = n - 48; } else if (!n) { q.targetQuality = null; ls.videoQuality = 0; } saveLocalStorage(); } } //動画が切り替わったかを調べる function checkChangeVideo(a) { logV('checkChangeVideo'); var e = returnVideo('checkChangeVideo'); if (!flag.change1 && !e) { log(['checkChangeVideo1', a]); flag.change1 = true; setTimeout(function() { flag.change1 = false; checkChangeVideo('checkChangeVideo'); }, 550); } else if (!flag.change2 && e && (!eV || e.id !== eV.id)) { log(['checkChangeVideo2', a]); flag.change2 = true; setTimeout(function() { flag.change2 = false; addEventVideo('checkChangeVideo'); }, 1000); } else { log(['checkChangeVideo3', a]); checkPlayingVideo(e.currentTime, 'checkChangeVideo3'); } } //キーボードのキーを押したとき function checkKeyDown(e) { if (/input|textarea/i.test(e.target.tagName)) return; if (e.shiftKey) { if (e.keyCode === 48) changeQuality(); else if (e.keyCode >= 49 && e.keyCode <= 53) changeQuality(e.keyCode); else if (e.keyCode === 57) checkQuality(); } } //ページが見えている状態かを調べる function checkPageVisibility() { var h = document.hidden; log(['checkPageVisibility', h]); if (flag.hidden && !h && !eV.paused) checkPlayingVideo(eV.currentTime, 'checkPageVisibility'); flag.hidden = h; } //動画が実際に再生中なのかを調べる function checkPlayingVideo(o, s) { setTimeout(function() { var now = eV.currentTime; log(['checkPlayingVideo', s, 'time', o, now]); if (!now) { setTimeout(function() { var old = eV.currentTime; setTimeout(function() { now = eV.currentTime; if (!now || now === old) reloadVideo('checkPlayingVideo1-' + s); }, 100); }, 2000); } else if (now === o) reloadVideo('checkPlayingVideo2-' + s); }, 100); } //動画の画質を調べる function checkQuality(s) { var p = theoplayer.player(0), vi = returnVideo('checkQuality'); if (p) { var pfr = p.frameRate, q = p.videoTracks.item(0).qualities, aq = q.activeQuality, max = q.getQualityByID(q.length - 1); if (s) { log(['checkQuality1', max.resolution.width, max.resolution.height], 'debug'); flag.checkQuality = true; clearInterval(iCheckQuality); iCheckQuality = setInterval(function() { if (max.resolution.height) { clearInterval(iCheckQuality); log(['checkQuality2', max.resolution.width, max.resolution.height], 'debug'); flag.checkQuality = false; if (ls.videoQuality) changeQuality(ls.videoQuality + 48); } }, 500); } else if (aq.resolution.height && max.resolution.height) { if (pfr && isFinite(pfr) && String(pfr).split('.')[1] && String(pfr).split('.')[1].length > 3) { pfr = pfr.toFixed(3); } notify( 'checkQuality', '現在の画質:' + p.videoWidth + '×' + p.videoHeight + ', ' + pfr + 'fps\n' + '選択(' + aq.id + '):' + aq.resolution.width + '×' + aq.resolution.height + ', ' + aq.frameRate + 'fps, ' + (aq.bandwidth / 1000) + 'kbps\n' + '最高(' + (q.length - 1) + '):' + max.resolution.width + '×' + max.resolution.height + ', ' + max.frameRate + 'fps, ' + (max.bandwidth / 1000) + 'kbps', 'debug', 8000 ); } } else if (vi) { notify( 'checkQuality', '現在の画質:' + vi.videoWidth + '×' + vi.videoHeight, 'debug', 8000 ); } } //解像度の情報を表示する要素を作成 function createInfo() { var style = '#AutoReload_Info {align-items:center; background-color:rgba(0,0,0,0.6); border-radius:4px; color:white; display:flex; justify-content:center; left:20px; min-height:2em; min-width:4em; opacity:0; padding:0.5ex 1ex; position:fixed; top:50px; visibility:hidden;}' + '#AutoReload_Info.ar_show{opacity:0.6; visibility:visible;}' + '#AutoReload_Info.ar_hidden{opacity:0; visibility:hidden; transition:opacity 0.5s ease-out, visibility 0.5s ease-out;}', ele = document.createElement('div'); GM_addStyle(style); ele.id = 'AutoReload_Info'; document.body.appendChild(ele); } //ページを開いたときに1度だけ実行 function init() { log('init'); setupSettings(); waitShowVideo('init'); createInfo(); } //デバッグ用 ログ function log(s, t) { if (ls.debug) { if (t) console[t](sid, s); else console.log(sid, s); } } //デバッグ用 詳細ログ function logV(s, t) { if (ls.debug) { try { var v = returnVideo('logV'); if (v) { var a = [ v.readyState, v.networkState, v.played.length, v.currentTime, v.id, { 'duration': v.duration, 'seeking': v.seeking, 'preload': v.preload, 'ended': v.ended, 'srcLength': v.src.length }, Object.assign({}, flag) ]; if (Array.isArray(s)) { for (var i = s.length; i > 0; i--) { a.unshift(s[i - 1]); } } else a.unshift(s); if (v.paused) a.push('paused'); if (v.error) a.push('error', v.error.code); log(a, t); } else log(['logV not found Video', s], 'warn'); } catch (error) { log(['logV error', s, error], 'error'); } } } //デスクトップ通知 function notify(f, m, t, s) { var title = 'AbemaTV Auto Reload', message = m, notifi; log(['notify', f, m], t); if ('Notification' in window) { if (Notification.permission === 'granted') { notifi = new Notification(title, { body: message, tag: f }); } else if (Notification.permission !== 'denied') { Notification.requestPermission(function(permission) { if (permission === 'granted') { notifi = new Notification(title, { body: message, tag: f }); } }); } if (notifi) { clearTimeout(iNotify); iNotify = setTimeout(notifi.close.bind(notifi), s ? s : 3000); } } else console.log(title, message); } //画質が変更されたとき function playerActiveQualityChanged() { if (displayInformation === 1) { log('activequalitychanged0', 'info'); var eInfo = document.getElementById('AutoReload_Info'), eSelect = document.getElementsByClassName('theoplayer-configuration-panel-content')[0], n = 0; clearInterval(iPlayerActiveQualityChanged); iPlayerActiveQualityChanged = setInterval(function() { var p = theoplayer.player(0); if (!p || !p.videoTracks.item(0)) return; var w = p.videoWidth, h = p.videoHeight, a, r; log(['activequalitychanged1', w, h], 'info'); a = p.videoTracks.item(0).qualities.activeQuality; if (a) r = a.resolution; if (r && r.width && r.height && isFinite(r.width) && isFinite(r.height)) { log(['activequalitychanged2', eSelect.selectedIndex, a.id, r.width, r.height, w, h], 'info'); if (!eInfo.classList.contains('ar_pre') && !flag.checkQuality) { eInfo.classList.add('ar_pre'); showInfo((eSelect.selectedIndex) ? '(' + r.height + 'p)' : 'Auto (' + r.height + 'p)'); } if (r.height === h) { log(['activequalitychanged3', eSelect.selectedIndex, a.id, r.width, r.height, w, h], 'info'); eInfo.classList.remove('ar_pre'); showInfo(w + '×' + h); n = 0; clearInterval(iPlayerActiveQualityChanged); } else if (n > 60) { n = 0; clearInterval(iPlayerActiveQualityChanged); eInfo.classList.remove('ar_show'); eInfo.classList.remove('ar_pre'); } else n++; } }, 1000); } } //ページを再読み込みする function reloadPage(s) { if (flag.reload) { log(['reloadPage', s], 'debug'); notify('reloadPage', 'ページを再読み込みします!'); setTimeout(function() { location.reload(); }, 500); } } //動画を再読み込みする function reloadVideo(s) { logV(['reloadVideo', s]); if (!document.hidden && !flag.changeChannel && !flag.undoChannel) { if (theoplayer.player(0) && (!flag.reloadTime || (flag.reloadTime && Date.now() - flag.reloadTime < waitingReloadChannel))) { if (!flag.reloadTime) flag.reloadTime = Date.now(); log(['--- loading ---', Date.now() - flag.reloadTime], 'debug'); theoplayer.player(0).load(); setTimeout(function() { checkPlayingVideo(eV.currentTime, 'reloadVideo'); }, 500); } else if (document.getElementsByTagName('video').length === document.getElementsByTagName('audio').length) { var vi = returnVideo('reloadVideo'); if (vi && vi.error) vi.load(); } else { if (!flag.changeChannel && !flag.undoChannel) changeChannel('reloadVideo'); else if (!flag.reload) { flag.reload = true; reloadPage(s); } } } } //チャンネルロゴ画像の要素を返す function returnChLogo(s) { var e = document.querySelector('div[class*="styles__channel-logo"] > img'); if (s) { if (e) { chId = e.getAttribute('alt'); log(['chId', chId], 'debug'); } else log('returnChLogo error', 'error'); } return e; } //番組名の要素を返す function returnProgramTitle() { return document.querySelector('span[class^="styles__title___"]'); } //HTML5動画の要素を返す function returnVideo(s) { var vi = document.getElementsByTagName('video'); if (!vi) log(['returnVideo error', s], 'debug'); for (var i = 0, j = vi.length; i < j; i++) { if (vi[i].src && vi[i].style.display !== 'none') return vi[i]; } return null; } //ローカルストレージに設定を保存する function saveLocalStorage() { localStorage.setItem(sid, JSON.stringify(ls)); } //設定の値を用意する function setupSettings() { var rc = (Number.isInteger(Number(waitingReloadChannel))) ? Number(waitingReloadChannel) : 0, di = (Number.isInteger(Number(displayInformation))) ? Number(displayInformation) : 0; if (!Number.isInteger(Number(ls.videoQuality))) ls.videoQuality = -1; rc = (rc === 0) ? 0 : (rc > 30000) ? 30000 : (rc < 1000) ? 1000 : rc; di = (di === 0) ? 0 : (di > 2) ? 2 : (di < 1) ? 1 : di; waitingReloadChannel = (ls.waitingReloadChannel) ? ls.waitingReloadChannel : (rc) ? rc : 1500; displayInformation = (ls.displayInformation) ? ls.displayInformation : (di) ? di : 1; if (rc && ls.waitingReloadChannel !== rc) { waitingReloadChannel = rc; ls.waitingReloadChannel = rc; saveLocalStorage(); } if (di && ls.displayInformation !== di) { displayInformation = di; ls.displayInformation = di; saveLocalStorage(); } } //解像度情報の要素を表示 function showInfo(s) { var eInfo = document.getElementById('AutoReload_Info'); eInfo.textContent = s; eInfo.classList.remove('ar_hidden'); eInfo.classList.add('ar_show'); clearTimeout(iInfo); iInfo = setTimeout(function() { eInfo.classList.remove('ar_show'); eInfo.classList.add('ar_hidden'); }, 2500); } //ページを開いて動画が表示されたら1度だけ実行 function startObserve() { log('startObserve'); ePrev = document.querySelector('div[class^="style__box"] > button:first-child'); eNext = document.querySelector('div[class^="style__box"] > button:last-child'); observerC.observe(returnChLogo(), moConfig); if (theoplayer.player(0)) observerV.observe(theoplayer.player(0).element, moConfig2); observerT.observe(returnProgramTitle(), moConfig2); addEventPage(); addEventVideo('startObserve'); } //動画の取得中にエラーが発生したとき function videoError() { logV('videoError'); checkPlayingVideo(eV.currentTime, 'videoError'); } //動画を再生し始めたときや再生中にソースが切り替わったとき function videoPlaying() { logV('videoPlaying'); if (flag.reloadTime) { log(['+++++ playing +++++', Date.now() - flag.reloadTime], 'debug'); flag.reloadTime = 0; } if (flag.undoChannel) flag.undoChannel = false; } //動画を取得できなかったときや取得し終えたとき function videoStalled() { logV('videoStalled'); if (eV && (eV.readyState < 3 || eV.networkState !== 2)) checkPlayingVideo(eV.currentTime, 'videoStalled'); } //動画の読み込みを待っているとき function videoWaiting() { logV('videoWaiting'); var old = eV.currentTime; if (!old) reloadVideo('videoWaiting'); else checkPlayingVideo(old, 'videoWaiting'); } //動画を再生できるがキャッシュが足りないとき function videoCanplay() { logV('videoCanplay'); } //動画を再生できてキャッシュも足りるとき function videoCanplaythrough() { logV('videoCanplaythrough'); } //動画の長さが変更されたとき function videoDurationchange() { if (eV.readyState < 3 || eV.networkState !== 2) logV('videoDurationchange'); } //動画が空になったとき function videoEmpied() { logV('videoEmpied'); } //動画を最後まで再生したとき function videoEnded() { logV('videoEnded'); } //動画のメタ情報を読み込んだとき function videoLoadedmetadata() { logV('videoLoadedmetadata'); } //動画を再生できる状態になったとき function videoLoadeddata() { logV('videoLoadeddata'); } //動画をこれから読み込むとき function videoLoadstart() { logV('videoLoadstart'); } //動画を一時停止・解除したとき function videoPause() { logV('videoPause'); } //動画を(手動操作で)再生し始めたとき function videoPlay() { logV('videoPlay'); } //動画を取得しているとき function videoProgress() { logV('videoProgress'); } //動画をシークし終えたとき function videoSeeked() { logV('videoSeeked'); } //動画をシークし始めたとき function videoSeeking() { logV('videoSeeking'); } //動画の取得を取りやめたとき function videoSuspend() { logV('videoSuspend'); } //動画の再生位置が更新されたとき function videoTimeupdate() { if (eV.readyState < 3 || eV.networkState !== 2) logV('videoTimeupdate'); } //動画が表示されるのを待つ function waitShowVideo(s) { log(['waitShowVideo', s, flag.show]); if (!flag.show) { flag.show = true; clearInterval(iWaitShowVideo); setTimeout(function() { if (theoplayer.player(0) && !isNaN(theoplayer.player(0).duration)) { eV = returnVideo('waitShowVideo'); if (s === 'init') startObserve(); else addEventVideo('waitShowVideo1'); flag.show = false; } else { clearInterval(iWaitShowVideo); iWaitShowVideo = setInterval(function() { if (theoplayer.player(0) && !isNaN(theoplayer.player(0).duration)) { clearInterval(iWaitShowVideo); flag.countWaitShowVideo = 0; eV = returnVideo('waitShowVideo'); if (s === 'init') startObserve(); else addEventVideo('waitShowVideo2'); flag.show = false; } else if (flag.countWaitShowVideo > 25) { clearInterval(iWaitShowVideo); flag.countWaitShowVideo = 0; changeChannel('waitShowVideo'); } }, 200); } }, 400); } if (s === 'observerC') { returnChLogo('waitShowVideo-observerC'); if (flag.changeChannel && !flag.undoChannel) { log('undoChannel', 'debug'); flag.changeChannel = false; flag.undoChannel = true; ePrev.click(); setTimeout(function() { flag.undoChannel = false; }, 100); } } } function waitShowVideoC() { waitShowVideo('observerC'); } function waitShowVideoV() { waitShowVideo('observerV'); } var sid = 'AutoReload', ls = JSON.parse(localStorage.getItem(sid)) || {}, observerC = new MutationObserver(waitShowVideoC), observerV = new MutationObserver(waitShowVideoV), observerT = new MutationObserver(changeProgramTitle), moConfig = { attributes: true, characterData: true }, moConfig2 = { childList: true }, flag = { change1: false, change2: false, changeChannel: false, checkQuality: false, countWaitShowVideo: 0, hidden: false, loadstart: false, reload: false, reloadTime: 0, show: false, undoChannel: false }, eV, ePrev, eNext, chId, programTitle, programStartTime, iWaitShowVideo, iCheckQuality, iPlayerActiveQualityChanged, iInfo, iNotify; init(); })();