nicoThumbWatch4HTML5の仕組み

nicoThumbWatch4HTML5の仕組み

過去の記事の解説

結構前に作ったからうろ覚え


Webの知識

クロスサイト制約

参考
クロスサイトスクリプティング - Wikipedia

クロスサイトとは,ドメインが違うサイト
(ちゃんとした定義があるのかも知れないが,今の話ではこの解釈でいいと思う.)

ブラウザには,クロスサイトスクリプティングの対策がかかっている.
なので,Javascriptからクロスサイトの情報が取得できない.
もちろん全てが遮断されるとwebの自由度が下がるし,やっぱり回避方法があったりする.

  1. JSONPをつかう
  2. Access-Control-Allow-Originを設定する

これ以外の方法があれば,是非教えてください.

Javascriptの実行順

HTMLは,基本的に上から順に処理される.
もちろんJavascriptもHTMLタグとして埋め込むので,例外ではない.
例えば,次のコードは動く.

<script src="jquery.js"></script>
<script>
  $(function(){
    console.log('Hello,jQuery!');
  });
</script>

だが,次のコードはエラーが出る.

<script>
  $(function(){
    console.log('Hello,jQuery!');
  });
</script>
<script src="jquery.js"></script>

jQueryを読み込む前に,jQueryを使おうとするとエラーが出る.


ニコニコ動画の仕組み

動画を見るためには

niconicoの動画を見るためには,動画URLCookieが必要.
逆に言えば,それさえあればよい.

  1. 動画URLの取得方法

    1. http://flapi.nicovideo.jp/api/getflv?v=[動画ID]
    2. http://ext.nicovideo.jp/thumb_watch?v=[動画ID]&k=[キー]

    で,1.の方法がログインが必要
    2.の方法はログインが要らない.

    備考
    2.のAPIは,ニコニコ動画の外部プレイヤーに使われている.
    だから,ログインの必要がない.

  2. Cookieの取得方法

    1. http://www.nicovideo.jp/watch/[動画ID]
    2. http://ext.nicovideo.jp/thumb_watch?v=[動画ID]

    で,上と同様1.はログインが必要
    2.の方法はログインが要らない.

画質について

ニコニコ動画には,エコノミー画質一般画質プレミアム画質がある.

参考
低画質モードとは (テイガシツモードとは) [単語記事] - ニコニコ大百科
プレミアム高画質とは (プレミアムコウガシツとは) [単語記事] - ニコニコ大百科

ちなみに,エコノミー画質ニコニコ動画側で再エンコードされるので,mp4ファイルになる.
(例外があるかも知れないが,自分はまだ遭遇してない.)

また,動画にはswfというFlash形式のものもある.(IDがnmから始まるもの)
これはエコノミー画質がない.

エコノミー画質を意図的に再生するには,URLのクエリに eco=1 をつければ見れる.

アクセス過多

ニコニコ動画では,同じデバイスから短時間に一定量を超えるアクセスをすると制限される.
どんな風になるかは動画再生画面でF5を長押ししてみればわかるが,やらないように.

外部プレイヤー

ニコニコ動画の再生画面の共有ボタンを押すと,外部プレイヤーのHTMLが出てくる.

<script type="text/javascript" src="http://ext.nicovideo.jp/thumb_watch/sm9"></script>
<noscript><a href="http://www.nicovideo.jp/watch/sm9">【ニコニコ動画】新・豪血寺一族 -煩悩解放 - レッツゴー!陰陽師</a></noscript>

このHTMLを貼った場所に外部プレイヤーができる.
ちなみに,Flashが使えない環境では動画へのリンクが張られる.

このスクリプトは色々対策されていて,最初から貼っていないと動かない.
後からJavascriptで追加しても動かない.
また,iframe内でも動かない.


nicoThumbWatch4HTML5の仕組み

問題提起

前述したが,ニコニコ動画にはブログなどのWebサイトに貼り付けることのできる外部プレイヤーが存在する.
外部プレイヤーは,その記事での話題の動画を気軽に見られる点で非常に便利である.
しかし,その外部プレイヤーはFlashでできているためか,Flash非対応だとただのリンクになる.

近年では,スマートフォンが普及してブログサイトもスマートフォンのアクセスが多い.
スマートフォンでは外部プレイヤーが機能しないため,せっかくの利点が活かせない.

なんとかしてスマートフォンから外部プレイヤーの動画が再生できるようにしたい.

余談
自分はその頃,ブログなど持っていなかったので,拡張機能として搭載したかった.
しかし,スマートフォンChromeには拡張機能が搭載できなかったので断念.
Firefoxならできるらしいが,情報量が少ないし,自分のメインブラウザでないのでやめた.

外部プレイヤーについて調べる

まず,普通に埋め込まれたFlashプレイヤーを見てみる.

<embed type="application/x-shockwave-flash" id="external_nico_0" name="external_nico_0" src="http://ext.nicovideo.jp/swf/player/thumbwatch.swf?ts=1409030935" width="240" height="180" allowscriptaccess="always" allowfullscreen="true" bgcolor="#000000" quality="high" flashvars="thumbWatch=1&amp;thumbPlayKey=1409491870.0.1.cvydqLR8hlqF5736rGX9bBpjVlA.aHR0cDovLzM4NDZtYXNhLndlYi5mYzIuY29tL25pY29UaHVtYldhdGNoLw%3D%3D..&amp;playerTimestamp=1409030966&amp;player_version_xml=1409030965&amp;player_info_xml=1409030966&amp;translation_version_json=1407995394&amp;v=sm9&amp;videoId=sm9&amp;thumbTitle=%E6%96%B0%E3%83%BB%E8%B1%AA%E8%A1%80%E5%AF%BA%E4%B8%80%E6%97%8F%20-%E7%85%A9%E6%82%A9%E8%A7%A3%E6%94%BE%20-%20%E3%83%AC%E3%83%83%E3%83%84%E3%82%B4%E3%83%BC%EF%BC%81%E9%99%B0%E9%99%BD%E5%B8%AB&amp;thumbDescription=%E3%83%AC%E3%83%83%E3%83%84%E3%82%B4%E3%83%BC%EF%BC%81%E9%99%B0%E9%99%BD%E5%B8%AB%EF%BC%88%E3%83%95%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%A9%E3%82%B9%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%EF%BC%89&amp;thumbImage=http%3A%2F%2Ftn-skr2.smilevideo.jp%2Fsmile%3Fi%3D9&amp;movie_type=flv&amp;wv_id=sm9&amp;language=ja-jp&amp;iee=1&amp;noAdSense=1&amp;category=%E9%9F%B3%E6%A5%BD%20%E3%82%B2%E3%83%BC%E3%83%A0&amp;leaf_switch=2&amp;accessFromHash=1409405470%3Afa248693c6553258cb78520b621014a2da15b92052aecf70b6c7afc0383e3da5">

わけわからんほど長い.
どう見ても,flashvarsに情報が詰め込まれている.
整形して,「&」で改行するととこうなる.

thumbWatch=1
thumbPlayKey=1409491870.0.1.cvydqLR8hlqF5736rGX9bBpjVlA.aHR0cDovLzM4NDZtYXNhLndlYi5mYzIuY29tL25pY29UaHVtYldhdGNoLw==..
playerTimestamp=1409030966
player_version_xml=1409030965
player_info_xml=1409030966
translation_version_json=1407995394
v=sm9
videoId=sm9
thumbTitle=新・豪血寺一族 -煩悩解放 - レッツゴー!陰陽師
thumbDescription=レッツゴー!陰陽師(フルコーラスバージョン)
thumbImage=http://tn-skr2.smilevideo.jp/smile?i=9
movie_type=flv
wv_id=sm9
language=ja-jp
iee=1
noAdSense=1
category=音楽 ゲーム
leaf_switch=2
accessFromHash=1409405470:fa248693c6553258cb78520b621014a2da15b92052aecf70b6c7afc0383e3da5

色々情報が書かれているが必要なのはthumbPlayKeyである.
これが前述のキーである.

このキーが重要なのだが,そもそもFlashが埋め込まれないとこのキーは手に入らない.
つまり,Flash非対応であるスマートフォンでは手に入らない.
Flashを埋め込んでもらう必要がある.

Flash埋め込みのソースコードを見て,埋め込んでもらえるようにする.

if (typeof Nicovideo == 'undefined') {
    Nicovideo = new Object();
}
if (typeof Nicovideo.Global == 'undefined') {
    Nicovideo.Global = {
        playerCount: 0,
        embedMode: "noflash"
    };
    (function () {
        if (navigator.plugins && typeof navigator.plugins["Shockwave Flash"] === "object" && navigator.mimeTypes && navigator.mimeTypes.length) {
            Nicovideo.Global.embedMode = "embed";
        } else if (typeof ActiveXObject !== "undefined") {
            try {
                var ax = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
                if (ax) {
                    Nicovideo.Global.embedMode = "object";
                }
            } catch (e) {}
        }
    })();
}

埋め込みソースコードの先頭部分である.
恐らくNicovideo.Global.embedModeがembedであれば良いと推測できる.
そこの部分のif文を詳しく見てみる.

if(
  navigator.plugins &&
  typeof navigator.plugins["Shockwave Flash"] === "object" &&
  navigator.mimeTypes &&
  navigator.mimeTypes.length
  )

まとめると,
1. navigator.pluginsが存在する
2. navigator.plugins['Shockwave Flash']Objectである.
3. navigator.mimeTypesが存在し,navigator.mimeTypes.lengthが存在する.

これらを満たすように書く.

const navigator = {'plugins': {'Shockwave Flash':{}},'mimeTypes':[1,2]};
navigator.plugins['Shockwave Flash'] = {};

navigatorを上書きとか,同じ値を2度代入とか余りに酷すぎるコードなのだが,試行錯誤の末である.
これでiOSSafariAndroidChrome,Firefoxの全てで動いた.
ここは改良の余地が大いにある.

ここで分かる通り,他のスクリプトに影響が出る可能性が高い.
何事にも犠牲が必要なのだ.
(何とかする方法はありそうだが,気力ないのでやめた)

Cookieと動画URL

さて,ついにCookieが手に入る.
http://ext.nicovideo.jp/thumb_watch?v=[動画ID]&k=[キー]
アクセスするとCookieがもらえるので,適当にdisplay:none;のiframeでアクセスさせる.

動画URLを手に入れるのは簡単で,アクセス先のデータを見ると書いてある.
(以下「&」区切り)

thread_id=1173108780
l=319
url=http://smile-com42.nicovideo.jp/smile?m=9.0468
ms=http://msg.nicovideo.jp/10/api/
ms_sub=http://sub.msg.nicovideo.jp/10/api/
is_premium=0
time=1409409032778
done=true

これのurlの部分が動画URLである.
ちなみに動画URLの最後にlowをつけるとエコノミー画質になる.

しかし,iframeからこのデータにアクセスできない.
前述のクロスサイト制約である.
これに関しては,クライアント側で取得する方法がまったく思いつかず断念.
簡易的にproxyサーバを作ることにした.
今回はGoogleAppsScriptを使って代わりに情報を取ってきてもらうように組む.
(このとき,自サーバはもっていなかった)

これで後は,videoタグでも使って元のFlashと置き換えれば完成だ.

余談
前述のとおり,動画URLを取得するにもキーが必要だ.
このキーはアクセス元ごとに違うものが割り当てられる.
当初,動画URLの取得にYQLを使おうとしていた.
しかし,YQLは接続ごとにサーバが変わるため上手く動かなかった.
YQLは面白かったので,他の機会にぜひ使いたい.

videoタグの弊害

HTML5からvideoタグができ,簡単に動画を埋め込めるようになった.
しかし,対応している動画形式が決まっている.
swfが再生できないのはもちろんのことだが,flv形式も再生できない.
ここで動画をmp4として取得するためにeco=1オプションを使う.
処理が面倒だったので,すべての動画においてエコノミー画質にしている.


そんな感じで成り立っている.
要約すると,
1. Flashが使えると偽装
2. 埋め込まれたHTMLタグからキーを取得
3. キーをAPIに投げてCookieを取得
4. 自前proxyから動画URLを取得する.
5. videoタグで埋め込む.

書き出してみると,大したことはしていないな.
コメント表示は色々面倒だし,iPhoneでは実装不可なので断念.

余談
Youtubeは埋め込み動画がHTML5プレイヤーになっているので,スマートフォンでも再生できる.
ニコニコ動画の外部プレイヤーもそうなると嬉しい.
コメント表示の部分の実装が難しいのかもしれないが.