生テレを眺めた

生テレとは、ブックウォーカー(KADOKAWA)がリリースした、タレント自身が生放送をするプラットフォーム。https://namatv.jp/

ニコ生との違いは”課金要素”。

アカウントはニコニコアカウント、課金システムもニコニコポイントを使うので、
課金の為に作られたシステムと言っても過言では無い。

リリース時のプレス記事によれば

また運営に関して、ブックウォーカーはドワンゴおよびKADOKAWAと連携。ドワンゴとはニコニコ生放送と生テレの番組共同制作などを実施、KADOKAWAは雑誌や映画などを通じて協業する。また、スターを目指すタレントのたまごを対象に、他メディアと協業してのオーディション企画も実施し、新人発掘なども実施していく計画だ。

http://ascii.jp/elem/000/001/242/1242480/
ということらしい。知らん。

因みに同時放送していた番組もニコ生は途中カットで誘導。あれ…

何であれ、録画する需要が出来てしまったので、色々と調べた。
生テレにはタイムシフトという概念は無いらしい。


まず番組ページを開くと、
https://namatv.jp/program/0001
番組開始まで、30秒毎にPOSTリクエストが送られる。
https://namatv.jp/api/program/before/show
要求ボディ:{“programId”:”0001″}

配信が始まると、番組ページに”var videoId=”と変数が定義されます。
同時にそのIDを利用して、プレイヤー本体であるBrightcoveに接続を開始。
https://edge.api.brightcove.com/playback/v1/accounts/4801429315001/videos/[videoID]
すると番組名やObject(AppleHLS)、reference_idが返ってくる。この応答ではvideo_IDはasset_idとして記述されている。
そしてmaster.m3u8→high.m3u8と繋がっていく。

master.m3u8があるということは、将来のabr対応を視野に入れているということなのだろうか。
少なくとも今は解像度768*432の一つだけ。

CDNはAkamaiだった。
https://bwhlslive-i.akamaihd.net/hls/live/262345/[reference_id]/4801429315001/high.m3u8
https://bwhlslive-i.akamaihd.net/hls/live/262345/[reference_id]/4801429315001/high-[00001-00300].ts

4801429315001は生テレ運営がBrightcoveを利用する為のaccount_id。
2623XXは番組によって前後したが、サーバーの振り分けだろうか?262335-262350まで確認した。

さて、生放送中に保存するならhigh.m3u8を投げておしまいである。
ただ、例えば出遅れた時等は見返すことが出来ない。
然しながら、生放送中に、メディアプレイリスト(high.m3u8)を調べておけば、何とかなる。
先のBrightcoveのAPIに、要求ヘッダをつけずにGETすると
[{“error_code”:”INVALID_POLICY_KEY”,”message”:”Request policy key is missing or invalid”}]
となるが、
要求ヘッダの、Acceptヘッダフィールドに、
Accept: application/json;pk=abcdefg1234567890(一例)
Accept:application/json;pk=BCpkADawqM0JuhdAR5ltYcF75lYGhlH-BMDUvcu5ryEdXB90DClTPOjHAWLNL9TfxPUeOHlM1LsBB74Rq8Vm7J_khrxLYtPwfOVtRzHVqHLcAt59eHMYeHT7S6yYPnp_df737dsxTHIJe_W6

を付けてあげれば401(Unauthorized)とならずに、ステータスコードは200(正常)となる。
(非ログイン状態や他の環境でも共通のStringであったため訂正)

通常Acceptヘッダフィールドは受け入れ可能なメディアタイプの他に、メディアタイプの品質係数を付ける”q”があるが、
それ以外にも”accept-extension”としてtoken等を記述できるらしい。初めて知った。

さて、生テレには他の配信プラットフォームのようにアーカイブ用のプレイリストは存在しないので、
上のプレイリストから得たセグメントを手動で繋げる必要がある。
ちなみに、配信後数時間でセグメントは消えるので、注意だ。

というわけで、自分は以下の様な方法をとった。
1.high.m3u8のURIを取得するBrightcoveのAPIからmaster.m3u8を取得する
2.それを元にセグメントのURIを予測し、Irvineにて一括ダウンロード
3.繋げる(繋げ方は後述)

よくわからなくても、ブラウザの検証ツールを開いておけばなんとかなるかも知れない。


2月16日の配信から、HLSセグメントが10秒から2秒になり、ユーザーコメントのラグが小さくなった。
これはこのようなコンテンツにとって、非常に喜ばしいこと。

しかしながら、何故かZencorderが生成したHLSセグメントは、
1セグメントあたりのVideoのdurationと、Audioのdurationが異なっていた。

HLSでは、プレイリストに記述されたセグメント長で再生する為、問題は起きないようだ。
映像がVFRモードの場合、フレーム数はセグメント毎に異なり、全てを正しいセグメント長には出来ないからだろうか。
一部のフレームを破棄して調整しているように見える。

これの何が問題か、というと、ffmpegのconcatを利用して連結した時に、断続的にプツプツという音切れが発生する。
(以下の様にfilelistをテキストで記述し、-i “list.txt”という風に使う。多数の動画ファイルの連結に使える。)
file ‘high-00001.ts’
file ‘high-00002.ts’

例えば60分の放送において、音声部分は60分ジャストであるものの、動画部分は63分になっており、
動画尺に合わせて再生するプレイヤー(VLC,MPCHC他)では音声を途切って調整してしまうらしい。
(音声のみ取り出してみると(=例えばAudacityに投げてみると)、60分ジャストであることが確認できる。)

セグメントが10秒の場合、あまり気にならなかったが(それでも40秒位の誤差はある)、
2秒になれば、音切れは言葉の途中にかかることも多くなり、流石に気持ち悪い上、誤差は200秒にも及ぶ。

ffmpegのvsyncやasync、-mapの[syncfile:syncstream]も使ってみたがうまくいかず、
結局一番の解決策は、全てのセグメントを記述したプレイリストファイルを生成することだった。
Fresh!ではarchive.m3u8のように、LINELIVEやLive.me等ではそれ用のプレイリストが別途生成されているが、
生テレにはそんなものはない。(そもそもセグメント自体すぐに消えてしまうが)

参考例として、実際に降ってきた最後のプレイリスト[high.m3u8]を見てみる。

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:1806
#EXT-X-TARGETDURATION:2
#EXTINF:2.00000,
high-01807.ts
#EXTINF:2.00000,
high-01808.ts
#EXTINF:2.00000,
high-01809.ts
#EXTINF:1.06700,
high-01810.ts
#ZEN-TOTAL-DURATION:3619.0670
#EXT-X-ENDLIST

#EXT-X-MEDIA-SEQUENCEでは、既に配信済のセグメント数、
#EXT-X-TARGETDURATIONではセグメント長を、
#ZEN-TOTAL-DURATIONでトータル尺を記述しているらしい。

そんなわけで作ったlist.m3u8はこんな風になった。Excelの並べ替えを使用すると簡単。

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-TARGETDURATION:2
#EXTINF:2.00000,
high-00001.ts
#EXTINF:2.00000,
high-00002.ts
~~中略~~
#EXTINF:2.00000,
high-01809.ts
#EXTINF:1.06700,
high-01810.ts
#ZEN-TOTAL-DURATION:3619.0670
#EXT-X-ENDLIST

そしてこのプレイリストを使用し、無事正しいファイルを手にすることが出来た。めでたしめでたし。
ffmpeg -i “list.m3u8” -c copy -bsf:a aac_adtstoasc out.mp4