סטרימינג אודיו ב-iOS עם AVAssetResourceLoader

סיכום קצר: השתמשו ב-AVAssetResourceLoaderDelegate עם סכמת URL מותאמת אישית כדי ליירט את טעינת המשאבים של AVPlayer. זה מאפשר לכם להוסיף כותרות הרשאה מותאמות לשירותי ענן, לשמור אודיו במטמון על הדיסק ולשלוט בהתנהגות הסטרימינג – הכל ללא כתיבת פרוקסי HTTP מקומי. קוד המקור המלא זמין ב-GitHub.
מדוע AVPlayer זקוק לטוען משאבים מותאם
AVPlayer מנגן אודיו מקבצים מקומיים ו-URL מרוחקות. עבור רוב שירותי הענן (Dropbox, Google Drive, Box), ניתן להעביר URL הורדה ישיר והניגון עובד מיד.
אולם, שירותים כגון Yandex.Disk ו-WebDAV דורשים כותרות הרשאה מותאמות בבקשות GET. AVPlayer אינו מספק דרך מובנית להזרקת כותרות אלה.
הפתרון: השתמשו במאפיין resourceLoader של AVURLAsset. ממשק API זה מיירט בקשות טעינת משאבים ופועל כפרוקסי HTTP מקומי ללא המורכבות.
כיצד זה עובד
AVPlayer משתמש ב-resourceLoader כאשר הוא אינו מזהה את סכמת ה-URL. על ידי החלפת https:// בסכמה מותאמת (למשל customscheme://), אתם מאלצים את AVPlayer להאציל את כל הטעינה לאפליקציה שלכם.
עליכם לממש שתי שיטות של AVAssetResourceLoaderDelegate:
resourceLoader:shouldWaitForLoadingOfRequestedResource:– נקראת כאשר AVPlayer זקוק לנתונים. שמרו את ה-AVAssetResourceLoadingRequestוהתחילו את פעולת טעינת הנתונים שלכם.resourceLoader:didCancelLoadingRequest:– נקראת כאשר בקשה מבוטלת או מוחלפת.
כיצד ליצור AVPlayer מותאם אישית
הגדירו AVPlayer עם סכמת 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];קוד זה:
- מגדיר URL עם הסכמה המותאמת שלכם
- יוצר
AVURLAssetעם דלגט בתור הראשי - בונה
AVPlayerItemמה-asset - מאתחל את
AVPlayer
מימוש דלגט טוען המשאבים
צרו מחלקה בשם LSFilePlayerResourceLoader לטיפול בשליפת הנתונים מהשרת והעברתם ל-AVURLAsset. אחסנו מופעי טוען במילון שמפתחותיו הם 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טעינת נתונים: תהליך דו-שלבי
כאשר בקשת טעינה נכנסת לתור, שתי פעולות מתבצעות ברצף:
- contentInfoOperation – מבצעת שאילתה על אורך התוכן, סוג MIME ותמיכה בטווחי בתים
- dataOperation – שולפת נתוני קובץ החל מה-
requestedOffset
אסטרטגיית מטמון על הדיסק
הנתונים שהורדו נכתבים לקובץ זמני על הדיסק. בקשות עוקבות לאותו תוכן מוגשות מהמטמון, תוך הימנעות מקריאות רשת מיותרות. גישה זו:
- מצמצמת שימוש ברוחב פס
- מאפשרת ניגון חוזר כמעט מיידי
- תומכת בפעולות דילוג בתוך טווחים שנשמרו במטמון
עיבוד בקשות ממתינות
המתודה processPendingRequests ממלאת את ה-contentInformationRequest של כל בקשה במטה-נתונים ומספקת טווחי בתים שנשמרו במטמון. בקשות שהושלמו מוסרות מהתור.
קוד מקור וצעדים הבאים
מדריך זה מספק סקירה כללית של מימוש AVAssetResourceLoaderDelegate לסטרימינג אודיו בענן עם כותרות הרשאה מותאמות. קוד המקור המלא זמין ב-GitHub.
גישה זו מניעה את מנוע הסטרימינג של האודיו ב-Evermusic, המסטרים מוזיקה מ-Dropbox, Google Drive, OneDrive, Yandex.Disk ושירותי ענן נוספים ב-iOS וב-macOS.
שאלות נפוצות
מתי להשתמש ב-AVAssetResourceLoaderDelegate במקום URL ישיר?
האם גישה זו עובדת עם Swift?
AVAssetResourceLoaderDelegate עובד באותו אופן ב-Swift. דוגמאות Objective-C שכאן מתורגמות ישירות.
האם ניתן להשתמש בזה גם לסטרימינג וידאו?
AVAssetResourceLoaderDelegate עובד עם כל סוג מדיה שנתמך על ידי AVPlayer, כולל וידאו. אותה גישה עם סכמה מותאמת חלה גם כאן.
האם זה תומך בניגון אודיו ברקע?
AVAudioSession שלכם כראוי.