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

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

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

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

การสร้าง DownloadService

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

  • getDownloadManager(): แสดง DownloadManager ที่จะใช้
  • getScheduler(): แสดงผล Scheduler ที่ไม่บังคับ ซึ่งสามารถรีสตาร์ท เมื่อต้องการให้การดาวน์โหลดที่รอดำเนินการเป็นไปตามข้อกำหนด ExoPlayer มีการใช้งานดังต่อไปนี้
    • PlatformScheduler ซึ่งใช้ JobScheduler (API ขั้นต่ำคือ 21) โปรดดู PlatformScheduler เป็น Javadocs สำหรับข้อกำหนดสิทธิ์ของแอป
    • 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 ได้ด้วย เพื่อเป็นคำแนะนำในกรณีที่ไม่สามารถอนุมานประเภทเนื้อหาจาก 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.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);
    
  2. (ไม่บังคับ) ตรวจสอบแทร็กเริ่มต้นที่เลือกโดยใช้ getMappedTrackInfo และ getTrackSelections แล้วทำการปรับเปลี่ยนโดยใช้ clearTrackSelections replaceTrackSelections และ addTrackSelection
  3. สร้าง DownloadRequest สำหรับแทร็กที่เลือกโดยการโทร getDownloadRequest สามารถส่งคำขอไปยัง DownloadService ของคุณเพื่อ เพิ่มการดาวน์โหลด ตามที่อธิบายไว้ข้างต้น
  4. ปล่อย Assistant โดยใช้ release()

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

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