Greasy Fork

AbemaTV Volume Control

AbemaTV(HTML5版)閲覧中にキーボードやマウスホイールで音量を調整します。

当前为 2017-01-09 提交的版本,查看 最新版本

// ==UserScript==
// @name         AbemaTV Volume Control
// @namespace    https://greasyfork.org/ja/scripts/26397
// @version      1
// @description  AbemaTV(HTML5版)閲覧中にキーボードやマウスホイールで音量を調整します。
// @include      https://abema.tv/now-on-air/*
// @grant        GM_addStyle
// ==/UserScript==

(function() {
  'use strict';


  /* ---------- Settings ---------- */

  // 変更した値はブラウザのローカルストレージに保存するので
  // スクリプトをバージョンアップするたびに書き換える必要はありません。
  // (値が0のとき、以前に変更した値か初期値を使用します)

  // ページ右下の音量ボタン&音量ゲージを表示する
  //(音量ゲージ操作で変更した音量と当スクリプトで変更した音量は同期できない場合があります)
  // 1:表示する / 2:表示しない
  // 初期値:2
  // 有効値:1 ~ 2
  var showVolumeButton = 0;

  /* ------------------------------ */


  //ページにイベントリスナーを追加
  function addEventPage() {
    var o = document.querySelector('div[class^="style__overlap___"]');
    if (o) {
      o.addEventListener('mousedown', checkMousedown, false);
      o.addEventListener('wheel', changeVolume, false);
    }
    if (showVolumeButton === 1) {
      var v = document.querySelector('div[class^="styles__volume"] button');
      if (v) v.addEventListener('click', clickVolumeButton, false);
    }
    document.addEventListener('keydown', checkKeyDown, true);
  }

  //音量を変更できるか判別する
  function changeableVolume() {
    return theoplayer.player(0) && theoplayer.player(0).hasOwnProperty('volume');
  }

  //チャンネルが切り替わったとき
  function changeChannel() {
    if (changeableVolume()) {
      checkVolume('changeChannel1');
    } else {
      clearInterval(interval.channel);
      interval.channel = setInterval(function() {
        if (changeableVolume()) {
          clearInterval(interval.channel);
          checkVolume('changeChannel2');
        }
      }, 200);
    }
  }

  //動画の音をミュート・解除
  function changeMute(a, b) {
    log(['changeMute', a, b]);
    if ((!a && b) || (a.button === 1) && changeableVolume()) {
      theoplayer.player(0).muted = !theoplayer.player(0).muted;
      if (theoplayer.player(0).muted) showInfo();
      else showInfo(String(Math.round(theoplayer.player(0).volume * 100)));
      if (showVolumeButton === 1 && !flag.mute) {
        switchVolumeButtonImage();
      }
    }
  }

  //ボリュームスライダーの位置が動いたとき
  function changeSlider() {
    log2(['changeSlider', flag.mute, flag.wheel]);
    if (!flag.mute && !flag.wheel) changeVolume(theoplayer.player(0).volume, 1);
  }

  //音量を変更する
  function changeVolume(a, b) {
    if (changeableVolume()) {
      log2(['changeVolume', a, b]);
      var info = document.getElementById('VolumeControl_Info'),
        vol;
      if (b) vol = a;
      else {
        var y = (a.deltaMode > 0) ? a.deltaY * 100 : a.deltaY;
        vol = theoplayer.player(0).volume + (y / -10000);
      }
      vol = (vol > 1) ? 1 : (vol < 0) ? 0 : vol;
      if (vol > 0.66) {
        info.classList.remove('vc_icon_before_hidden');
        info.classList.remove('vc_icon_after_hidden');
      } else if (vol > 0.33) {
        info.classList.add('vc_icon_before_hidden');
        info.classList.remove('vc_icon_after_hidden');
      } else {
        info.classList.add('vc_icon_before_hidden');
        info.classList.add('vc_icon_after_hidden');
      }
      clearTimeout(interval.wheel);
      flag.wheel = true;
      interval.wheel = setTimeout(function() {
        flag.wheel = false;
      }, 150);
      if (showVolumeButton === 1 && eSlider) eSlider.style.height = Math.ceil(vol * 92) + 'px';
      if (theoplayer.player(0).muted && b !== 3) {
        theoplayer.player(0).muted = false;
        if (showVolumeButton === 1 && eSlider) switchVolumeButtonImage();
      }
      ls.volume = vol;
      saveLocalStorage();
      theoplayer.player(0).volume = vol;
      if (b !== 1) showInfo(String(Math.round(vol * 100)));
    }
  }

  //キーボードのキーを押したとき
  function checkKeyDown(e) {
    if (/input|textarea/i.test(e.target.tagName)) return;
    if (!e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey && changeableVolume()) {
      var v = theoplayer.player(0).volume;
      if (e.keyCode === 38) {
        e.stopPropagation();
        v += 0.05;
        if (v > 1) v = 1;
        changeVolume(v, 2);
      } else if (e.keyCode === 40) {
        e.stopPropagation();
        v -= 0.05;
        if (v < 0) v = 0;
        changeVolume(v, 2);
      } else if (e.keyCode === 77) changeMute(null, true);
    }
  }

  //マウスのボタンを押したとき
  function checkMousedown(e) {
    if (e.button === 1) changeMute(e);
  }

  //ボリュームの値を調べる
  function checkVolume(s) {
    if (theoplayer.player(0).muted) showInfo();
    else if (theoplayer.player(0).volume !== ls.volume) {
      log2(['checkVolume', s]);
      restoreVolume();
    }
  }

  //ボリュームボタンをクリックしたとき
  function clickVolumeButton(e) {
    log2('clickVolumeButton');
    if (!flag.mute) {
      e.stopPropagation();
      flag.mute = true;
      clearTimeout(interval.mute);
      interval.mute = setTimeout(function() {
        flag.mute = false;
      }, 150);
      changeMute(null, true);
      switchVolumeButtonImage();
    } else {
      if (theoplayer.player(0).volume && ls.volume) ls.beforeMute = ls.volume;
      else changeVolume(ls.beforeMute, 1);
    }
  }

  //音量を表示する要素を作成
  function createInfo() {
    var style = '#VolumeControl_Info {align-items:center; background-color:rgba(0,0,0,0.4); border-radius:4px; bottom:80px; color:#FFF; display:flex; justify-content:center; left:20px; min-height:30px; min-width:3em; opacity:0; padding:0.5ex 1ex; position:fixed; visibility:hidden;}' +
      '#VolumeControl_Info.vc_show {opacity:0.8; visibility:visible;}' +
      '#VolumeControl_Info.vc_hidden {opacity:0; transition:opacity 0.5s ease-out, visibility 0.5s ease-out; visibility:hidden;}' +
      '#VolumeControl_Info span:before, #VolumeControl_Info span:after {box-sizing:content-box !important;}' +
      '.vc_icon_before_hidden #VolumeControl_Volume2::before, .vc_icon_after_hidden #VolumeControl_Volume2::after {visibility:hidden;}' +
      '#VolumeControl_Info span::before, #VolumeControl_Info span::after {content:""; display:block; position:absolute;}' +
      '#VolumeControl_Volume1 {height:20px; position:relative; width:30px;}' +
      '#VolumeControl_Volume1::before {background:#FFF; height:8px; left:2px; top:6px; width:4px;}' +
      '#VolumeControl_Volume1::after {border:5px transparent solid; border-left-width:0; border-right-color:#FFF; height:8px; left:6px; top:1px; width:0;}' +
      '#VolumeControl_Volume2, #VolumeControl_Volume3 {position:absolute;}' +
      '#VolumeControl_Volume2 {top:5px; left:8px;}' +
      '#VolumeControl_Volume2::before, #VolumeControl_Volume2::after {border:2px solid transparent; border-right:2px solid #FFF;}' +
      '#VolumeControl_Volume2::before {border-radius:20px; height:20px; left:-3px; top:-2px; width:20px;}' +
      '#VolumeControl_Volume2::after {border-radius:10px; height:15px; left:-2px; top:1px; width:15px;}' +
      '#VolumeControl_Volume3 {left:20px; top:14px;}' +
      '#VolumeControl_Volume3::before, #VolumeControl_Volume3::after {background-color:#FFF; height:2px; width:12px;}' +
      '#VolumeControl_Volume3::before {transform:rotate(45deg);}' +
      '#VolumeControl_Volume3::after {transform:rotate(135deg);}' +
      '#VolumeControl_Volume4 {font-weight:bold; margin-left:1ex;}',
      div = document.createElement('div');
    GM_addStyle(style);
    div.id = 'VolumeControl_Info';
    div.innerHTML = '<span id="VolumeControl_Volume1"></span><span id="VolumeControl_Volume2"></span><span id="VolumeControl_Volume3"></span><span id="VolumeControl_Volume4"></span>';
    document.body.appendChild(div);
  }

  //ページを開いたときに1度だけ実行
  function init() {
    log('init');
    setupSettings();
    if (showVolumeButton === 1) observerS = new MutationObserver(changeSlider);
    waitShowVideo();
    createInfo();
  }

  //デバッグ用 ログ
  function log(s, t) {
    if (ls.debug) {
      if (t) console[t](sid, s);
      else console.log(sid, s);
    }
  }

  //デバッグ用 ログ2
  function log2(s, t) {
    if (ls.debug) {
      var a = [theoplayer.player(0).volume, ls.volume];
      if (Array.isArray(s)) {
        for (var i = s.length; i > 0; i--) {
          a.unshift(s[i - 1]);
        }
      } else a.unshift(s);
      log(a, t);
    }
  }

  //以前調整した音量を復元する
  function restoreVolume() {
    log('restoreVolume');
    if (changeableVolume() && ls.volume >= 0) {
      changeVolume(ls.volume, 3);
    }
  }

  //ローカルストレージに設定を保存する
  function saveLocalStorage() {
    localStorage.setItem(sid, JSON.stringify(ls));
  }

  //設定の値を用意する
  function setupSettings() {
    var vb = (Number.isInteger(Number(showVolumeButton))) ? Number(showVolumeButton) : 0;
    vb = (vb === 0) ? 0 : (vb > 2) ? 2 : (vb < 1) ? 1 : vb;
    showVolumeButton = (ls.showVolumeButton) ? ls.showVolumeButton : (vb) ? vb : 2;
    if (vb && ls.showVolumeButton !== vb) {
      showVolumeButton = vb;
      ls.showVolumeButton = vb;
      saveLocalStorage();
    }
    if (isNaN(Number(ls.volume))) ls.volume = -1;
    else if (ls.volume >= 0) {
      localStorage.setItem('abm_volume', ls.volume);
      localStorage.setItem('volume', ls.volume);
    }
    if (isNaN(Number(ls.beforeMute))) ls.beforeMute = -1;
  }

  //現在の音量を表示
  function showInfo(s) {
    var eInfo = document.getElementById('VolumeControl_Info'),
      eVol2 = document.getElementById('VolumeControl_Volume2'),
      eVol3 = document.getElementById('VolumeControl_Volume3'),
      eVol4 = document.getElementById('VolumeControl_Volume4');
    eVol4.textContent = (theoplayer.player(0).muted) ? 'ミュート' : (s) ? s : '';
    if (theoplayer.player(0).muted) {
      eVol2.style.display = 'none';
      eVol3.style.display = 'block';
    } else {
      eVol2.style.display = 'block';
      eVol3.style.display = 'none';
    }
    eInfo.classList.remove('vc_hidden');
    eInfo.classList.add('vc_show');
    clearTimeout(interval.info);
    interval.info = setTimeout(function() {
      eInfo.classList.remove('vc_show');
      eInfo.classList.add('vc_hidden');
    }, 1000);
  }

  //ページを開いて動画が表示されたら1度だけ実行
  function startObserve() {
    log('startObserve');
    addEventPage();
    restoreVolume();
    observerC.observe(document.querySelector('div[class*="styles__channel-logo"] > img'), moConfig);
    if (showVolumeButton === 1) {
      eSlider = document.querySelector('div[class^="styles__highlighter___"]');
      observerS.observe(eSlider, moConfig);
      switchVolumeButtonImage();
    } else {
      var eVolume = document.querySelector('div[class^="styles__volume___"]');
      if (eVolume) eVolume.style.display = 'none';
    }
  }

  //ボリュームボタンの画像を切り替える
  function switchVolumeButtonImage() {
    log('switchVolumeButtonImage');
    var use = document.querySelector('div[class^="styles__volume"] use'),
      href;
    if (use && use.hasAttribute('xlink:href')) {
      href = use.getAttribute('xlink:href');
      if (href) {
        if (theoplayer.player(0).muted) href = href.replace(/^(.+volume_)on$/, '$1off');
        else href = href.replace(/^(.+volume_)off$/, '$1on');
        use.setAttribute('xlink:href', href);
      }
    }
  }

  //動画が表示されるのを待つ
  function waitShowVideo() {
    setTimeout(function() {
      if (theoplayer.player(0) && !isNaN(theoplayer.player(0).duration)) startObserve();
      else {
        clearInterval(interval.video);
        interval.video = setInterval(function() {
          if (theoplayer.player(0) && !isNaN(theoplayer.player(0).duration)) {
            clearInterval(interval.video);
            flag.countWaitShowVideo = 0;
            startObserve();
          } else if (flag.countWaitShowVideo > 50) {
            clearInterval(interval.video);
            flag.countWaitShowVideo = 0;
          } else flag.countWaitShowVideo++;
        }, 200);
      }
    }, 400);
  }

  var sid = 'VolumeControl',
    ls = JSON.parse(localStorage.getItem(sid)) || {},
    observerC = new MutationObserver(changeChannel),
    moConfig = { attributes: true, characterData: true },
    flag = { countWaitShowVideo: 0, mute: false, wheel: false },
    interval = { chhannel: 0, info: 0, mute: 0, video: 0, wheel: 0 },
    observerS, eSlider;
  init();

})();