htsign's blog

ノンジャンルで書くつもりだけど技術系が多いんじゃないかと思います

MIMEタイプがaudio/xxxやvideo/xxxなときにやをどうにかこうにかするスクリプトを改造した

以前書いたの<a>の直後にエレメントを追加して直接ページ上で再生するやつだったけど、今回のはwindow.openを使って子ウィンドウを作るタイプにした。

こっちの方がスタイルシートの影響範囲をいちいち考えなくて済むから楽かもしれない。

audio/xxxvideo/xxxなリンクをクリックする度に、子ウィンドウを開いてそこに<audio><video>な要素を配置します。
既に子ウィンドウが出ている場合は、同じ子ウィンドウの末尾に要素を追加していきます。
それぞれのメディア要素は再生が終わると自身を殺します。
他のすべてのメディア要素が死んでいる状態で、最後のメディア要素が終了すると子ウィンドウが閉じる仕組みです。

(function(){
  var checked = [];        // 同じURLに複数回リクエストを送るのを防ぐためのリストを格納
  var timeout = 30 * 1000; // タイムアウトになるまでの時間(単位:ms)
  
  var allowList = ["audio", "video"];

  if (!HTMLAnchorElement.prototype.addPopup) { // addPopupメソッドをA要素に追加

    Object.defineProperty(HTMLAnchorElement.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.getElementsByTagName("a");
  elems = Array.prototype.slice.call(elems); // A要素を全て抽出

  for (var i in elems) {
    var url = elems[i].href;

    if (url.indexOf("http") === 0) {

      url = url.indexOf("#") + 1 ? url.split("#")[0] : url; // 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();
        return false;
      }
    };

    function openPopup() {
      var popup = window.open("", "media-popup", "menubar=no,toolbar=no,location=no,status=no,scrollbars=yes");
      var doc   = popup.document;

      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++);
      });

      var element; // 殻の中身を定義

      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);

      doc.body.appendChild(element);
      var rect = element.getBoundingClientRect();
      popup.resizeTo(rect.width + 80, rect.height + 200);

      function close() { // 再生中のメディアがゼロのとき、子ウィンドウを閉じる
        doc.querySelectorAll( allowList.join(",") ).length === 0
        ? popup.close() : 0;
      }
    }
  }

})();
ブックマークレット
javascript:(function(d,A,c,l,P,E,i,u){l=["audio","video"],A=function(E,t,P){P=function(p,d,S,e,r){p=open("","media-popup","menubar=no,toolbar=no,location=no,status=no,scrollbars=yes"),d=p.document,S=d.createElement("style"),i=0;d.querySelector("head").appendChild(S);["body{margin:0;padding:0}","audio{display:block;margin:auto}"].forEach(function(s){S.sheet.insertRule(s,i++)});e=d.createElement(t);e.src=E.href,e.autoplay="autoplay",e.controls="controls";e.addEventListener("ended",function(){this.parentElement.removeChild(this);d.querySelectorAll(l.join(",")).length===0?p.close():0},false);d.body.appendChild(e);r=e.getBoundingClientRect();p.resizeTo(r.width+80,r.height+200)},E.onclick=function(e){if(e.ctrlKey&&e.altKey)return true;else{P();return false}}},c=[],P=HTMLAnchorElement.prototype;if(P.addPopup)return false;else{Object.defineProperty(P,"addPopup",{value:function(u,S,X){S=this,X=new XMLHttpRequest();X.open("HEAD",u);X.onreadystatechange=function(h,t){if(X.readyState===4){console.log(X.status+" : "+u);if(X.status!==200)return;h=X.getResponseHeader("Content-Type"),t=h.split("/")[0];if(l.some(function(v){return v===t})){console.log(h+" - OK");A(S,t)}}};X.send();setTimeout(function(){X.abort()},3e4)}})}E=d.querySelectorAll("a");for(i=E.length;i;){u=E[--i].href.split("#")[0];if(/^http/.test(u))if(c.every(function(i){return u!==i}))E[i].addPopup(c[c.length]=u)}})(document)

目立たないけど、ブックマークレット版の方はコード量を減らすためにかなりの工夫を凝らしてるつもりです。
可能な限りセミコロンやブレースを削ったり、効率的なビルドイン関数を使ったり、ちまちまやってます。
読んでもらえると楽しいです。たぶん。可読性最悪ですが。