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

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

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

การสร้าง DownloadService

หากต้องการสร้าง DownloadService ให้สร้างคลาสย่อยและใช้ เมธอดแบบนามธรรม

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

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

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

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

คุณสามารถเพิ่มผู้ฟังไปยัง 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 จะจัดการเรื่องนี้ให้คุณ