iOS-Audio-Streaming mit AVAssetResourceLoader

TL;DR: Verwenden Sie AVAssetResourceLoaderDelegate mit einem benutzerdefinierten URL-Schema, um das Laden von Ressourcen durch AVPlayer abzufangen. Damit können Sie benutzerdefinierte Autorisierungs-Header für Cloud-Dienste hinzufugen, Audio auf der Festplatte zwischenspeichern und das Streaming-Verhalten steuern – ohne einen lokalen HTTP-Proxy schreiben zu mussen. Der vollstandige Quellcode ist auf GitHub verfugbar.
Warum AVPlayer einen benutzerdefinierten Resource Loader benotigt
AVPlayer spielt Audio aus lokalen Dateien und Remote-URLs ab. Bei den meisten Cloud-Diensten (Dropbox, Google Drive, Box) können Sie eine direkte Download-URL übergeben und die Wiedergabe funktioniert sofort.
Einige Dienste wie Yandex.Disk und WebDAV erfordern jedoch benutzerdefinierte Autorisierungs-Header bei GET-Anfragen. AVPlayer bietet keine integrierte Moglichkeit, diese Header einzufügen.
Die Losung: Verwenden Sie die resourceLoader-Eigenschaft von AVURLAsset. Diese API fängt Ressourcen-Ladeanfragen ab und fungiert wie ein lokaler HTTP-Proxy ohne die Komplexität.
Funktionsweise
AVPlayer verwendet resourceLoader, wenn es das URL-Schema nicht erkennt. Indem Sie https:// durch ein benutzerdefiniertes Schema ersetzen (z. B. customscheme://), zwingen Sie AVPlayer, das gesamte Laden an Ihre App zu delegieren.
Sie mussen zwei AVAssetResourceLoaderDelegate-Methoden implementieren:
resourceLoader:shouldWaitForLoadingOfRequestedResource:– wird aufgerufen, wenn AVPlayer Daten benotigt. Speichern Sie dasAVAssetResourceLoadingRequestund starten Sie Ihren Datenladevorgang.resourceLoader:didCancelLoadingRequest:– wird aufgerufen, wenn eine Anfrage abgebrochen oder uberholt wurde.
So erstellen Sie einen benutzerdefinierten AVPlayer
Richten Sie einen AVPlayer mit einem benutzerdefinierten URL-Schema ein:
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];Dieser Code:
- Definiert eine URL mit Ihrem benutzerdefinierten Schema
- Erstellt ein
AVURLAssetmit einem Delegate im Hauptthread - Erstellt ein
AVPlayerItemaus dem Asset - Initialisiert
AVPlayer
Implementierung des Resource Loader Delegate
Erstellen Sie eine Klasse namens LSFilePlayerResourceLoader, um Daten vom Server abzurufen und an AVURLAsset zurückzugeben. Speichern Sie Loader-Instanzen in einem Dictionary mit der Ressourcen-URL als Schlüssel.
- (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];
}Diese Methoden prüfen das URL-Schema, erstellen oder rufen einen Loader ab und fügen die Anfrage zur Warteschlange des Loaders hinzu. Nicht erkannte Schemata geben NO zurück.
LSFilePlayerResourceLoader-Schnittstelle
@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;
@endDatenladen: Zweistufiger Prozess
Wenn eine Ladeanfrage in die Warteschlange kommt, werden zwei Operationen nacheinander ausgefuhrt:
- contentInfoOperation – fragt Inhaltslaenge, MIME-Typ und Byte-Bereich-Unterstutzung ab
- dataOperation – ruft Dateidaten ab, beginnend vom
requestedOffset
Disk-Caching-Strategie
Heruntergeladene Daten werden in eine temporäre Datei auf der Festplatte geschrieben. Nachfolgende Anfragen für denselben Inhalt werden aus diesem Cache bedient, wodurch redundante Netzwerkaufrufe vermieden werden. Dieser Ansatz:
- Reduziert die Bandbreitennutzung
- Ermoglicht nahezu sofortige Wiedergaben
- Unterstutzt Suchoperationen innerhalb zwischengespeicherter Bereiche
Verarbeitung ausstehender Anfragen
Die Methode processPendingRequests fullt das contentInformationRequest jeder Anfrage mit Metadaten und liefert zwischengespeicherte Byte-Bereiche. Abgeschlossene Anfragen werden aus der Warteschlange entfernt.
Quellcode und nachste Schritte
Dieses Tutorial bietet einen allgemeinen Überblick uber die Implementierung von AVAssetResourceLoaderDelegate fur Cloud-Audio-Streaming mit benutzerdefinierten Autorisierungs-Headern. Der vollständige Quellcode ist auf GitHub verfügbar.
Dieser Ansatz betreibt die Audio-Streaming-Engine in Evermusic, das Musik von Dropbox, Google Drive, OneDrive, Yandex.Disk und anderen Cloud-Diensten auf iOS und macOS streamt.
Häufig gestellte Fragen
Wann sollte ich AVAssetResourceLoaderDelegate anstelle einer direkten URL verwenden?
Funktioniert dieser Ansatz mit Swift?
AVAssetResourceLoaderDelegate-Protokoll funktioniert in Swift auf die gleiche Weise. Die Objective-C-Beispiele hier lassen sich direkt ubertragen.
Kann ich dies auch fur Video-Streaming verwenden?
AVAssetResourceLoaderDelegate funktioniert mit jedem Medientyp, den AVPlayer unterstuzt, einschliesslich Video. Derselbe benutzerdefinierte Schema-Ansatz gilt.
Wird die Hintergrund-Audio-Wiedergabe unterstuzt?
AVAudioSession korrekt konfigurieren.