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

ExoPlayer มีฟังก์ชันการดาวน์โหลดสื่อเพื่อเล่นแบบออฟไลน์ ใน Use Case ส่วนใหญ่ แนะนำให้ดาวน์โหลดต่อไปแม้ว่าแอปจะทำงานอยู่เบื้องหลัง สำหรับกรณีการใช้งานเหล่านี้ แอปของคุณควรสร้างคลาสย่อยของ 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 ได้ แต่แอปสามารถใช้รูปแบบรหัสใดก็ได้ที่เหมาะกับกรณีการใช้งานมากที่สุด 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() ได้โดยโทรไปที่ 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. ปล่อยตัวช่วยโดยใช้ release()

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

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