Strumieniowanie audio iOS z AVAssetResourceLoader

TL;DR: Użyj AVAssetResourceLoaderDelegate z niestandardowym schematem URL, aby przechwytywać ładowanie zasobów przez AVPlayer. Pozwala to dodawać niestandardowe nagłówki autoryzacji dla usług chmurowych, buforować audio na dysku i kontrolować zachowanie strumieniowania — bez konieczności pisania lokalnego proxy HTTP. Pełny kod źródłowy dostępny jest na GitHub.
Dlaczego AVPlayer potrzebuje niestandardowego ładowarki zasobów
AVPlayer odtwarza audio z plików lokalnych i zdalnych adresów URL. W przypadku większości usług chmurowych (Dropbox, Google Drive, Box) możesz przekazać bezpośredni URL pobierania, a odtwarzanie działa od razu.
Jednak niektóre usługi, takie jak Yandex.Disk i WebDAV, wymagają niestandardowych nagłówków autoryzacji w żądaniach GET. AVPlayer nie zapewnia wbudowanego sposobu na ich wstrzyknięcie.
Rozwiązanie: użyj właściwości resourceLoader klasy AVURLAsset. Ten interfejs API przechwytuje żądania ładowania zasobów, działając jak lokalne proxy HTTP bez związanej z tym złożoności.
Jak to działa
AVPlayer używa resourceLoader, gdy nie rozpoznaje schematu URL. Zastępując https:// niestandardowym schematem (np. customscheme://), zmuszasz AVPlayer do delegowania całego ładowania do Twojej aplikacji.
Musisz zaimplementować dwie metody AVAssetResourceLoaderDelegate:
resourceLoader:shouldWaitForLoadingOfRequestedResource:– wywoływana, gdy AVPlayer potrzebuje danych. ZapiszAVAssetResourceLoadingRequesti uruchom operację ładowania danych.resourceLoader:didCancelLoadingRequest:– wywoływana, gdy żądanie zostanie anulowane lub zastąpione.
Jak stworzyć niestandardowy AVPlayer
Skonfiguruj AVPlayer z niestandardowym schematem URL:
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];Ten kod:
- Definiuje URL z Twoim niestandardowym schematem
- Tworzy
AVURLAssetz delegatem na głównej kolejce - Buduje
AVPlayerItemz zasobu - Inicjalizuje
AVPlayer
Implementacja delegata ładowarki zasobów
Utwórz klasę o nazwie LSFilePlayerResourceLoader do obsługi pobierania danych z serwera i przekazywania ich z powrotem do AVURLAsset. Przechowuj instancje ładowarki w słowniku kluczowanym przez URL zasobu.
- (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];
}Te metody sprawdzają schemat URL, tworzą lub pobierają ładowarkę i dodają żądanie do kolejki ładowarki. Nierozpoznane schematy zwracają NO.
Interfejs 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Ładowanie danych: proces dwuetapowy
Gdy żądanie ładowania trafia do kolejki, wykonywane są sekwencyjnie dwie operacje:
- contentInfoOperation – pobiera długość zawartości, typ MIME i obsługę zakresów bajtów
- dataOperation – pobiera dane pliku począwszy od
requestedOffset
Strategia buforowania na dysku
Pobrane dane są zapisywane do tymczasowego pliku na dysku. Kolejne żądania dotyczące tej samej zawartości są obsługiwane z tej pamięci podręcznej, co pozwala uniknąć zbędnych wywołań sieciowych. Takie podejście:
- Zmniejsza zużycie przepustowości
- Umożliwia niemal natychmiastowe ponowne odtwarzanie
- Obsługuje operacje przewijania w buforowanych zakresach
Przetwarzanie oczekujących żądań
Metoda processPendingRequests wypełnia contentInformationRequest każdego żądania metadanymi i dostarcza buforowane zakresy bajtów. Ukończone żądania są usuwane z kolejki.
Kod źródłowy i następne kroki
Ten poradnik przedstawia ogólne omówienie implementacji AVAssetResourceLoaderDelegate do strumieniowania audio z chmury z niestandardowymi nagłówkami autoryzacji. Pełny kod źródłowy dostępny jest na GitHub.
To podejście napędza silnik strumieniowania audio w Evermusic, który strumieniuje muzykę z Dropbox, Google Drive, OneDrive, Yandex.Disk i innych usług chmurowych na iOS i macOS.
Często zadawane pytania
Kiedy powinienem używać AVAssetResourceLoaderDelegate zamiast bezpośredniego URL?
Czy to podejście działa ze Swift?
AVAssetResourceLoaderDelegate działa w taki sam sposób w Swift. Przykłady w Objective-C przekładają się bezpośrednio.
Czy mogę tego używać również do strumieniowania wideo?
AVAssetResourceLoaderDelegate działa z każdym typem mediów obsługiwanym przez AVPlayer, w tym z wideo. To samo podejście z niestandardowym schematem ma zastosowanie.
Czy to obsługuje odtwarzanie audio w tle?
AVAudioSession.