ExoPlayer מספק פונקציונליות להורדת מדיה להפעלה במצב אופליין. ברוב תרחישי השימוש, רצוי שההורדות יימשכו גם כשהאפליקציה פועלת ברקע. במקרים האלה, האפליקציה צריכה להיות מחלקת משנה של DownloadService ולשלוח פקודות לשירות כדי להוסיף, להסיר ולשלוט בהורדות. בתרשים הבא מוצגות המחלקות העיקריות שמשתתפות בתהליך.
-
DownloadService: עוטףDownloadManagerומעביר אליו פקודות. השירות מאפשר לאפליקציהDownloadManagerלהמשיך לפעול גם כשהיא ברקע. -
DownloadManager: מנהל כמה הורדות, טוען (ומאחסן) את המצבים שלהן מתוך (ואל)DownloadIndex, מתחיל ומפסיק הורדות על סמך דרישות כמו קישוריות לרשת וכו'. כדי להוריד את התוכן, חשבון הניהול בדרך כלל קורא את הנתונים שמורידים מ-HttpDataSourceוכותב אותם ב-Cache. -
DownloadIndex: שמירת מצבי ההורדות.
יצירת DownloadService
כדי ליצור DownloadService, יוצרים מחלקת משנה ומטמיעים את ה-methods המופשטים שלה:
-
getDownloadManager(): מחזירה את הערךDownloadManagerשבו צריך להשתמש. -
getScheduler(): מחזירהSchedulerאופציונלי, שיכול להפעיל מחדש את השירות כשמתקיימות הדרישות להמשך ההורדות בהמתנה. ExoPlayer מספק את ההטמעות הבאות:-
PlatformScheduler, שמשתמש ב-JobScheduler (גרסת ה-API המינימלית היא 21). אפשר לעיין במסמכי ה-Javadoc של PlatformScheduler כדי לראות את דרישות ההרשאות של האפליקציה. -
WorkManagerScheduler, שמשתמש ב-WorkManager.
-
-
getForegroundNotification(): מחזירה הודעה שתוצג כשהשירות פועל בחזית. אתם יכולים להשתמש ב-DownloadNotificationHelper.buildProgressNotificationכדי ליצור התראה בסגנון ברירת המחדל.
לבסוף, מגדירים את השירות בקובץ AndroidManifest.xml:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<application>
<service android:name="com.myapp.MyDownloadService"
android:exported="false"
android:foregroundServiceType="dataSync">
<!-- This is needed for Scheduler -->
<intent-filter>
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
</application>
דוגמה קונקרטית לכך אפשר לראות ב-DemoDownloadService וב-AndroidManifest.xml באפליקציית ההדגמה של ExoPlayer.
יצירת DownloadManager
קטע הקוד הבא מדגים איך ליצור מופע של DownloadManager, שאפשר להחזיר אותו על ידי getDownloadManager() ב-DownloadService:
Kotlin
// Note: This should be a singleton in your app. val databaseProvider = StandaloneDatabaseProvider(context) // A download cache should not evict media, so should use a NoopCacheEvictor. val downloadCache = SimpleCache(downloadDirectory, NoOpCacheEvictor(), databaseProvider) // Create a factory for reading the data from the network. val dataSourceFactory = DefaultHttpDataSource.Factory() // Choose an executor for downloading data. Using Runnable::run will cause each download task to // download data on its own thread. Passing an executor that uses multiple threads will speed up // download tasks that can be split into smaller parts for parallel execution. Applications that // already have an executor for background downloads may wish to reuse their existing executor. val downloadExecutor = Executor(Runnable::run) // Create the download manager. val downloadManager = DownloadManager(context, databaseProvider, downloadCache, dataSourceFactory, downloadExecutor) // Optionally, properties can be assigned to configure the download manager. downloadManager.requirements = requirements downloadManager.maxParallelDownloads = 3
Java
// Note: This should be a singleton in your app. databaseProvider = new StandaloneDatabaseProvider(context); // A download cache should not evict media, so should use a NoopCacheEvictor. downloadCache = new SimpleCache(downloadDirectory, new NoOpCacheEvictor(), databaseProvider); // Create a factory for reading the data from the network. dataSourceFactory = new DefaultHttpDataSource.Factory(); // Choose an executor for downloading data. Using Runnable::run will cause each download task to // download data on its own thread. Passing an executor that uses multiple threads will speed up // download tasks that can be split into smaller parts for parallel execution. Applications that // already have an executor for background downloads may wish to reuse their existing executor. Executor downloadExecutor = Runnable::run; // Create the download manager. downloadManager = new DownloadManager( context, databaseProvider, downloadCache, dataSourceFactory, downloadExecutor); // Optionally, setters can be called to configure the download manager. downloadManager.setRequirements(requirements); downloadManager.setMaxParallelDownloads(3);
דוגמה קונקרטית לכך אפשר לראות ב-DemoUtil באפליקציית ההדגמה.
הוספת הורדה
כדי להוסיף הורדה, יוצרים DownloadRequest ושולחים אותו לDownloadService. לגבי סטרימינג אדפטיבי, כדאי להשתמש ב-DownloadHelper כדי ליצור DownloadRequest. בדוגמה הבאה אפשר לראות איך ליצור בקשת הורדה:
Kotlin
val downloadRequest = DownloadRequest.Builder(contentId, contentUri).build()
Java
DownloadRequest downloadRequest = new DownloadRequest.Builder(contentId, contentUri).build();
בדוגמה הזו, contentId הוא מזהה ייחודי של התוכן. במקרים פשוטים, אפשר להשתמש ב-contentUri בתור contentId, אבל אפליקציות יכולות להשתמש בכל סכמת מזהים שמתאימה לתרחיש השימוש שלהן. ל-DownloadRequest.Builder יש גם כמה פונקציות אופציונליות להגדרת ערכים. לדוגמה, אפשר להשתמש ב-setKeySetId וב-setData כדי להגדיר DRM ונתונים מותאמים אישית שהאפליקציה רוצה לשייך להורדה, בהתאמה. אפשר גם לציין את סוג ה-MIME של התוכן באמצעות setMimeType, כרמז למקרים שבהם אי אפשר להסיק את סוג התוכן מ-contentUri.
אחרי שיוצרים את הבקשה, אפשר לשלוח אותה אל DownloadService כדי להוסיף את ההורדה:
Kotlin
DownloadService.sendAddDownload( context, MyDownloadService::class.java, downloadRequest, /* foreground= */ false )
Java
DownloadService.sendAddDownload( context, MyDownloadService.class, downloadRequest, /* foreground= */ false);
בדוגמה הזו, MyDownloadService הוא מחלקת המשנה DownloadService של האפליקציה, והפרמטר foreground קובע אם השירות יופעל בחזית. אם האפליקציה כבר בחזית, בדרך כלל הפרמטר foreground
צריך להיות מוגדר ל-false כי DownloadService יציב את עצמו בחזית אם הוא יקבע שיש לו עבודה לבצע.
מסיר הורדות
כדי להסיר הורדה, שולחים פקודת הסרה אל DownloadService, כאשר contentId מציין את ההורדה שרוצים להסיר:
Kotlin
DownloadService.sendRemoveDownload( context, MyDownloadService::class.java, contentId, /* foreground= */ false )
Java
DownloadService.sendRemoveDownload( context, MyDownloadService.class, contentId, /* foreground= */ false);
אפשר גם להסיר את כל הנתונים שהורדו באמצעות הפקודה DownloadService.sendRemoveAllDownloads.
התחלה והפסקה של הורדות
ההורדה תתבצע רק אם יתקיימו ארבעה תנאים:
- אין סיבה להפסקת ההורדה.
- ההורדות לא מושהות.
- הדרישות להתקדמות ההורדה מתקיימות. בדרישות אפשר לציין מגבלות על סוגי הרשתות המותרים, וגם אם המכשיר צריך להיות במצב לא פעיל או מחובר למטען.
- לא חרגתם מהמספר המקסימלי של הורדות מקבילות.
אפשר לשלוט בכל התנאים האלה באמצעות שליחת פקודות אל DownloadService.
הגדרה ומחיקה של סיבות להפסקת ההורדה
אפשר להגדיר סיבה להפסקת הורדה אחת או יותר:
Kotlin
// Set the stop reason for a single download. DownloadService.sendSetStopReason( context, MyDownloadService::class.java, contentId, stopReason, /* foreground= */ false ) // Clear the stop reason for a single download. DownloadService.sendSetStopReason( context, MyDownloadService::class.java, contentId, Download.STOP_REASON_NONE, /* foreground= */ false )
Java
// Set the stop reason for a single download. DownloadService.sendSetStopReason( context, MyDownloadService.class, contentId, stopReason, /* foreground= */ false); // Clear the stop reason for a single download. DownloadService.sendSetStopReason( context, MyDownloadService.class, contentId, Download.STOP_REASON_NONE, /* foreground= */ false);
stopReason יכול להיות כל ערך שאינו אפס (Download.STOP_REASON_NONE = 0 הוא ערך מיוחד שמציין שההורדה לא נעצרת). באפליקציות שבהן יש כמה סיבות להפסקת ההורדות, אפשר להשתמש בערכים שונים כדי לעקוב אחרי הסיבה להפסקת כל הורדה. ההגדרה והביטול של סיבת העצירה לכל ההורדות פועלים באותו אופן כמו ההגדרה והביטול של סיבת העצירה להורדה אחת, רק שהערך של contentId צריך להיות null.
אם להורדה יש סיבת עצירה שאינה אפס, היא תהיה במצב Download.STATE_STOPPED. הסיבות להפסקת התהליך נשמרות ב-DownloadIndex, ולכן הן נשמרות גם אם התהליך של האפליקציה נסגר ואז מופעל מחדש.
השהיה והפעלה מחדש של כל ההורדות
אפשר להשהות את כל ההורדות ולהמשיך אותן באופן הבא:
Kotlin
// Pause all downloads. DownloadService.sendPauseDownloads( context, MyDownloadService::class.java, /* foreground= */ false ) // Resume all downloads. DownloadService.sendResumeDownloads( context, MyDownloadService::class.java, /* foreground= */ false )
Java
// Pause all downloads. DownloadService.sendPauseDownloads(context, MyDownloadService.class, /* foreground= */ false); // Resume all downloads. DownloadService.sendResumeDownloads(context, MyDownloadService.class, /* foreground= */ false);
כשההורדות מושהות, הן יהיו במצב Download.STATE_QUEUED.
בניגוד להגדרת סיבות להפסקת השימוש, בגישה הזו לא נשמרים שינויים במצב. היא משפיעה רק על מצב זמן הריצה של DownloadManager.
הגדרת הדרישות להתקדמות ההורדות
אפשר להשתמש ב-Requirements כדי לציין אילוצים שצריכים להתקיים כדי שההורדות ימשיכו. אפשר להגדיר את הדרישות באמצעות קריאה ל-DownloadManager.setRequirements() כשיוצרים את DownloadManager, כמו בדוגמה למעלה. אפשר גם לשנות אותם באופן דינמי על ידי שליחת פקודה אל DownloadService:
Kotlin
// Set the download requirements. DownloadService.sendSetRequirements( context, MyDownloadService::class.java, requirements, /* foreground= */ false)
Java
// Set the download requirements. DownloadService.sendSetRequirements( context, MyDownloadService.class, requirements, /* foreground= */ false);
אם אי אפשר להמשיך בהורדה כי הדרישות לא מתקיימות, ההורדה תהיה במצב Download.STATE_QUEUED. אפשר לשלוח שאילתה לגבי הדרישות שלא מתקיימות באמצעות DownloadManager.getNotMetRequirements().
הגדרת המספר המקסימלי של הורדות מקבילות
אפשר להגדיר את המספר המקסימלי של הורדות מקבילות באמצעות הקריאה ל-DownloadManager.setMaxParallelDownloads(). בדרך כלל, הפעולה הזו מתבצעת כשיוצרים את DownloadManager, כמו בדוגמה למעלה.
אם ההורדה לא יכולה להתבצע כי כבר מתבצעות הורדות מקבילות במספר המקסימלי, היא תהיה במצב Download.STATE_QUEUED.
שליחת שאילתות לגבי הורדות
אפשר לשלוח שאילתה לגבי DownloadIndex של DownloadManager כדי לראות את הסטטוס של כל ההורדות, כולל אלה שהושלמו או נכשלו. אפשר לקבל את DownloadIndex
בשיחה עם DownloadManager.getDownloadIndex(). אחרי כן אפשר לקבל סמן שחוזר על עצמו בכל ההורדות על ידי קריאה ל-DownloadIndex.getDownloads(). אפשרות אחרת היא לשלוח שאילתה לגבי מצב ההורדה של קובץ יחיד באמצעות קריאה ל-DownloadIndex.getDownload().
DownloadManager מספקת גם את DownloadManager.getCurrentDownloads(), שמחזירה רק את הסטטוס של ההורדות הנוכחיות (כלומר, לא הושלמו או נכשלו). השיטה הזו שימושית לעדכון התראות ורכיבים אחרים בממשק המשתמש שמציגים את ההתקדמות והסטטוס של ההורדות הנוכחיות.
האזנה להורדות
אתם יכולים להוסיף מאזין ל-DownloadManager כדי לקבל מידע כשהמצב של ההורדות הנוכחיות משתנה:
Kotlin
downloadManager.addListener( object : DownloadManager.Listener { // Override methods of interest here. } )
Java
downloadManager.addListener( new DownloadManager.Listener() { // Override methods of interest here. });
דוגמה קונקרטית לכך אפשר לראות ב-DownloadManagerListener בכיתה DownloadTracker של אפליקציית ההדגמה.
הפעלת תוכן שהורד
הפעלת תוכן שהורד דומה להפעלת תוכן אונליין, אלא שהנתונים נקראים מההורדה Cache ולא דרך הרשת.
כדי להפעיל תוכן שהורדתם, צריך ליצור CacheDataSource.Factory באמצעות אותו מופע של Cache ששימש להורדה, ולהוסיף אותו ל-DefaultMediaSourceFactory כשיוצרים את הנגן:
Kotlin
// Create a read-only cache data source factory using the download cache. val cacheDataSourceFactory: DataSource.Factory = CacheDataSource.Factory() .setCache(downloadCache) .setUpstreamDataSourceFactory(httpDataSourceFactory) .setCacheWriteDataSinkFactory(null) // Disable writing. val player = ExoPlayer.Builder(context) .setMediaSourceFactory( DefaultMediaSourceFactory(context).setDataSourceFactory(cacheDataSourceFactory) ) .build()
Java
// Create a read-only cache data source factory using the download cache. DataSource.Factory cacheDataSourceFactory = new CacheDataSource.Factory() .setCache(downloadCache) .setUpstreamDataSourceFactory(httpDataSourceFactory) .setCacheWriteDataSinkFactory(null); // Disable writing. ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory( new DefaultMediaSourceFactory(context).setDataSourceFactory(cacheDataSourceFactory)) .build();
אם אותו מופע של נגן ישמש גם להפעלת תוכן שלא הורד, צריך להגדיר את CacheDataSource.Factory כקריאה בלבד כדי למנוע הורדה של התוכן הזה גם במהלך ההפעלה.
אחרי שמגדירים את הנגן עם CacheDataSource.Factory, תהיה לו גישה לתוכן שהורד להפעלה. כדי להפעיל את ההורדה, פשוט מעבירים את MediaItem המתאים לנגן. אפשר לקבל MediaItem מ-Download באמצעות Download.request.toMediaItem, או ישירות מ-DownloadRequest באמצעות DownloadRequest.toMediaItem.
הגדרת MediaSource
בדוגמה שלמעלה, המטמון של ההורדות זמין להפעלה של כל קובצי MediaItem. אפשר גם להפוך את מטמון ההורדות לזמין למופעים ספציפיים של MediaSource, שאפשר להעביר ישירות לנגן:
Kotlin
val mediaSource = ProgressiveMediaSource.Factory(cacheDataSourceFactory) .createMediaSource(MediaItem.fromUri(contentUri)) player.setMediaSource(mediaSource) player.prepare()
Java
ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(cacheDataSourceFactory) .createMediaSource(MediaItem.fromUri(contentUri)); player.setMediaSource(mediaSource); player.prepare();
הורדה והפעלה של שידורים דינמיים
שידורים אדפטיביים (למשל DASH, SmoothStreaming ו-HLS) בדרך כלל מכילים כמה טראקים של מדיה. לעתים קרובות יש כמה טראקים שמכילים את אותו תוכן באיכויות שונות (למשל, טראקים של וידאו באיכות SD, HD ו-4K). יכול להיות גם כמה טראקים מאותו סוג שמכילים תוכן שונה (למשל, כמה טראקים של אודיו בשפות שונות).
במקרה של הפעלת סטרימינג, אפשר להשתמש בכלי לבחירת רצועות כדי לבחור אילו רצועות יופעלו. באופן דומה, כשמורידים, אפשר להשתמש ב-DownloadHelper כדי לבחור אילו טראקים להוריד. שימוש אופייני ב-DownloadHelper
כולל את השלבים הבאים:
- בניית
DownloadHelperבאמצעות מכונתDownloadHelper.Factory. מכינים את העוזר ומחכים לקריאה חוזרת (callback).Kotlin
val downloadHelper = DownloadHelper.Factory() .setRenderersFactory(DefaultRenderersFactory(context)) .setDataSourceFactory(dataSourceFactory) .create(MediaItem.fromUri(contentUri)) downloadHelper.prepare(callback)
Java
DownloadHelper downloadHelper = new DownloadHelper.Factory() .setRenderersFactory(new DefaultRenderersFactory(context)) .setDataSourceFactory(dataSourceFactory) .create(MediaItem.fromUri(contentUri)); downloadHelper.prepare(callback);
- אופציונלי: בודקים את הטראקים שנבחרו כברירת מחדל באמצעות
getMappedTrackInfoו-getTrackSelections, ומבצעים שינויים באמצעותclearTrackSelections,replaceTrackSelectionsו-addTrackSelection. - יצירת
DownloadRequestעבור הטראקים שנבחרו על ידי קריאה ל-getDownloadRequest. אפשר להעביר את הבקשה אלDownloadServiceכדי להוסיף את ההורדה, כפי שמתואר למעלה. - משחררים את העוזר באמצעות
release().
כדי להפעיל תוכן דינמי שהורד, צריך להגדיר את הנגן ולהעביר את MediaItem המתאים, כמו שמתואר למעלה.
כשיוצרים את MediaItem, צריך להגדיר את MediaItem.localConfiguration.streamKeys כך שיתאים לערכים ב-DownloadRequest, כדי שהנגן ינסה להפעיל רק את קבוצת המשנה של הרצועות שהורדו. אם תשתמשו ב-Download.request.toMediaItem וב-DownloadRequest.toMediaItem כדי ליצור את MediaItem, המערכת תדאג לזה בשבילכם.