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, כרמז למקרים שבהם אי אפשר להסיק את סוג ה-MIME מהתג 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). - אפשר לבדוק את הטראקים שנבחרו כברירת מחדל באמצעות
getMappedTrackInfoו-getTrackSelections, ולבצע שינויים באמצעותclearTrackSelections,replaceTrackSelectionsו-addTrackSelection. - יצירת
DownloadRequestעבור הטראקים שנבחרו על ידי קריאה ל-getDownloadRequest. אפשר להעביר את הבקשה אלDownloadServiceכדי להוסיף את ההורדה, כמו שמתואר קודם. - משחררים את העוזר באמצעות
release().
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);
כדי להפעיל תוכן אדפטיבי שהורד, צריך להגדיר את נגן המדיה ולהעביר את MediaItem המתאים, כמו שמתואר למעלה.
כשיוצרים את MediaItem, צריך להגדיר את MediaItem.localConfiguration.streamKeys כך שיתאימו לאלה שב-DownloadRequest, כדי שהנגן ינסה להפעיל רק את קבוצת המשנה של הרצועות שהורדו. השימוש ב-Download.request.toMediaItem וב-DownloadRequest.toMediaItem כדי ליצור את MediaItem יטפל בזה בשבילכם.