ObjecTips

Swift & Objective-C で iOS とか macOS とか

AVAudioPlayer と AVPlayer の初期化時の注意点

AVAudioPlayer には NSURL で初期化を行う以下のメソッドがある。

- (instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError;


AVPlayer にも NSURL で初期化を行う以下のメソッドがある。

+ (id)playerWithURL:(NSURL *)URL;
- (instancetype)initWithURL:(NSURL *)URL;


一見この2つのメソッドの使い方に違いは無いようだが実はちょっとした落とし穴(といっても特定の状況下での)がある。
以下のように NSURL から各プレイヤーのインスタンスを生成してオーディオ再生を行うコードを書いてみる。

問題の現象

引数として渡す NSURL を以下のようアプリに組み込んだオーディオファイルから生成してみる。

NSURL *URL = [[NSBundle mainBundle] URLForResource:@"test" withExtension:@"aif"];

この NSURL を使ってオーディオ再生を行う場合どちらのプレイヤーも正常に再生される。
問題は無い。


次に以下のようにテンポラリのディレクトリに保存されているオーディオファイルから NSURL を生成してみる。

NSString *URLString = [NSTemporaryDirectory() stringByAppendingPathComponent:@"temp.aif"];
NSURL *URL = [NSURL URLWithString:URLString];

この NSURL を使ってオーディオ再生を行おうとすると AVAudioPlayer は再生されるが AVPlayer の方は再生されないという現象が起こる。

原因

原因は前者の NSURL と後者の NSURL との違いにある。
生成された NSURL のログ出力を確認すると前者は

file:///private/var/mobile/Containers/Bundle/Application/D4829216-D6F9-4670-B20E-0ECF7D74766B/Test.app/test.aif

後者は

/private/var/mobile/Containers/Data/Application/6B0B08BE-80E4-4901-B455-053719CA2326/tmp/temp.aif

となっていて、前者は fileURL で後者は fileURL ではないという違いがある。
この2つの違いは以下のメソッドで確認する事ができる。

@property (readonly, getter=isFileURL) BOOL fileURL; // Whether the scheme is file:; if [myURL isFileURL] is YES, then [myURL path] is suitable for input into NSFileManager or NSPathUtilities.

前者は URL.fileURL == YES で後者は URL.fileURL == NO となる。


もし AVAudioPlayerAVPlayer 同様に fileURL でないと再生不可であれば問題に気付きやすいが実際には以下の表のようになっていて、なぜか AVPlayer の方だけ再生できないという事で嵌ってしまった。

Playable for fileURL NOT fileURL
AVAudioPlayer YES YES
AVPlayer YES NO
解決策

AVAudioPlayer の方は内部で以下のようなコードで fileURL に変換してやっているのかも知れない。
ネットワーク上のオーディオファイルを直接読み込んで再生するケースもあるので、fileURL かどうかではなく scheme の有無で fileURL に変換するかどうかを決める。

if (!URL.scheme) {
    URL = [NSURL fileURLWithPath:URL.path];
}

AVPlayer に渡すURLをこの様に前処理してやるのも安全度は高まるが、それよりまず NSURL を生成する際のコードに気を付ける。
今回の場合

NSString *URLString = [NSTemporaryDirectory() stringByAppendingPathComponent:@"temp.aif"];
NSURL *URL = [NSURL URLWithString:URLString];

これを以下のように書き換え、元から正しく fileURL を生成して AVAudioPlayer に渡すようにする。

NSString *URLString = [NSTemporaryDirectory() stringByAppendingPathComponent:@"temp.aif"];
NSURL *URL = [NSURL fileURLWithPath:URLString];
まとめ

AVPlayer は fileURL でないパスのみのローカルファイルの NSURL を再生できない。
ローカルのファイルから NSURL を生成する時は fileURLWithPath: メソッドなどを用いて正しく fileURL を生成するようにする。