それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

radikoのjs / m3u8 / hls 化に併せてAuthtokenと再生URL取得をするサンプル

authtokenの取得方法が変更になってた

Radikoが、JSプレーヤーでHTML5になっていたので、ちょっとリクエストを追いかけて見てた続き

  • auth1 へリクエスト投げてauth_token を取得
  • auth2 でauth_token を有効化
  • m3u8にリクエストを投げる
  • 使い切りのchunklist の url が投げられるのでそこへリクエストを出す。

だいたいはコレで再生ができると思う。

python pip とか npm にしてやろうって人も居るだろうけど、radikoは年に1回にAPI変更することがあるので、メンテする覚悟がないとゴミをnpm に作ることになるよね。

コード書いた

gist.github.com

Raspiのomxplayer でRadikoのm3u8 を再生する(omxplayer でHTTPカスタムヘッダ追加)

何気なく、omxplayer のヘルプを読んでたら --avdict という項目を見つけたので調べてみた。

omxplayer

        --avdict 'opts'         Options passed to demuxer, e.g., 'rtsp_transport:tcp,...'

コレを使えば、カスタムヘッダを追加できるんじゃないかなと。 つまり、Raidkoの再生にffmpeg が要らなくなるじゃん?

omxplayer --avdict '  'X-MY-HEADER: my_header_key'

この形でカスタムヘッダを追加できる。 ffmpeg の --header 相当ですね。

ってことでやってみた

再生する手順

最初に、AuthTokeを持ってm3u8 にアクセスして、テンパラリのURLを生成してもらう。次にそのURLへリクエストを放り込む m3u8 内に記述されたm3u8 のネストをomxplayer は取得できないので、いったんcurl を経由する必要がある。

m3u8のURLを出してもらう

takuya@raspi3:~/omxplayer$ curl -H  'X-Radiko-AuthToken: xxxxxxxxxxxxxxxxxxx' 'https://radiko.jp/v2/api/ts/playlist.m3u8?station_id=MBS&ft=201711101530&to=201711101730'
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=52973,CODECS="mp4a.40.5"
https://radiko.jp/v2/api/ts/chunklist/8NwMph0V.m3u8

m3u8 のURLにomxplayer でアクセスする。

takuya@raspi3:~/omxplayer$ omxplayer -hw -o local --avdict 'X-Radiko-AuthToken: xxxxxxxxxxxxxxx' https://radiko.jp/v2/api/ts/chunklist/8NwMph0V.m3u8
Audio codec aac channels 2 samplerate 48000 bitspersample 16
Subtitle count: 0, state: off, index: 1, delay: 0
have a nice day ;)

これで再生できた。raspi 使う限りにおいてffmpeg でパイプする必要もなくなるしomxplayer だけで解決するのも嬉しい。

東京の放送が聞きたきゃmoperaやLTE経由すればいいし、大阪や関西の放送が聞きたければさくらVPSを経由すればいいし。既存のインフラでなんとかなるのが嬉しい。ちなみにYahooBBだとほとんど福岡にされるのはナゼなんだろう。便利だけどw

Raspberry Pi 3は優秀なRadikoプレーヤーになりそうです。

ただ、m3u8 の場合は スキップが効かないのでそのへんは今後の課題ですね。。。

radiko の js プレイヤーのリクエストを追いかける

radikoがJSになってた

swftoolsいらない可能性があるので追いかける。

auth1 /auth2

従来通り、auth1 / auth2 に投げるのは変化なし。

auth1 で auth_token を貰ってきて auth2 で auth_token で署名をもらうイメージ

auth1 にリクエストを投げて、次の値を取る。

< X-Radiko-AuthToken: m_9ub1okKzi1vctuMS0NNg
< X-Radiko-KeyLength: 16
< X-Radiko-KeyOffset: 6

auth_token がセッションキー

だいたいこんな感じだった。

/// auth 1 の値を持っておいて
var auth_token
var auth_key = "bcd151073c03b352e1ef2fd66c32209da9ca0afa" // 現状は固定
var key_length
var key_offset

x = str2Buffer( auth_key )
y  = new Uint8Array(b, key_offset,key_length )

for (var r = y, a = "", i = 0; i < r.length; i++){ a += String.fromCharCode(r[i]); }

partial_key = btoa(a)

///auth2へ
str2Buffer = (e) =>  {
      return new Uint8Array([].map.call(e, function(e) {
        return e.charCodeAt(0)
      })).buffer
}

メッチャバイト列に戻してるけどそれいるのか

auth2 のリクエス

fetch("https://" + location.host + "/v2/api/auth1", {
        headers: {
          "X-Radiko-App": this._appId, // pc_html5固定
          "X-Radiko-App-Version": "0.0.1",
          "X-Radiko-User": "dummy_user",
          "X-Radiko-Device": this._device  //'pc' 固定
        },
        method: "get",
        credentials: "include"
      }).

auth1 は curl で直叩き出来る

curl -v \
-H 'X-Radiko-App: pc_html5' \
-H 'X-Radiko-App-Version: 0.0.1' \
-H  'X-Radiko-User: dummy_user' \
-H  'X-Radiko-Device: pc' \
    https://radiko.jp/v2/api/auth1

*   Trying 203.211.199.120...
* TCP_NODELAY set
* Connected to radiko.jp (203.211.199.120) port 443 (#0)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
* Server certificate: radiko.jp
* Server certificate: RapidSSL SHA256 CA
* Server certificate: GeoTrust Global CA
> GET /v2/api/auth1 HTTP/1.1
> Host: radiko.jp
> User-Agent: curl/7.56.1
> Accept: */*
> X-Radiko-App: pc_html5
> X-Radiko-App-Version: 0.0.1
> X-Radiko-User: dummy_user
> X-Radiko-Device: pc
>
< HTTP/1.1 200 OK
< Server: nginx
< Date: Wed, 08 Nov 2017 18:06:18 GMT
< Content-Type: text/plain
< Transfer-Encoding: chunked
< Connection: keep-alive
< X-Radiko-AppType: pc
< X-Radiko-AppType2: pc
< X-Radiko-AuthToken: iTzjvyV51b5KGS6i4yaLsA
< X-Radiko-AuthWait: 0
< X-Radiko-Delay: 15
< X-Radiko-KeyLength: 16
< X-Radiko-KeyOffset: 0
< Access-Control-Expose-Headers: X-Radiko-AuthToken, X-Radiko-Partialkey, X-Radiko-AppType, X-Radiko-AuthWait, X-Radiko-Delay, X-Radiko-KeyLength, X-Radiko-KeyOffset
< Access-Control-Allow-Credentials: true

auth2

auth2 のリクエス

 fetch("https://" + location.host + "/v2/api/auth2", {
        headers: {
          "X-Radiko-AuthToken": this._token,
          "X-Radiko-Partialkey": this._partialKey,
          "X-Radiko-User": "dummy_user",
          "X-Radiko-Device": this._device // 'pc' 固定
        },
        method: "get",
        credentials: "include"

なんでわざわざJSにはUInt8ArrayやArrayBufferのバイナリ処理が入ってるのかよくわからない。元が文字列なら、str.split('')で良いんだし。わざわざバイナリ処理にしてるってことは、将来的に auth_key を画像とかバイナリとか暗号化データにするつもりなんだろうかね

auth_key の文字列をレスポンスのoffset と length 分だけ切り取ってbase64するだけなので以前に比べると楽になってるんじゃなかろうか。たぶんcurlbashだけで簡単にかける。眠いのでまた明日。