NLE(Niconico Live Encoder)はどうやって配信用URLを取得しているのか?

  • 9
    Like
  • 1
    Comment
More than 1 year has passed since last update.

NLEではセッションを消費せずに配信用のURLやキーを取得しています。この動きと同じことが実装できれば、配信ツールでセッションを一個消費ということも無くなるはずです。ということで、解析結果とサンプルコード(Ruby)になります。

全体的な動作

  1. アップデート確認
  2. ログイン
  3. ライセンス取得
  4. ログイン
  5. 配信情報確認
  6. 動画配信!

なお、アップデート確認とライセンス取得は不要です。

ログイン

Request

  • URL: https://account.nicovideo.jp/api/v1/login
  • Method: POST
  • User Agent: nicoliveenc/{NLEバージョン番号}
  • Content Type: application/x-www-form-urlencoded
  • Form Data:
    • site: nicolive_encoder
    • time: {UNIXタイム}
    • hash_key: {8桁16進数} # 省略可
    • mail: {メールアドレス}
    • password: {パスワード}

hash_keyの生成方法は不明です。なくても認証できます。

Response

  • Set Cookie:
    • Name: nicosid
    • Vlaue: {UNIXタイム}.{数字}
    • Path: /
    • Domain: .nicovideo.jp
  • Data: XML

    <nicovideo_user_response status="ok">
        <ticket>nicolive_encoder_{数字}</ticket>
    </nicovideo_user_response>
    

必要なのはticketのコンテンツのみです。nicosidは別に使わなくてもいいようです。もし、ログインに失敗している場合はstatusがfailになります。

バージョン確認

URL: http://live.nicovideo.jp/encoder/update.xml

NLEの最新バージョンを確認します。必須ではありません。詳細は省略。

ライセンス取得

URL: http://live.nicovideo.jp/encoder/getlicence

NLEのライセンスを取得します。必須ではありません。詳細は省略。

配信情報取得

Request

  • URL: http://live.nicovideo.jp/api/getpublishstatus
  • Method: POST
  • User Agent: nicoliveenc/{NLEバージョン番号}
  • Content Type: application/x-www-form-urlencoded
  • Cookie: nicosid={loginで取得した値} # 省略可
  • Form Data:
    • ticket: {loginで取得したticketの値}
    • nleserial: {getlicenceで取得したシリアル番号} # 省略可
    • accept-multi: {0/1}

Cookieは渡さなくてもいいようです。nlserialにはライセンス取得で取得したシリアル番号が必要ですが、なくても問題ありません。accept-multiは一つのみ取得(0)か複数取得(1)を指定できます。値によって結果のXMLが変わります。

Response

accept-multi=0の場合

  • Data: XML

    <getpublishstatus status="ok" time="1432436641" multi="true">
        <stream>{動画の情報 省略}</stream>
        <user>{ユーザーの情報 省略}</user>
        <rtmp is_fms="1">
            <url>rtmp://{ホスト}.live.nicovideo.jp:{ポート番号}/publicorigin/{数字}</url>
            <stream>lv{数字}</stream>
            <ticket>{ユーザID}:{ライブID}:{数字}:{数字}:{数字}:{数字}:{16進数}</ticket>
            <bitrate>{数字}</bitrate>
        </rtmp>
    </getpublishstatus>
    

accept-multi=1の場合

  • Data: XML

    <getpublishstatus status="ok" time="1432436641" multi="true">
        <list>
            <item>
                <stream>{動画の情報 省略}</stream>
                <rtmp is_fms="1">
                    <url>rtmp://{ホスト}.live.nicovideo.jp:{ポート番号}/publicorigin/{数字}</url>
                    <stream>lv{数字}</stream>
                    <ticket>{ユーザID}:{ライブID}:{数字}:{数字}:{数字}:{数字}:{16進数}</ticket>
                    <bitrate>{数字}</bitrate>
                </rtmp>
            </item>
        </list>
        <user>{ユーザーの情報 省略}</user>
    </getpublishstatus>
    

statusがokなら配信可能です。配信できないときはfailが入ります。accept-multi=0の場合は、単純にアクセスしたときと同じです。accept-multi=1の場合は、配信の情報だけ、list化されてitemに入れられます。実際の配信URLとストリームキーは下記になります。

  • 配信URL: {urlの値}?{tickeの値}
  • ストリームキー: {streamの値}

bitrateは現在配信可能なビットレートを表します。

サンプルコード

以上を踏まえて、配信URL、ストリームキー、ビットレートを取得するツールを作りました。煮るなり、焼くなり、Go言語製コメビュに移植するなり、ご自由に!

simnle.rb
# coding: utf-8

require "net/https"
require "uri"
require "nokogiri"

class SimNLE

  NICOURI = {
    login: URI("https://account.nicovideo.jp/api/v1/login"),
    getpub: URI("http://live.nicovideo.jp/api/getpublishstatus"),
  }

  USER_AGENT = "simnle.rb/0.0.1"

  def initialize(mail, password)
    @mail = mail
    @password = password
    @ticket = nil
    @nicosid = nil
  end

  def post_header
    header = {
      "User-Agent" => SimNLE::USER_AGENT,
      "Content-Type" => "application/x-www-form-urlencoded",
    }
    if @nicosid
      header["Cookie"] = "nicosid=#{URI.encode_www_form_component(@nicosid)}"
    end
    return header
  end

  def login
    https = Net::HTTP.new(SimNLE::NICOURI[:login].host, 443)
    https.use_ssl = true
    https.verify_mode = OpenSSL::SSL::VERIFY_NONE

    data = {
      "site" => "nicolive_encoder",
      "time" => Time.now.to_i.to_s,
      "mail" => @mail,
      "password" => @password,
    }

    response = nil
    https.start do
      response = https.post(SimNLE::NICOURI[:login].path,
          URI.encode_www_form(data), post_header)
    end
    response.value

    doc = Nokogiri::XML.parse(response.body)
    case doc.xpath("/nicovideo_user_response").attribute("status").to_s
    when "ok"
      @ticket = doc.xpath("/nicovideo_user_response/ticket").inner_text
      response.get_fields("set-cookie").each do |cookie_data|
        list = cookie_data.split(";")[0].strip.split("=")
        if list[0] == "nicosid"
          @nicosid = list[1]
        end
      end
      return @ticket
    when "fail"
      return nil
    else
      raise "Unknown status"
    end
  end

  def getpub
    unless @ticket || login
      raise "Cannot login"
    end

    http = Net::HTTP.new(SimNLE::NICOURI[:getpub].host, 80)

    data = {
      "ticket" => @ticket,
      "accept-multi" => 0,
    }

    response = nil
    http.start do
      response = http.post(SimNLE::NICOURI[:getpub].path,
          URI.encode_www_form(data), post_header)
    end
    response.value

    doc = Nokogiri::XML.parse(response.body)
    case doc.xpath("/getpublishstatus").attribute("status").to_s
    when "ok"
      url = doc.xpath("/getpublishstatus/rtmp/url").inner_text
      stream = doc.xpath("/getpublishstatus/rtmp/stream").inner_text
      ticket = doc.xpath("/getpublishstatus/rtmp/ticket").inner_text
      bitrate = doc.xpath("/getpublishstatus/rtmp/bitrate").inner_text
      rtmp_url = "#{url}?#{ticket}"
      rtmp_key = stream
      return rtmp_url, rtmp_key, bitrate
    when "fail"
      return nil
    else
      raise "Unknown status"
    end
  end
end

if __FILE__ == $0
  if ARGV.size < 2
    warn "Usage: ruby #{$0} mail password"
    exit 1
  end
  simnle = SimNLE.new(ARGV[0], ARGV[1])
  # puts simnle.login
  puts simnle.getpub
end