iOS Audio Streaming med AVAssetResourceLoader

TL;DR: Använd AVAssetResourceLoaderDelegate med ett anpassat URL-schema för att avlyssna AVPlayers resursinläsning. Detta låter dig lägga till anpassade auktoriseringsrubriker för molntjänster, cacha ljud till disk och styra streamingbeteendet – allt utan att skriva en lokal HTTP-proxy. Fullständig källkod finns på GitHub.
Varför AVPlayer behöver en anpassad resource loader
AVPlayer spelar upp ljud från lokala filer och fjärr-URL:er. För de flesta molntjänster (Dropbox, Google Drive, Box) kan du ange en direkt nedladdnings-URL och uppspelningen fungerar direkt.
Vissa tjänster som Yandex.Disk och WebDAV kräver dock anpassade auktoriseringsrubriker på GET-förfrågningar. AVPlayer erbjuder inget inbyggt sätt att injicera dessa rubriker.
Lösningen: använd egenskapen resourceLoader hos AVURLAsset. Detta API avlyssnar resursinläsningsförfrågningar och fungerar som en lokal HTTP-proxy utan komplexiteten.
Hur det fungerar
AVPlayer använder resourceLoader när det inte känner igen URL-schemat. Genom att ersätta https:// med ett anpassat schema (t.ex. customscheme://) tvingar du AVPlayer att delegera all inläsning till din app.
Du behöver implementera två AVAssetResourceLoaderDelegate-metoder:
resourceLoader:shouldWaitForLoadingOfRequestedResource:– anropas när AVPlayer behöver data. SparaAVAssetResourceLoadingRequestoch starta din datainläsningsoperation.resourceLoader:didCancelLoadingRequest:– anropas när en förfrågan avbryts eller ersätts.
Hur man skapar en anpassad AVPlayer
Konfigurera en AVPlayer med ett anpassat URL-schema:
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];Den här koden:
- Definierar en URL med ditt anpassade schema
- Skapar en
AVURLAssetmed en delegat på huvudkön - Bygger ett
AVPlayerItemfrån tillgången - Initierar
AVPlayer
Implementera resource loader-delegaten
Skapa en klass som heter LSFilePlayerResourceLoader för att hantera datahämtning från servern och skicka tillbaka den till AVURLAsset. Lagra loader-instanser i en ordbok med resurs-URL som nyckel.
- (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];
}Dessa metoder kontrollerar URL-schemat, skapar eller hämtar en loader och lägger till förfrågan i loaderns kö. Okända scheman returnerar NO.
LSFilePlayerResourceLoader-gränssnittet
@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;
@endDatainläsning: tvåstegsprocess
När en inläsningsförfrågan kommer in i kön utförs två operationer i sekvens:
- contentInfoOperation – frågar efter innehållslängd, MIME-typ och stöd för byte-intervall
- dataOperation – hämtar fildata med start från
requestedOffset
Diskcachningsstrategi
Nedladdade data skrivs till en temporär fil på disk. Efterföljande förfrågningar om samma innehåll hanteras från detta cacheminne, vilket undviker onödiga nätverksanrop. Detta tillvägagångssätt:
- Minskar bandbreddsanvändningen
- Möjliggör nästan omedelbar uppspelning igen
- Stöder sökoperationer inom cachade intervall
Hantering av väntande förfrågningar
Metoden processPendingRequests fyller varje förfrågans contentInformationRequest med metadata och levererar cachade byte-intervall. Slutförda förfrågningar tas bort från kön.
Källkod och nästa steg
Den här handledningen ger en överblick av hur man implementerar AVAssetResourceLoaderDelegate för moln-ljud-streaming med anpassade auktoriseringsrubriker. Den fullständiga källkoden finns på GitHub.
Det här tillvägagångssättet driver ljud-streaming-motorn i Evermusic, som streamar musik från Dropbox, Google Drive, OneDrive, Yandex.Disk och andra molntjänster på iOS och macOS.
Vanliga frågor
När ska jag använda AVAssetResourceLoaderDelegate istället för en direkt URL?
Fungerar det här tillvägagångssättet med Swift?
AVAssetResourceLoaderDelegate fungerar på samma sätt i Swift. Objective-C-exemplen här översätts direkt.
Kan jag använda detta för video-streaming också?
AVAssetResourceLoaderDelegate fungerar med alla medietyper som AVPlayer stöder, inklusive video. Samma anpassade schema-metod gäller.
Stöder detta bakgrundsljuduppspelning?
AVAudioSession korrekt.