המדיה בהורדה

ExoPlayer מספק פונקציונליות להורדת מדיה להפעלה במצב אופליין. במרבית מומלץ שההורדות יימשכו גם כשהאפליקציה נמצאת רקע. בתרחישים לדוגמה האלה, האפליקציה שלך צריכה לתת מחלקה משנית ל-DownloadService שולחים לשירות פקודות להוספה, להסרה או לשליטה בהורדות. בתרשים הבא מוצגים הסוגים העיקריים של הגורמים המעורבים.

כיתות להורדת מדיה. כיוון החיצים מציין את זרימת הנתונים.

  • DownloadService: הפונקציה אורזת DownloadManager ומעבירה אליו פקודות. השירות מאפשר ל-DownloadManager להמשיך לפעול גם כשהאפליקציה נמצאת את הרקע.
  • DownloadManager: ניהול של הורדות מרובות, טעינה (ואחסון) שלהן משתנה מ-(ואל) DownloadIndex, התחלה ועצירה של הורדות מבוססות בדרישות כמו קישוריות רשת וכן הלאה. כדי להוריד את לרוב, המנהל יקרא את הנתונים שמורידים HttpDataSource, ולכתוב אותה בCache.
  • DownloadIndex: שמירת מצבי ההורדות.

יצירת שירות הורדה

כדי ליצור DownloadService, צריך לתת לו מחלקה משנית ולהטמיע אותו שיטות מופשטות:

  • getDownloadManager(): הפונקציה מחזירה את הערך DownloadManager שיש להשתמש בו.
  • getScheduler(): מחזירה Scheduler אופציונלי, שיכול להפעיל מחדש את השירות כאשר מתמלאים הדרישות הנדרשות להתקדמות של הורדות ממתינות. ExoPlayer מספק את ההטמעות הבאות:
    • PlatformScheduler, שמשתמש בJobScheduler (ה-API המינימלי הוא 21). צפייה את מסמכי Java Docs של 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, SmoothStreaming ו-HLS) בדרך כלל מכילים טראקים של מדיה. לעיתים קרובות יש מספר טראקים שמכילים את אותו תוכן איכויות שונות (למשל: טראקים של וידאו באיכות SD, HD ו-4K). ייתכן שיש גם מספר טראקים מאותו סוג שמכילים תוכן שונה (למשל, טראקים של אודיו בשפות שונות).

להפעלות סטרימינג, ניתן להשתמש בבורר טראקים כדי לבחור הטראקים האלה יופעלו. באופן דומה, אפשר להשתמש ב-DownloadHelper להורדה, לבחור אילו מהטראקים להוריד. שימוש אופייני ב-DownloadHelper פועל לפי השלבים הבאים:

  1. בניית DownloadHelper באמצעות אחד מ-DownloadHelper.forMediaItem שיטות. מכינים את ה-Helper וממתינים לקריאה החוזרת.

    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);
    
  2. אפשר גם לבדוק את הטראקים שנבחרו כברירת מחדל באמצעות getMappedTrackInfo וגם getTrackSelections, ולבצע התאמות באמצעות clearTrackSelections, replaceTrackSelections וגם addTrackSelection.
  3. יצירת DownloadRequest לטראקים שנבחרו בטלפון getDownloadRequest. ניתן להעביר את הבקשה אל DownloadService אל מוסיפים את ההורדה, כמו שמתואר למעלה.
  4. שחרור כלי העזר באמצעות release().

כדי להפעיל תוכן מותאם שהורדתם, יש להגדיר את הנגן העברת הערך המתאים של MediaItem, כמו שמתואר למעלה.

כשיוצרים את MediaItem, MediaItem.localConfiguration.streamKeys חייב להיות מוגדר להתאמה לאלה שבDownloadRequest, כך שהשחקן ינסה רק להפעיל את קבוצת המשנה של הטראקים שהורדתם. באמצעות Download.request.toMediaItem ו-DownloadRequest.toMediaItem כדי ליצור את MediaItem יטפל בזה בשבילך.