カテゴリー別アーカイブ: ActionScript

AIRからJavaScriptの関数を呼ぶ方法

ExternalInterface.call(“JS関数名”, parameter); ってやるのかと思ってたらそうじゃないみたいなのでメモしておきます。

air2jsTest.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx" 
    creationComplete="init(event);">

  <s:layout>
    <s:VerticalLayout/>
  </s:layout>

  <fx:Script>
    <![CDATA[
      private var htmlLoader:HTMLLoader;

      private function init(event:Event):void {
        htmlLoader = new HTMLLoader();
        htmlLoader.addEventListener(Event.COMPLETE, onComplete);
        //jsを埋め込んだhtmlをロードする
        htmlLoader.load(new URLRequest("./html/air2jsTest.html"));
      }

      private function onComplete(event:Event):void {
        //htmlLoader.window.JS関数名(引数)
        var response:String = htmlLoader.window.jsFunc("http://www.google.com/");
        trace(response);
      }
    ]]>
  </fx:Script>

</s:WindowedApplication>

src/html/air2jsTest.html

<html>
<head>
<title>air2jsTest</title>
  <script type="text/javascript" src="../js/xmlhttpreq.js"></script>
</head>
<body>
</body>
</html>

src/js/xmlhttpreq.js

function jsFunc(url) {
  var req = new XMLHttpRequest();
  req.open("GET", url, false);
  req.send(null);
  return req.responseText;
}

 

パラメータつけてPOST

リクエストのときのパラメータはObjectに入れてURLRequest#dataに渡すのかと思ってたら違ったのでメモ。

var req:URLRequest = new URLRequest("http://example.com");
req.method = URLRequestMethod.POST;
var params:URLVariables = new URLVariables();  //これを使う
params["onigiri"] = "ume";
params.otya = "iemon";    //こうでもOK
req.data = params;

var loader:URLLoader = new URLLoader();
loader.load(req);

AIRでAirRecord越しにSQLiteを使う

AIRでローカルDBのSQLiteを使う際に、直接使わずにO/Rマッパーを通して使ってみようということで、AirRecord を使ってみました。

Adobe Developer Connection の記事 を見ながらサンプルコードを書いてみました。

DBConfig.as

package  {
   public class DBConfig {
      /** ここに指定した文字列 + ".db" というファイル名でdbファイルが作成される。*/
      public static const DB_NAME:String = "myDB";

      /** .dbファイルを置くパス */
      public static const PATH:String = "app-storage:/";

      /** DB接続ユーザー名 */
      public static const USER:String = "hogehoge";

      /** DB接続に使用するパスワード */
      public static const PASSWORD:String = "hugahuga";

      /** 接続時に実行させるクエリ  配列要素順に実行 */
      public static const DDL:Array = [
         Bookmarks.DDL
      ];

      public function DBConfig() {
      }
   }
}

Bookmarks.as

package  {
   import jp.cre8system.framework.airrecord.model.ARModel;

   public class Bookmarks extends ARModel {
      /** 対応させるテーブル名 */
      public static const TABLE_NAME:String = "bookmarks";

      /** テーブル定義 */
      public static const DDL:String =
            "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" + 
            "   id         INTEGER PRIMARY KEY, " +
            "   name   TEXT, " + 
            "   url      TEXT" +
            ");";

      public var id:Number;
      public var name:String = "";
      public var url:String = "";

      public function Bookmarks() {
         super();
         this.__table = TABLE_NAME;   //テーブル名を渡す
      }
   }
}

AirRecordText.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
                  xmlns:s="library://ns.adobe.com/flex/spark" 
                  xmlns:mx="library://ns.adobe.com/flex/mx" 
                  creationComplete="init(event);" 
                  fontFamily="Meiryo, MS PGothic, Osaka" 
                  showStatusBar="false">
   <s:layout>
      <s:VerticalLayout verticalAlign="middle"/>
   </s:layout>

   <fx:Script>
      <![CDATA[
         import jp.cre8system.framework.airrecord.db.ARDatabase;
         import mx.collections.ArrayCollection;

         [Bindable]
         public var selectedData:ArrayCollection = null;   //SELECTしてきたデータの格納先

         private function init(event:Event):void {
            //DBへの接続
            var db:ARDatabase = ARDatabase.instance;
            db.add(
               DBConfig.USER, 
               DBConfig.PASSWORD, 
               DBConfig.DB_NAME, 
               DBConfig.DDL, 
               "",    //コネクション識別名
               DBConfig.PATH
            );
            db.connect();

            selectData();
         }

         //SELECTしてきてDataGridに渡す
         private function selectData():void {
            var bookmarks:Bookmarks = new Bookmarks();
            var result:Array = bookmarks.find(null, "id DESC");   //findメソッドでSELECTできる
            selectedData = new ArrayCollection(result);
         }

         //データ追加
         private function insertData(name:String, url:String):void {
            var bookmarks:Bookmarks = new Bookmarks();
            bookmarks.name = nameInput.text;
            bookmarks.url = urlInput.text;
            bookmarks.insert();
            //bookmarksのフィールドに代入してinsert()を呼ばずに
            //bookmarks.insert({name: nameInput.text, url:urlInput.text});
            //でもOK
         }

         //追加ボタンクリック時
         private function onClick(event:MouseEvent):void {
            if (nameInput.text == "" || urlInput.text == "") return;
            insertData(nameInput.text, urlInput.text);
            selectData();
         }
      ]]>
   </fx:Script>

   <s:HGroup width="100%" horizontalAlign="center" verticalAlign="middle">
      <s:Label text="サイト名" width="80" textAlign="right" paddingRight="5" />
      <s:TextInput id="nameInput" width="400"/>
   </s:HGroup>
   <s:HGroup width="100%" horizontalAlign="center" verticalAlign="middle">
      <s:Label text="URL" width="80" textAlign="right" paddingRight="5" />
      <s:TextInput id="urlInput" width="400"/>
   </s:HGroup>
   <s:HGroup width="100%" horizontalAlign="center" verticalAlign="middle">
      <mx:Spacer width="410" />
      <s:Button id="addButton" label="追加" click="onClick(event);" />
   </s:HGroup>

   <mx:Spacer width="100" height="50"/>

   <s:HGroup width="100%" horizontalAlign="center">
      <mx:Spacer width="30" />
      <mx:DataGrid width="450" dataProvider="{selectedData}">
         <mx:columns>
            <mx:DataGridColumn headerText="id" dataField="id" width="40"/>
            <mx:DataGridColumn headerText="name" dataField="name"/>
            <mx:DataGridColumn headerText="url" dataField="url"/>
         </mx:columns>
      </mx:DataGrid>
   </s:HGroup>

</s:WindowedApplication>

DB使ってるって気がしなくて楽ちんですね。

アプリケーションディスクリプタを読む手順

バージョンチェックのために、アプリケーションディスクリプタ(アプリケーション名-app.xml)から現在使っているバージョンを取得したい。そんなときは、

var xml:XML = NativeApplication.nativeApplication.applicationDescriptor;
var ns:Namespace = xml.namespace();    //名前空間取得
var version:String = xml.ns::version;  //名前空間を指定して要素にアクセス

で取得できるみたいです。

名前空間のついたXMLを読むのはちょっと面倒ですね。

関連付けられているアプリケーションでファイルを開く

AIRアプリケーションから、例えばユーザーのデスクトップのディレクトリをエクスプローラで開こうと思ったら NativeProcess を使ってexplorer.exe を呼ぶしかないのかなと思っていました。

しかしそんなことはありませんでした。

var dir:File = File.desktopDirectory;
dir.openWithDefaultApplication();

完全に灯台下暗し! もっとよく入力補完のところでも見とくべきでした。

AIRアプリケーションで自動アップデート通知を出す

まずアップデート通知に使うxmlを用意する。

仮に update.xml とする

<?xml version="1.0" encoding="utf-8"?>
<update xmlns="http://ns.adobe.com/air/framework/update/description/1.0">
	<version>1.0.2</version>
	<url>http://www.example.com/hoge.air</url>
	<description><![CDATA[
ほげほげ
	]]></description>
</update>

あとは air.update.ApplicationUpdaterUI を使って update.xml をチェックしに行けばいいらしい。

private var updater:ApplicationUpdaterUI = new ApplicationUpdaterUI();

//creationComplete とかで呼ぶ
private function checkUpdate():void {
	updater.updateURL = "http://www.example.com/update.xml";
	updater.addEventListener(UpdateEvent.INITIALIZED, function(event:UpdateEvent):void {
		updater.checkNow();
	});
	updater.addEventListener(ErrorEvent.ERROR, function(event:ErrorEvent):void {
		trace(event.toString());
	});
	updater.isCheckForUpdateVisible = false;
	updater.initialize();
}

これでいいらしい。

NativeMenuの使い方

var menu:NativeMenu = new NativeMenu();                 //入れ物
var item1:NativeMenuItem = new NativeMenuItem("hoge");  //アイテム
var item2:NativeMenuItem = new NativeMenuItem("fuga");

menu.addItem(item1);
menu.addItem(new NativeMenuItem("", true));  //仕切り
menu.addItem(item2);

と準備しておいて、あとは NativeWindow.menu に menu を渡してウィンドウのメニューとして使ったり、マウスイベントなんかで menu.display(stage, event.stageX, event.stageY) を呼んでポップアップ表示させたりして使えばOK。他にはシステムトレイのアイコンクリック時のメニューもこれで。

ByteArray.readBytes と ByteArray.writeBytes

その前に FLVの分割方法を調べ中 の続き。

前回書いた通り、

  1. 適当な長さのByteArrayからキーフレームを探し出して
  2. キーフレームになっている FLV Tag(Video) から、終わりにしたい場所にあるキーフレームの直前の FLV Tag までを抜き出して
  3. 抜き出してきたByteArrayの先頭にFLV Header(46 4C 56 01 05 00 00 00 09) + FLV Stream(00 00 00 00)を書き足して
  4. ファイルに書き出し。

という感じで動くものができあがりました。

1つのFLVを4つくらいに分割したすると、2つ目の分割ByteArray以降は 2. の処理の後に FLV Tag のtimestamp(再生開始ミリ秒)の値を書き換える処理が入るくらいです。

今後の課題:

FLV Tag(Meta) を生成して、分割したファイルそれぞれに入れるようにする。

メモリの使い方をもうちょっと考えてコードを修正する。

分割されているファイルを1つに結合する処理も作る。

対応ファイルフォーマットを増やす。


で、これを書いているときに readBytes とか writeBytes をたくさん使いまして、ごちゃごちゃやっているうちに軽く混乱したのでまとめておきます。

var data:ByteArray = new ByteArray();
data.writeInt(0xAABBCCDD);
data.position = 0;
var ba:ByteArray = new ByteArray();

こういうコードがあったとして、data の値を ba にコピーしたいときに

data.readBytes(ba);

または

ba.writeBytes(data);

でコピーできます。

それでなんですが、このときにどちらを使うかをさして意識せずに使っているうちに data.position や ba.position の値の変わり方に混乱してしまいました。

data.readBytes(ba);  //data.position 変わる。 ba.position 変わらない。
ba.writeBytes(data);  //ba.position 変わる。 data.position 変わらない。

ちょっと落ち着いて考えれば、メソッドを持っているオブジェクトのpositionプロパティが変わるのはそうだよなと納得できますし、引数の方も省略なしの ByteArray.xxxBytes(bytes:ByteArray, offset:uint=0, length:uint=0) を眺めれば変わらないだろうなと予測できるんですけどね。

今後は気にしながら書くのでもう大丈夫だと思いますが、また混乱とか勘違いしながら書いてたらバグる!ということでメモしておきます。

URLStreamでダウンロードしてきたflvをNetStreamで再生する方法

URLStreamでflvをダウンロードしつつ、そのバイトデータをNetStream.appendBytes()に逐一渡して再生した場合には動画のシークができなくなってしまう。なので、その現象をなんとか回避する方法を模索中。

 

回避案1:

NetStream.appendBytes()は使わずに、ダウンロードしたデータをちょこちょこファイルに書き出してそのファイルパスをNetStream.play()に渡して再生する方法

  1. URLStreamでflvをダウンロード。
  2. 程よいバイト数読みこんだところでファイルに書き出し。
  3. 書き出したファイルパスをNetStream.play()に渡して再生開始。
  4. 更に程よいところまで読みこんだら再びファイルに書き出す…が、2.で書き出したファイルに追加で書き込みたいので、ファイルのロックを解放するためにファイルを利用中のNetStreamを一旦close()する。close()前にNetStream.timeの値(現在再生中の位置(秒))を取り出しておく。
  5. NetStream.close()してFileMode.APPENDで書きこんだら、3.と同じようにNetStream.play()にファイルパスを渡して再生再開。
  6. NetStream.seek()にclose前に取りだしたNetStream.timeの値を渡して再生位置を復元。
  7. 以下flvのダウンロードが完了するまで4~6の繰り返し。

問題点:

6.でseek()するが、このときにシークが可能な位置はseek()に渡した秒数(以降?)にある最も近いキーフレームになるらしい。なので4→6の部分で再生が滑らかに繋がらない。

 

キーフレームのある秒数を取得する方法があればだいぶ滑らかに繋げられそう…。

ひとまずこの回避案は置いておいて別の方法を模索中。