MediaPlayerでANDROID_LOOP=trueメタデータ付きのoggファイルを再生すると、setLooping(false)にもかかわらずループしてしまいます。この問題(というか仕様)への対処方法を検索してもいい方法が見つからなかったため、自分で調べてみました。
対策としては、ANDROID_LOOP=trueのoggファイルを再生するときには、getDuration()で得た再生時間(ms)経過後にMediaPlayerの再生を停止するしかないようです。
問題はどうやってANDROID_LOOP=trueのoggファイルか判定するかです。
まずANDROID_LOOP=trueメタデータ付きのoggファイルをバイナリエディタで開いてみました。すると文字列で"ANDROID_LOOP=true"を確認することができます。
ちょっと強引な方法ですが、oggファイルを読み込んでその中に文字列"ANDROID_LOOP=true"が含まれているか確認すれば判定できそうです。
いくつかのoggファイルを確認したところ、"ANDROID_LOOP=true"が存在する場所はファイルによって異なっていたため、読み込むバイト数はあらかじめきめられません。ただしいずれのoggファイルも先頭付近にありましたので、とりあえず256バイト読み込んで判定してみます。
static public boolean isAndroidLoop(InputStream stream) {
try {
byte[] data = new byte[256];
stream.read(data);
String text = new String(data);
return text.contains("ANDROID_LOOP=true");
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
手持ちのoggファイルをいくつか試してみましたが、これで一応、ANDROID_LOOP=trueかどうか判定することができました。でも本当にこれでいいんでしょうか。
- ANDROID_LOOP=trueが256バイト以降にあったら正しく判定できない
- oggファイルのサイズが256バイト以下の小さいファイルであったら正しく判定できない
- そもそもこのやり方って正しいの?
まじめにやるには、oggファイルのフォーマットを理解して、フォーマットに従ってファイルを読み込む必要があります。フォーマットの詳細についてすべて理解する必要はありませんが(私も理解していません)、簡単に説明すると
- oggファイルは音声専用のファイルフォーマットではなく、movやaviのように動画や音声など複数のデータをまとめて格納するためのコンテナファイル
- oggファイルはページという単位でデータを格納する
そこでフォーマットにしたがってoggファイルを読み込んでみます。フォーマットは以下のサイトを参考にしました
本家のogg仕様
Mitsugu Oyama のソフトウェア倉庫
まず1ページ分のbyte配列を読み込む関数を作成します。
public static byte[] readPageBytes(InputStream stream) {
try {
DataInputStream dis = new DataInputStream(stream);
// 読み込めなかったらデータの終わりと判断する
byte[] capture_pattern = new byte[4];
if (dis.read(capture_pattern) == -1) return new byte[0];
if (capture_pattern[0]!=0x4f || capture_pattern[1]!=0x67 || capture_pattern[2]!=0x67 || capture_pattern[3]!=0x53) return null;
byte[] header = new byte[22];
dis.read(header);
byte page_segments = dis.readByte();
int segment_data_bytes = 0;
byte[] segment_table = new byte[page_segments];
int segment_table_int[] = new int[page_segments];
for(int i=0; i<page_segments; i++) {
segment_table[i] = dis.readByte();
segment_table_int[i] = segment_table[i] & 0xFF; //JavaではByteは符号付き
segment_data_bytes += segment_table_int[i];
}
byte[][] segment_data = new byte[page_segments][];
for(int i=0; i<page_segments; i++) {
segment_data[i] = new byte[segment_table_int[i]];
dis.read(segment_data[i]);
}
int result_index = 0;
int result_bytes = 4 + 22 + 1 + page_segments + segment_data_bytes;
byte[] result = new byte[result_bytes];
System.arraycopy(capture_pattern, 0, result, result_index, 4); result_index += 4;
System.arraycopy(header, 0, result, result_index, 22); result_index += 22;
result[result_index] = page_segments; result_index += 1;
System.arraycopy(segment_table, 0, result, result_index, page_segments); result_index += page_segments;
for(int i = 0; i<page_segments; i++) {
System.arraycopy(segment_data[i], 0, result, result_index, segment_table_int[i]); result_index += segment_table_int[i];
}
return result;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
この関数を以下のように 呼び出します。
static public boolean isAndroidLoop(InputStream stream) {
while(true) {
byte[] raw_page = readPageBytes(stream);
if (raw_page == null) break;
if (raw_page.length == 0) break;
String text = new String(raw_page);
if (text.contains("ANDROID_LOOP=true")) {
return true;
}
}
return false;
}