Streaming Audio iOS cu AVAssetResourceLoader

Streaming Audio iOS cu AVAssetResourceLoader

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

TL;DR: Utilizați AVAssetResourceLoaderDelegate cu o schemă URL personalizată pentru a intercepta încărcarea resurselor AVPlayer. Aceasta vă permite să adăugați antete de autorizare personalizate pentru serviciile cloud, să memorați audio pe disc și să controlați comportamentul de streaming – fără a scrie un proxy HTTP local. Codul sursă complet se află pe GitHub.


De ce AVPlayer are nevoie de un încărcător de resurse personalizat

AVPlayer redă audio din fișiere locale și URL-uri la distanță. Pentru majoritatea serviciilor cloud (Dropbox, Google Drive, Box), puteți transmite un URL de descărcare directă și redarea funcționează imediat.

Cu toate acestea, unele servicii precum Yandex.Disk și WebDAV necesită antete de autorizare personalizate la solicitările GET. AVPlayer nu oferă nicio modalitate integrată de a injecta aceste antete.

Soluția: utilizați proprietatea resourceLoader a AVURLAsset. Acest API interceptează solicitările de încărcare a resurselor, acționând ca un proxy HTTP local fără complexitatea asociată.

Cum funcționează

AVPlayer utilizează resourceLoader atunci când nu recunoaște schema URL. Înlocuind https:// cu o schemă personalizată (ex.: customscheme://), forțați AVPlayer să delege toată încărcarea aplicației dvs.

Trebuie să implementați două metode AVAssetResourceLoaderDelegate:

  1. resourceLoader:shouldWaitForLoadingOfRequestedResource: – apelată când AVPlayer are nevoie de date. Salvați AVAssetResourceLoadingRequest și începeți operațiunea de încărcare a datelor.
  2. resourceLoader:didCancelLoadingRequest: – apelată când o solicitare este anulată sau înlocuită.

Cum se creează un AVPlayer personalizat

Configurați un AVPlayer cu o schemă URL personalizată:

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];

Acest cod:

  • Definește un URL cu schema dvs. personalizată
  • Creează un AVURLAsset cu un delegate pe coada principală
  • Construiește un AVPlayerItem din asset
  • Inițializează AVPlayer

Implementarea delegatului încărcătorului de resurse

Creați o clasă numită LSFilePlayerResourceLoader pentru a gestiona preluarea datelor de pe server și transmiterea lor înapoi la AVURLAsset. Stocați instanțele încărcătorului într-un dicționar indexat după URL-ul resursei.

- (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];
}

Aceste metode verifică schema URL, creează sau recuperează un încărcător și adaugă solicitarea în coada încărcătorului. Schemele nerecunoscute returnează NO.

Interfața 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

Încărcarea datelor: proces în două etape

Când o solicitare de încărcare intră în coadă, două operațiuni se execută în secvență:

  • contentInfoOperation – interogă lungimea conținutului, tipul MIME și suportul pentru intervale de octeți
  • dataOperation – preia datele fișierului începând de la requestedOffset

Strategia de cache pe disc

Datele descărcate sunt scrise într-un fișier temporar pe disc. Solicitările ulterioare pentru același conținut sunt servite din acest cache, evitând apelurile de rețea redundante. Această abordare:

  • Reduce utilizarea lățimii de bandă
  • Permite reluări aproape instantanee
  • Suportă operațiuni de căutare în intervalele din cache

Procesarea solicitărilor în așteptare

Metoda processPendingRequests completează contentInformationRequest al fiecărei solicitări cu metadate și livrează intervalele de octeți din cache. Solicitările finalizate sunt eliminate din coadă.

Cod sursă și pașii următori

Acest tutorial oferă o prezentare generală a implementării AVAssetResourceLoaderDelegate pentru streaming audio din cloud cu antete de autorizare personalizate. Codul sursă complet este disponibil pe GitHub.

Această abordare alimentează motorul de streaming audio din Evermusic, care transmite muzică din Dropbox, Google Drive, OneDrive, Yandex.Disk și alte servicii cloud pe iOS și macOS.

Întrebări frecvente

Când ar trebui să folosesc AVAssetResourceLoaderDelegate în loc de un URL direct?
Utilizați-l atunci când serviciul cloud necesită antete de autorizare personalizate, când aveți nevoie de cache pe disc pentru audio transmis în flux sau când doriți un control detaliat asupra modului în care datele sunt încărcate și memorate în buffer.
Această abordare funcționează cu Swift?
Da. Protocolul AVAssetResourceLoaderDelegate funcționează în același mod în Swift. Exemplele din Objective-C se traduc direct.
Pot folosi aceasta și pentru streaming video?
Da. AVAssetResourceLoaderDelegate funcționează cu orice tip de media pe care AVPlayer îl suportă, inclusiv video. Aceeași abordare cu schemă personalizată se aplică.
Aceasta suportă redarea audio în fundal?
Da, atâta timp cât activați modul de fundal „Audio, AirPlay și Picture in Picture" în capabilitățile aplicației dvs. și configurați corect AVAudioSession.
Ultima actualizare la