ExoPlayer מספק פונקציונליות להורדת מדיה להפעלה במצב אופליין. ברוב התרחישים לדוגמה, רצוי שההורדות ימשיכו גם כשהאפליקציה נמצאת ברקע. בתרחישי השימוש האלה, האפליקציה צריכה ליצור תת-סוג של DownloadService
ולשלוח פקודות לשירות כדי להוסיף, להסיר ולשלוט בהורדות. בתרשים הבא מוצגים הסוגים העיקריים של הגורמים המעורבים.
DownloadService
: עוטףDownloadManager
ומעביר אליו פקודות. השירות מאפשר ל-DownloadManager
להמשיך לפעול גם כשהאפליקציה פועלת ברקע.DownloadManager
: ניהול מספר הורדות, טעינת המצבים שלהן (ושמירתם) מ-DownloadIndex
(ול-DownloadIndex
), הפעלה ועצירה של הורדות על סמך דרישות כמו קישוריות לרשת וכו'. כדי להוריד את התוכן, בדרך כלל מנהל הקבצים יקריא את הנתונים שהורדתם מ-HttpDataSource
ויכתוב אותם ב-Cache
.DownloadIndex
: שמירת המצבים של ההורדות.
יצירת DownloadService
כדי ליצור DownloadService
, יוצרים אותה כסוג משנה ומטמיעים את השיטות המופשטות שלה:
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
, שאפשר להחזיר על ידי 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, SmoothStream ו-HLS) מכילים בדרך כלל מספר טראקים של מדיה. בדרך כלל יש כמה טראקים שמכילים את אותו תוכן באיכות שונה (למשל, טראקים של וידאו באיכות SD, HD ו-4K). יכול להיות גם שיהיו כמה טראקים מאותו סוג שמכילים תוכן שונה (למשל, כמה טראקי אודיו בשפות שונות).
בהפעלה בסטרימינג, אפשר להשתמש בבורר טראקים כדי לבחור אילו טראקים יופעלו. באופן דומה, להורדה, אפשר להשתמש ב-DownloadHelper
כדי לבחור אילו טראקים יורדים. כך משתמשים ב-DownloadHelper
:
- יוצרים
DownloadHelper
באמצעות אחת מהשיטות שלDownloadHelper.forMediaItem
. מכינים את העזרה וממתינים לקריאה החוזרת.Kotlin
val downloadHelper = DownloadHelper.forMediaItem( context, MediaItem.fromUri(contentUri), DefaultRenderersFactory(context), dataSourceFactory ) downloadHelper.prepare(callback)
Java
DownloadHelper downloadHelper = DownloadHelper.forMediaItem( context, MediaItem.fromUri(contentUri), new DefaultRenderersFactory(context), dataSourceFactory); downloadHelper.prepare(callback);
- אפשר גם לבדוק את הטראקים שנבחרו כברירת מחדל באמצעות
getMappedTrackInfo
ו-getTrackSelections
, ולבצע שינויים באמצעותclearTrackSelections
,replaceTrackSelections
ו-addTrackSelection
. - יוצרים
DownloadRequest
עבור הטראקים שנבחרו באמצעות קריאה ל-getDownloadRequest
. אפשר להעביר את הבקשה ל-DownloadService
כדי להוסיף את ההורדה, כפי שמתואר למעלה. - משחררים את העזרה באמצעות
release()
.
כדי להפעיל תוכן מותאם אישית שהורדתם, צריך להגדיר את הנגן ולהעביר את הערך המתאים של MediaItem
, כפי שמתואר למעלה.
כשיוצרים את MediaItem
, צריך להגדיר את MediaItem.localConfiguration.streamKeys
כך שיתאים לערכים ב-DownloadRequest
, כדי שהנגן ינסה להשמיע רק את קבוצת המשנה של הטראקים שהורדתם. הבעיה הזו תיפתר אם תשתמשו ב-Download.request.toMediaItem
וב-DownloadRequest.toMediaItem
כדי ליצור את MediaItem
.