Greasy Fork

Greasy Fork is available in English.

AbemaTV Auto Reload

AbemaTV(HTML5版)閲覧中に動画が止まったとき、動画を再読み込みします。

目前为 2017-08-04 提交的版本,查看 最新版本

// ==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();

})();