メディア再生窓生成

  • /*
     * @title メディア再生窓生成
     * @description マルチメディア要素へのリンクのクリックイベントに<audio>や<video>なウィンドウをポップアップする機能をオーバーライドする
     * @include http://*
     * @license NYSL
     */
    
    
    // 一つのウィンドウでaudioとvideoを同時再生するとウィンドウのサイズがえらいことになりますが、
    // 非常にレアケースだと思われるので無視しています。「運用回避してください。」
    
    
    (function(){
      var checked = [];        // 同じURLに複数回リクエストを送るのを防ぐためのリストを格納
      var timeout = 30 * 1000; // タイムアウトになるまでの時間(単位:ms)
      
      var allowList = ["audio", "video"];
    
      if (!HTMLElement.prototype.addPopup) { // addPopupメソッドを要素に追加
    
        Object.defineProperty(HTMLElement.prototype, "addPopup", {
          value: function(url){
            var self = this;
    
            var xhr = new XMLHttpRequest(); // HEADリクエストを送る
            xhr.open("HEAD", url, true);
            xhr.onreadystatechange = function(){
              if (xhr.readyState === 4) {
                console.log(xhr.status + " : " + url);
    
                if (xhr.status !== 200) return;
    
                var header = xhr.getResponseHeader("Content-Type");
                var type   = header.split("/")[0];
    
                if (allowList.some(function(v){ return v === type })) { // いずれかにヒットすれば true
                  console.log(header + " - OK");
                  addPopupItem(self, type);
                }
              }
            };
            xhr.send(null);
    
            window.setTimeout(function(){ // 一定時間でタイムアウト
              xhr.abort();
            }, timeout);
          }
        });
      }
      else {
        return false;
      }
    
      var elems = document.querySelectorAll("a[href], area[href]");
      elems = Array.prototype.slice.call(elems); // リンカブル要素を全て抽出
    
      for (var i in elems) {
        var url = elems[i].href;
    
        if (url.indexOf("http") === 0) {
    
          url = url.split("#")[0]; // URLに # を含む場合は除去
    
          if (checked.every(function(li){ return url !== li })) { // 全てとアンマッチなら true
            checked.push(url);
            elems[i].addPopup(url);
          }
        }
      }
    
      function addPopupItem(target, mediaType) { // ポップアップを生成
        target.onclick = function(evt){
          if (evt.ctrlKey && evt.altKey) {
            return true; // Ctrl+Alt を押しながらクリックすれば普段のクリックと同じ効果
          }
          else {
            openPopup();
            evt.preventDefault();
          }
        };
    
        function openPopup() {
          var popup = window.open("", "media-popup", "titlebar=no,menubar=no,toolbar=no,location=no,status=no,scrollbars=no");
          var doc   = popup.document;
          var rect;
          var element;
    
          var diff = {
            width:  popup.outerWidth  - doc.documentElement.clientWidth,
            height: popup.outerHeight - doc.documentElement.clientHeight
          };
    
          if (!rect) {
            element = doc.createElement(mediaType);
            element.setAttribute("controls", "controls");
            doc.body.appendChild(element);
            rect = element.getBoundingClientRect();
            doc.body.removeChild(element);
          }
          
          doc.body.addEventListener("DOMSubtreeModified", function(){ // 要素数が変化したらウィンドウサイズを変える
            var len = this.childNodes.length;
            popup.resizeTo( rect.width + diff.width, rect.height * len + diff.height );
          }, false);
    
          var style = doc.createElement("style");
          doc.querySelector("head").appendChild(style);
          var s = style.sheet, i = 0;
          [
            "body {"
            + "margin: 0;"
            + "padding: 0;"
            + "}",
            "audio {"
            + "display: block;"
            + "margin: auto;"
            + "}"
          ].forEach(function(value){
            s.insertRule(value, i++);
          });
    
          element = doc.createElement(mediaType);
          element.setAttribute("src", target.href);
          element.setAttribute("autoplay", "autoplay");
          element.setAttribute("controls", "controls");
          element.addEventListener("ended", function(){ // 終端まで移動したら自らを削除する
            this.parentElement.removeChild(this);
            close();
          }, false);
    
          /*
            末尾に挿入する
            appendChildでないのはIEがDOMSubtreeModifiedに反応しない(らしい)ため
            http://msdn.microsoft.com/en-us/library/gg558014(v=vs.85).aspx
          */
          doc.body.insertBefore(element, null);
    
          function close() { // ポップアップ内のメディアがゼロのとき、子ウィンドウを閉じる
            doc.querySelectorAll( allowList.join(",") ).length === 0
            ? popup.close() : 0;
          }
        }
      }
    
    })();
    
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。

History

  1. 2013/04/28 18:17:21 - 2013-04-28
  2. 2013/04/27 14:07:33 - 2013-04-27
  3. 2013/04/27 03:49:45 - 2013-04-27
  4. 2013/04/27 02:47:19 - 2013-04-27
  5. 2013/04/27 02:44:48 - 2013-04-27
  6. 2013/04/27 02:43:38 - 2013-04-27
  7. 2013/04/26 17:28:58 - 2013-04-26
  8. 2013/04/23 02:15:08 - 2013-04-23
  9. 2013/04/22 10:27:13 - 2013-04-22
  10. 2013/04/22 10:01:50 - 2013-04-22
  11. 2013/04/22 09:50:14 - 2013-04-22