スマホ

CordovaアプリをiOS 8のiPhone実機で動作させると音が止まる現象

  • このエントリーをはてなブックマークに追加
  • Pocket
  • LINEで送る

最近、CordovaでiPhoneとAndroidアプリを開発しています。
CordovaはPhoneGapとも呼ばれていますが、ほとんど同じものです。

CordovaApache財団で管理しているオープンソースのプロジェクト名
PhoneGapAdobeの製品名ですね。

Cordovaは、HTML5とJavaScriptというWebアプリの技術だけで、iPhoneとAndroidのハイブリッドアプリが作れる!という夢のように聴こえるフレームワークです。
これで僕もスマホアプリ作れるぞーと思って、ウキウキしながら触ってみましたが・・・・、
実際に使ってみると色々とハマるハマる。(-_-;)

サクッと作って、サクッとアプリ公開・・・とは、なかなかいかないです。
簡単そうに見えて、実にやっかいですね。Cordovaってやつは!

言い挙げていけばキリがないですが、特にひどいなぁと思っている問題を今回メモしておきます。

まず簡単にMedia Pluginの使い方について

Cordovaで音を鳴らすには、Cordova-Media-Pluginというプラグインを一般的に使用します。

プラグインのインストールはこんな感じ。
cordova plugin add cordova-plugin-media

プラグインが提供するMediaクラスで音楽ファイルを開きます。

media = new Media (
  this.getPath() + "sounds/music.mp3",
  // success callback
  function () {
    console.log("playAudio():Audio Success");
  },
  // error callback
  function (err) {
    console.log("playAudio():Audio Error: " + err);
  }
);

第1引数がファイル名、第2引数がファイルオープンに成功したときのコールバック関数、第3引数がファイルオープンに失敗したときのコースバック関数です。

再生する。

media.play();

ポーズする。

media.pause();

ストップする。

media.stop();

とっても簡単ですね。

Androidでのループ再生テクニック

この一見とても簡単に見えるCordova-Media-Plugin、実際使う上ではちょっと面倒な点がいろいろあります。

例えば、音楽をループ再生させる場合。
iPhoneの場合ならば、

media.play(numberOfLoops:"infinite");

と書くだけでループ再生できます。

しかし、Androidの場合は、APIレベルでループ再生ができません。
仕方がないので、タイマーで100msくらいのインターバルで再生が終わったかどうかをチェックし続ける必要があります。

公式ドキュメントに記載されていますが、こんな感じです。

// Audio player
//
var my_media = new Media(src, onSuccess, onError);

// Mediaの再生ポジションを一定周期で確認する。(この場合は1秒周期)
var mediaTimer = setInterval(function () {
    // Mediaの再生ポジションを取得
    my_media.getCurrentPosition(
        // success callback
        function (position) {
            if (0 > position {
                // 再生が終了していたら、また再生する。
                console.log((position) + " sec");
                media.play();
            }
        },
        // error callback
        function (e) {
            console.log("Error getting pos=" + e);
        }
    );
}, 1000);

iOS 8以上の実機で発生する問題(バグ?)

で、このCordova-Media-Pluginを使って、やっと作ったiOSアプリ。
iPhoneシミュレータ上では問題なく動作するものの、iPhoneの実機では動作しない現象が起こります。

僕もよく詳しく把握してませんが、どうも条件として下記のときに問題が発生するようです。

  1. iOS8以上のとき
  2. 複数のMediaインスタンスを使って音楽を同時再生させているとき
  3. ループ再生ではない音楽(効果音など)が再生し終わったとき

ようは、BGMを鳴らしながら、効果音を鳴らす、というよくあるシチューエーションですね。
この条件が重なったタイミングで、下記のエラーメッセージとともに、オーディオIOが無効化されて、再生中の全ての音楽が止まります。

ERROR:     [0x198425310] AVAudioSession.mm:646: -[AVAudioSession setActive:withOptions:error:]: Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session.

media.stop()で手動で音楽を止めるとこの問題は発生しません。

ある音楽ファイルが最後まで再生し終わって、自動的に再生が止まるとったその瞬間、この問題が発生します。

iOS8からオーディオ周りの仕様が変わったことに起因しているようです。(僕はiOS SDKとかObjective Cとか全然知らないので、はっきりとした原因は理解していません)

とりあえず、下記の対策で解決することがわかりました。

【解決策】Media Pluginがビルド時に出力するObjective Cソースを編集する

- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer*)player successfully:(BOOL)flag
{
    CDVAudioPlayer* aPlayer = (CDVAudioPlayer*)player;
    NSString* mediaId = aPlayer.mediaId;
    CDVAudioFile* audioFile = [[self soundCache] objectForKey:mediaId];
    NSString* jsString = nil;

    if (audioFile != nil) {
        NSLog(@"Finished playing audio sample '%@'", audioFile.resourcePath);
    }
    if (flag) {
        audioFile.player.currentTime = 0;
        jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%d);", @"cordova.require('cordova-plugin-media.Media').onStatus", mediaId, MEDIA_STATE, MEDIA_STOPPED];
    } else {
        // jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"cordova.require('cordova-plugin-media.Media').onStatus", mediaId, MEDIA_ERROR, MEDIA_ERR_DECODE];
        jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('cordova-plugin-media.Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_DECODE message:nil]];
    }
    if (self.avSession) {
        [self.avSession setActive:NO error:nil];
    }
    [self.commandDelegate evalJs:jsString];
}

これの最後の部分を↓のようにコメントアウトする。

    //if (self.avSession) {
    //    [self.avSession setActive:NO error:nil];
    //}
    [self.commandDelegate evalJs:jsString];
}

とりあえずは、これで動作するようになります。

これを調べるのに、すごい時間かかりました。

しかし、いまはiOS9の時代となっているので、いまだにこの症状が残っているというのが、どうしてなのかなぁ。

  • このエントリーをはてなブックマークに追加
  • Pocket
  • LINEで送る

コメント

コメントを残す

*