意図したNetStatusEventが上がってこないことへの対策

2008.04.15 / actionscript

Cameraオブジェクトを利用して、RTMPでライブストリーミングを行うときに、ストリーミングの開始、終了をハンドリングするために肝なのがNetStatusEvent。NetStreamに対してハンドラを設定しておき、開始、終了を検知します。

  ns.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
  ...
  function netStatusHandler(evt:NetStatusEvent) : void {
      if(evt.info.code == "NetStream.Play.Start"){
          // 配信開始                 
      } else if(evt.info.code == "NetStream.Play.Reset"){ 
          // 配信中    
      } else if(evt.info.code == "NetStream.Play.UnpublishNotify"){
          // 配信停止
      } else {
          // その他
      }
  }
  

基本的にはこんな感じ。

罠が多い

ところが、あくまでこの情報は「基本的」なもので、結構アテにならないことが多いです。NetStreamで受信しているストリームが1つであればまだイベントの発生も安定していますが、複数ストリームを同時に受信している場合、たとえば終了イベントがうまく起こらず、うまく終了処理が行えないことがあります。

このような場合、映像を表示しているVideoオブジェクトのclear()を呼び出すことができず、残像が残っちゃったようなことになってしまう、格好悪い表示になってしまうことがあります。今回はこんなとき、どうやってハンドリングすればいいか、という話。

Timerを走らせる

じゃあどうすればいいか、というと本当にライブ配信をしているかどうか、x秒ごとに定期的に映像のFPS(=フレームレート)をチェックするTimerを走らせるといい結果になります。一定時間ごとに数回確認して、n回連続して同じ低レートの値であれば、すでに表示しているユーザは配信を停止している、という発想。

ここでのポイントは「n回連続して同じ低レートの値であれば」という点。終了検知なら、FPS=0でハンドリングしてもよさそうですが、配信を停止していてもFPSは0になるとは限らず、0.1, 0.2くらいの値が出ることは割と多いです。なので、「0」ではなく、「低レートの値」がポイント。

また、配信対象物が停止している場合(風景とか)、これもかなり低い値が出ます。ただ、カメラで実際に映像を取得しているかぎり、毎秒常に同じ値が出ることはまず無いので、「n回連続して」というのが効いてきます。

実際に、どんな値であればいいのか、というと何度か試行錯誤した結果、こんなパラメータを僕は利用しています。

  • x = 15
  • FPS = 0.2
  • n = 3

つまり、「15秒ごとにFPSを調べて0.2以下の同じ値が3回連続して続いたら、そのユーザは配信を停止していると見なす」という意味ですね。

NetStream.Play.Resetは実は重要

あと、普段はほとんど利用しないであろうNetStatusEvent#info.code値のNetStream.Play.Resetですが、実は結構重要です。と、いうのも、ストリーミングを行っている中で、このイベントは割と頻繁に起こります。Livedocsによると「再生リストのリセットが原因です。」と、ありますが意味はよくわかりません。よく分かりませんが頻繁に発生します。頻繁に発生する、ということは「このイベントが発生しているかぎりユーザは停止せずに再生しつづけている」ということを示すものとして利用できます。

具体的には、上で示したFPS確認ロジックにおいて、NetStatusEvent#info.code==NetStream.Play.Resetであれば、確認カウンタをクリアさせます。(たとえFPSが低い値を出していても、ユーザは確実に再生を続けているから)。これらをまとめて、こんなTimerを用意しておけばOKです。

PlayerManager.as(的なもの)

  var timer:Timer = new Timer(15000);
  timer.addEventListener(TimerEvent.TIMER, streamCheckerHandler);
  timer.start();
  ...
  
  function streamCheckHandler(evt:TimerEvent) : void {
      for(var i:int = 0; i<players.length; i++){
          var player : Player = players[i];
          var ns : NetStream = player.getNetStream();
          var fps : Number = ns.currentFPS;
          if(fps < 0.2) {
              player.checkFPSHistory(fps);
          } 
      } 
  }
  

Player.as(的なもの)

  var prevFPS : Number = 0;
  var fpsHistoryCount : int = 0;
  const FPS_HISTORY_MAX_COUNT : int = 3;
  ...
  function checkFPSHistory(fps : Number) : void {
      if(fps!=prevFPS){
          prevFPS = fps;
          fpsHistoryCount = 0;
      } else {
          fpsHistoryCount++;
      }           
      if(fpsHistoryCount==FPS_HISTORY_MAX_COUNT){
          unPublish();
          prevFPS = 0;
          fpsHistoryCount = 0;
      }
  }
  ...
  ns.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
  ...
  fuction netStatusHandler(evt : NetStatusEvent) : void {
      if(evt.info.code == "NetStream.Play.Start"){
          // start to publish     
      } else if(evt.info.code == "NetStream.Play.Reset"){ 
          fpsHistoryCount = 0;
      } else if(evt.info.code == "NetStream.Play.UnpublishNotify"){
          stopPublish();
      } 
  }
  

PlayerManagerがいくつかのPlayerオブジェクトを作成、参照をもってTimerを実行しているオブジェクトです。PlayerはVideoオブジェクトやNetStreamオブジェクトなどをメンバに持ち、実際に映像の再生を行うもの。こんな感じで全体の流れを掴んでいただければと思います。