Twitter Streaming APIを使って関連TLをテレビにオーバーレイ表示するAIRアプリを作ってみた

一昨年の記事、テレビにTwitterの関連TLをオーバーレイ表示するAIRアプリを作ってみた - Tosshi Note で紹介したアプリを Twitter Streaming API に対応してみました。

AIRアプリ自体の公開は反応をみることにして、Twitter Streaming APIActionScript で実装する例があまり見つからなかったので、とりあえず載せときます。
ちなみに URLStream を使って実装していますが、URLStream の Progress イベントは HTTP chunk レスポンスの受信とは別に起こります。そのため、流れの速いTLでないと TweetReceived イベントにタイムラグが発生するかもしれません。

URLStreamから取得できるデータ本体は、[ツイートJSON][CRLF][ツイートJSON][CRLF][ツイートJSON][CRLF] となります。
ツイートがないときに、Keep-Aliveのためのパケットも[CRLF]となるため、バッファに文字列をためつつ、[CRLF]を探しては切り出して、文字列長0でなければJSONをパースしてツイート受信イベント発生という流れです。

以下、ソースをべた貼りしておきます。

TwitterStreamLoader.as

コンストラクタ引数にBASIC認証Twitterスクリーン名とパスワードを指定します。

package com.tilfin.tvtwlayer.twitter
{
  import com.adobe.serialization.json.JSON;
  
  import flash.events.EventDispatcher;
  import flash.events.HTTPStatusEvent;
  import flash.events.IOErrorEvent;
  import flash.events.ProgressEvent;
  import flash.net.URLRequest;
  import flash.net.URLRequestHeader;
  import flash.net.URLRequestMethod;
  import flash.net.URLStream;
  import flash.net.URLVariables;
  
  import mx.utils.Base64Encoder;
  
  /**
   * Tweet Received イベント 
   */
  [Event(name="tweetReceived", type="com.tilfin.tvtwlayer.twitter.TwitterStreamEvent")]
  
  /**
   * Twitter Stream API Loader
   */
  public class TwitterStreamLoader extends EventDispatcher
  {
    private static const ROOT_URL:String = "http://stream.twitter.com/1/statuses/";
    
    private var _authHeader:URLRequestHeader;
    private var _stream:URLStream;
    private var _streamBuffer:String = "";
    
    /**
     * コンストラクタ
     * 
     * @param screenName スクリーン名
     * @param password パスワード
     */
    public function TwitterStreamLoader(screenName:String , password:String) {
      super();
      
      var encoder:Base64Encoder = new Base64Encoder();
      encoder.encode(screenName + ":" + password);
      _authHeader = new URLRequestHeader("Authorization", "Basic " + encoder.flush());
    }
    
    /**
     * 各種フィルタをかけたストリームの受信を開始します。
     * 
     * @param params クエリパラメータ
     */
    public function loadFilterStream(params:Object):void {
      var variables:URLVariables = new URLVariables();
      for (var key:String in params) {
        variables[key] = params[key];
      }
      
      var request:URLRequest = new URLRequest(ROOT_URL + "filter.json");
      request.method = URLRequestMethod.POST;
      request.requestHeaders = [ _authHeader ];
      request.data = variables;

      _stream = new URLStream();
      _stream.addEventListener(IOErrorEvent.IO_ERROR, onIoError);
      _stream.addEventListener(HTTPStatusEvent.HTTP_RESPONSE_STATUS, onHttpResponseStatus);
      _stream.addEventListener(ProgressEvent.PROGRESS, onProgress);
      _stream.load(request);
    }
    
    /**
     * ストリームを閉じます。
     */
    public function close():void {
      _stream.removeEventListener(IOErrorEvent.IO_ERROR, onIoError);
      _stream.removeEventListener(ProgressEvent.PROGRESS, onProgress);
      _stream.close();
      _stream = null;
    }
    
    private function onProgress(event:ProgressEvent):void {
      var buffer:String = _stream.readUTFBytes(_stream.bytesAvailable);
      _streamBuffer += buffer;
      
      var twstr:String;
      var splitpos:int = _streamBuffer.indexOf("\r\n");
      while (splitpos > -1) {
        twstr = _streamBuffer.substr(0, splitpos);
        if (twstr.length > 0) {
          receivedTweet(twstr);
        }
        _streamBuffer = _streamBuffer.substr(splitpos + 2);
        splitpos = _streamBuffer.indexOf("\r\n");
      }
    }
    
    private function receivedTweet(str:String):void {
      var tweet:Object = JSON.decode(str);
      dispatchEvent(new TwitterStreamEvent(TwitterStreamEvent.TWEET_RECEIVED, tweet));
    }
    
    private function onIoError(event:IOErrorEvent):void {
      trace("IO Error.");
    }
  }
}

TwitterStreamEvent.as

package com.tilfin.tvtwlayer.twitter
{
  import flash.events.Event;
  
  /**
   * Twitter Stream イベント
   */
  public class TwitterStreamEvent extends Event
  {
    /**
     * イベントタイプ : ツイート受信
     */
    public static const TWEET_RECEIVED:String = "tweetReceived";
    
    
    private var _tweet:Object;
    
    /**
     * @return ツイート
     */
    public function get tweet():Object {
      return _tweet;
    }
    
    /**
     * コンストラクタ
     * 
     * @param type イベントタイプ
     * @param tweet ツイート
     */
    public function TwitterStreamEvent(type:String, tweet:Object = null) {
      super(type, false, false);
      
      _tweet = tweet;
    }
  }
}

実行例

Public streams | Twitter Developers の statuses/filter の Parameters をマップかして loadFilterStream メソッドの引数に渡すと、ツイートを受信するたびに JSON を ActionScript Object化したものを受け取れます。

var streamLoader:TwitterStreamLoader = new TwitterStreamLoader(screenname, password);
streamLoader.addEventListener(TwitterStreamEvent.TWEET_RECEIVED, function(event:TwitterStreamEvent):void {
      event.tweet; // ツイートの JSON を ActionScript Object化したもの
    });
streamLoader.loadFilterStream({ "track": "#f1jp,#f1" });

trackは「,」区切りで検索語を複数指定できる引数です。