AVAssetResourceLoaderを使ったiOSオーディオストリーミング

AVAssetResourceLoaderを使ったiOSオーディオストリーミング

Artem Meleshko
Artem Meleshko Co-Founder & iOS Engineer at Everappz

TL;DR: カスタムURLスキームと組み合わせたAVAssetResourceLoaderDelegateを使用して、AVPlayerのリソース読み込みをインターセプトします。これにより、クラウドサービスへのカスタム認証ヘッダーの追加、オーディオのディスクへのキャッシュ、ストリーミング動作の制御が可能になります – ローカルHTTPプロキシを書かずに済みます。完全なソースコードはGitHubで公開されています。


なぜAVPlayerにカスタムリソースローダーが必要なのか

AVPlayerはローカルファイルとリモートURLからオーディオを再生します。ほとんどのクラウドサービス(Dropbox、Google Drive、Box)では、直接ダウンロードURLを渡すだけで再生がすぐに動作します。

しかし、Yandex.DiskWebDAVなどの一部のサービスは、GETリクエストにカスタム認証ヘッダーを必要とします。AVPlayerにはこれらのヘッダーを注入する組み込みの方法がありません。

解決策: AVURLAssetresourceLoaderプロパティを使用します。このAPIはリソース読み込みリクエストをインターセプトし、複雑さなしにローカルHTTPプロキシのように機能します。

仕組み

AVPlayerはURLスキームを認識しない場合にresourceLoaderを使用します。https://をカスタムスキーム(例: customscheme://)に置き換えることで、AVPlayerがすべての読み込みをアプリに委任するよう強制できます。

2つのAVAssetResourceLoaderDelegateメソッドを実装する必要があります:

  1. resourceLoader:shouldWaitForLoadingOfRequestedResource: – AVPlayerがデータを必要とするときに呼び出されます。AVAssetResourceLoadingRequestを保存し、データ読み込み操作を開始します。
  2. resourceLoader:didCancelLoadingRequest: – リクエストがキャンセルまたは上書きされたときに呼び出されます。

カスタムAVPlayerの作成方法

カスタムURLスキームでAVPlayerをセットアップします:

NSURL *url = [NSURL URLWithString:@"customscheme://host/audio.mp3"];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
[asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()];
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:asset];
[self addObserversForPlayerItem:item];
self.player = [AVPlayer playerWithPlayerItem:item];
[self addObserversForPlayer];

このコードは:

  • カスタムスキームでURLを定義します
  • メインキューにデリゲートを持つAVURLAssetを作成します
  • アセットからAVPlayerItemを構築します
  • AVPlayerを初期化します

リソースローダーデリゲートの実装

サーバーからのデータ取得を処理し、AVURLAssetに返すためのLSFilePlayerResourceLoaderというクラスを作成します。リソースURLをキーとしてローダーインスタンスをディクショナリに保存します。

- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {
    NSURL *resourceURL = [loadingRequest.request URL];
    if ([resourceURL.scheme isEqualToString:@"customscheme"]) {
        LSFilePlayerResourceLoader *loader = [self resourceLoaderForRequest:loadingRequest];
        if (!loader) {
            loader = [[LSFilePlayerResourceLoader alloc] initWithResourceURL:resourceURL session:self.session];
            loader.delegate = self;
            [self.resourceLoaders setObject:loader forKey:[self keyForResourceLoaderWithURL:resourceURL]];
        }
        [loader addRequest:loadingRequest];
        return YES;
    }
    return NO;
}

- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {
    LSFilePlayerResourceLoader *loader = [self resourceLoaderForRequest:loadingRequest];
    [loader removeRequest:loadingRequest];
}

これらのメソッドはURLスキームを確認し、ローダーを作成または取得し、リクエストをローダーのキューに追加します。未知のスキームはNOを返します。

LSFilePlayerResourceLoaderインターフェース

@interface LSFilePlayerResourceLoader : NSObject
@property (nonatomic, readonly, strong) NSURL *resourceURL;
@property (nonatomic, readonly) NSArray *requests;
@property (nonatomic, readonly, strong) YDSession *session;
@property (nonatomic, readonly, assign) BOOL isCancelled;
@property (nonatomic, weak) id<LSFilePlayerResourceLoaderDelegate> delegate;
- (instancetype)initWithResourceURL:(NSURL *)url session:(YDSession *)session;
- (void)addRequest:(AVAssetResourceLoadingRequest *)loadingRequest;
- (void)removeRequest:(AVAssetResourceLoadingRequest *)loadingRequest;
- (void)cancel;
@end

@protocol LSFilePlayerResourceLoaderDelegate <NSObject>
@optional
- (void)filePlayerResourceLoader:(LSFilePlayerResourceLoader *)resourceLoader didFailWithError:(NSError *)error;
- (void)filePlayerResourceLoader:(LSFilePlayerResourceLoader *)resourceLoader didLoadResource:(NSURL *)resourceURL;
@end

データ読み込み: 2ステップのプロセス

読み込みリクエストがキューに入ると、2つの操作が順番に実行されます:

  • contentInfoOperation – コンテンツの長さ、MIMEタイプ、バイト範囲のサポートを照会します
  • dataOperationrequestedOffsetから始まるファイルデータを取得します

ディスクキャッシュ戦略

ダウンロードされたデータはディスク上の一時ファイルに書き込まれます。同じコンテンツへのその後のリクエストはこのキャッシュから提供され、冗長なネットワーク呼び出しを避けます。このアプローチは:

  • 帯域幅の使用量を削減します
  • ほぼ即時の再生を可能にします
  • キャッシュされた範囲内のシーク操作をサポートします

保留中のリクエストの処理

processPendingRequestsメソッドは各リクエストのcontentInformationRequestにメタデータを入力し、キャッシュされたバイト範囲を配信します。完了したリクエストはキューから削除されます。

ソースコードと次のステップ

このチュートリアルでは、カスタム認証ヘッダーを使用したクラウドオーディオストリーミングのためのAVAssetResourceLoaderDelegate実装の概要を説明しました。完全なソースコードはGitHubで公開されています。

このアプローチは、iOSおよびmacOSでDropbox、Google Drive、OneDrive、Yandex.Diskなどのクラウドサービスから音楽をストリーミングするEvermusicのオーディオストリーミングエンジンを動かしています。

よくある質問

直接URLの代わりにAVAssetResourceLoaderDelegateを使うのはいつですか?
クラウドサービスがカスタム認証ヘッダーを必要とする場合、ストリーミングされたオーディオのディスクキャッシュが必要な場合、またはデータの読み込みとバッファリングの方法を細かく制御したい場合に使用します。
このアプローチはSwiftでも動作しますか?
はい。AVAssetResourceLoaderDelegateプロトコルはSwiftでも同じように動作します。ここにあるObjective-Cの例はそのまま翻訳できます。
ビデオストリーミングにも使えますか?
はい。AVAssetResourceLoaderDelegateはビデオを含むAVPlayerがサポートするあらゆるメディアタイプで動作します。同じカスタムスキームアプローチが適用されます。
バックグラウンドでのオーディオ再生はサポートされていますか?
はい。アプリの機能で「Audio, AirPlay, and Picture in Picture」バックグラウンドモードを有効にし、AVAudioSessionを正しく設定する限り、サポートされます。
最終更新日