// ==UserScript==
// @name AbemaTV Auto Reload
// @namespace http://greasyfork.icu/ja/scripts/25598
// @version 7
// @description AbemaTV(HTML5版)閲覧中に動画が止まったとき、動画を再読み込みします。
// @include https://abema.tv/now-on-air/*
// @grant GM_addStyle
// ==/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 && theoplayer.player && 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 (aq.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',
'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 && theoplayer.player && 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 && theoplayer.player && 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 && theoplayer.player && 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 && theoplayer.player && 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();
})();