กำลังดาวน์โหลดสื่อ

ExoPlayer มีฟังก์ชันการดาวน์โหลดสื่อสำหรับการเล่นแบบออฟไลน์ ในกรณีการใช้งานส่วนใหญ่ เป็นที่พึงปรารถนาที่การดาวน์โหลดจะดำเนินต่อไปแม้ว่าแอปของคุณจะอยู่ในพื้นหลังก็ตาม สำหรับกรณีการใช้งานเหล่านี้ แอปของคุณควรเป็นซับคลาส DownloadService และส่งคำสั่งไปยังบริการเพื่อเพิ่ม ลบ และควบคุมการดาวน์โหลด แผนภาพต่อไปนี้แสดงให้เห็นคลาสหลักที่เกี่ยวข้อง

ชั้นเรียนสำหรับการดาวน์โหลดสื่อ ทิศทางลูกศรแสดงการไหลของข้อมูล

  • DownloadService: ห่อ DownloadManager และส่งต่อคำสั่งไปยังมัน บริการนี้จะช่วยให้ DownloadManager ยังคงทำงานได้แม้ว่าแอปจะอยู่เบื้องหลังก็ตาม
  • DownloadManager: จัดการการดาวน์โหลดหลายรายการ การโหลด (และจัดเก็บ) สถานะจาก (และไปยัง) DownloadIndex การเริ่มและหยุดการดาวน์โหลดตามความต้องการ เช่น การเชื่อมต่อเครือข่าย และอื่นๆ ในการดาวน์โหลดเนื้อหา ผู้จัดการมักจะอ่านข้อมูลที่กำลังดาวน์โหลดจาก HttpDataSource และเขียนลงใน Cache
  • DownloadIndex: คงสถานะการดาวน์โหลดไว้

การสร้างบริการดาวน์โหลด

ในการสร้าง DownloadService ให้ทำการสร้างคลาสย่อยและนำวิธีการแบบนามธรรมมาใช้:

  • getDownloadManager(): ส่งคืน DownloadManager ที่จะใช้
  • getScheduler(): ส่งคืน Scheduler ที่เป็นทางเลือก ซึ่งสามารถเริ่มบริการใหม่ได้เมื่อตรงตามข้อกำหนดที่จำเป็นในการดำเนินการดาวน์โหลดที่รอดำเนินการ ExoPlayer จัดให้มีการใช้งานดังต่อไปนี้:
    • PlatformScheduler ซึ่งใช้ JobScheduler (API ขั้นต่ำคือ 21) ดูเอกสาร javadocs ของ 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 ได้ แต่แอปจะใช้ รูปแบบรหัสใดก็ได้ที่เหมาะกับ Use Case ของตนมากที่สุด 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() ซึ่งจะส่งคืนสถานะของการดาวน์โหลดปัจจุบันเท่านั้น (เช่น ไม่เสร็จสมบูรณ์หรือล้มเหลว) วิธีนี้มีประโยชน์สำหรับการอัปเดตการแจ้งเตือนและส่วนประกอบ UI อื่นๆ ที่แสดงความคืบหน้าและสถานะของการดาวน์โหลดปัจจุบัน

การฟังการดาวน์โหลด

คุณเพิ่มผู้ฟังไปยัง 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.Factory เตรียมผู้ช่วยให้พร้อมและรอการติดต่อกลับ

    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);
  2. ไม่บังคับ: ตรวจสอบแทร็กที่เลือกไว้โดยค่าเริ่มต้นโดยใช้ getMappedTrackInfo และ getTrackSelections แล้วปรับโดยใช้ clearTrackSelections replaceTrackSelections และ addTrackSelection
  3. สร้าง DownloadRequest สำหรับแทร็กที่เลือกโดยเรียก getDownloadRequest คุณสามารถส่งคำขอไปยัง DownloadService ของคุณเพื่อเพิ่มการดาวน์โหลดได้ ดังที่อธิบายไว้ข้างต้น
  4. ปล่อยตัวช่วยโดยใช้ release()

การเล่นเนื้อหาที่ปรับเปลี่ยนได้ซึ่งดาวน์โหลดมานั้นต้องกำหนดค่าเครื่องเล่นและส่ง MediaItem ที่สอดคล้องกันตามที่อธิบายไว้ข้างต้น

เมื่อสร้าง MediaItem จะต้องตั้งค่า MediaItem.localConfiguration.streamKeys ให้ตรงกับ DownloadRequest เพื่อให้เครื่องเล่นพยายามเล่นเฉพาะชุดย่อยของแทร็กที่ดาวน์โหลดมาเท่านั้น การใช้ Download.request.toMediaItem และ DownloadRequest.toMediaItem เพื่อสร้าง MediaItem จะจัดการเรื่องนี้ให้กับคุณ