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

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

คลาสสำหรับการดาวน์โหลดสื่อ ทิศทางลูกศรแสดงถึงการไหลของข้อมูล

  • DownloadService: ห่อหุ้ม DownloadManager และส่งต่อคำสั่งไปยัง DownloadManager บริการนี้ช่วยให้ DownloadManager ทำงานต่อไปได้แม้ว่าแอปจะทำงานใน เบื้องหลังก็ตาม
  • DownloadManager: จัดการการดาวน์โหลดหลายรายการ โหลด (และจัดเก็บ) สถานะจาก (และไปยัง) 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

ข้อมูลโค้ดต่อไปนี้แสดงวิธีสร้างอินสแตนซ์ของ 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

การเริ่มและหยุดดาวน์โหลด

การดาวน์โหลดจะดำเนินการต่อได้ก็ต่อเมื่อเป็นไปตามเงื่อนไข 4 ข้อต่อไปนี้

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

คุณควบคุมเงื่อนไขทั้งหมดนี้ได้โดยส่งคำสั่งไปยัง 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 สามารถเป็นค่าที่ไม่ใช่ 0 ได้ (Download.STOP_REASON_NONE = 0 เป็นค่าพิเศษที่หมายความว่าการดาวน์โหลดจะไม่หยุด) แอปที่มี เหตุผลหลายประการในการหยุดดาวน์โหลดสามารถใช้ค่าที่แตกต่างกันเพื่อติดตาม สาเหตุที่หยุดดาวน์โหลดแต่ละรายการ การตั้งค่าและการล้างเหตุผลในการหยุดสำหรับ การดาวน์โหลดทั้งหมดจะทำงานในลักษณะเดียวกับการตั้งค่าและการล้างเหตุผลในการหยุดสำหรับ การดาวน์โหลดรายการเดียว ยกเว้นว่าควรตั้งค่า contentId เป็น null

เมื่อการดาวน์โหลดมีเหตุผลในการหยุดที่ไม่ใช่ 0 การดาวน์โหลดจะอยู่ในสถานะ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 อื่นๆ ที่แสดงความคืบหน้าและสถานะของการดาวน์โหลดปัจจุบัน

การฟังรายการที่ดาวน์โหลด

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