ExoPlayer มีฟังก์ชันการดาวน์โหลดสื่อสำหรับการเล่นแบบออฟไลน์ ในกรณีการใช้งานส่วนใหญ่ เป็นที่พึงปรารถนาที่การดาวน์โหลดจะดำเนินต่อไปแม้ว่าแอปของคุณจะอยู่ในพื้นหลังก็ตาม สำหรับกรณีการใช้งานเหล่านี้ แอปของคุณควรเป็นซับคลาส DownloadService และส่งคำสั่งไปยังบริการเพื่อเพิ่ม ลบ และควบคุมการดาวน์โหลด แผนภาพต่อไปนี้แสดงให้เห็นคลาสหลักที่เกี่ยวข้อง
DownloadService: ห่อDownloadManagerและส่งต่อคำสั่งไปยังมัน บริการนี้จะช่วยให้DownloadManagerยังคงทำงานได้แม้ว่าแอปจะอยู่เบื้องหลังก็ตามDownloadManager: จัดการการดาวน์โหลดหลายรายการ การโหลด (และจัดเก็บ) สถานะจาก (และไปยัง)DownloadIndexการเริ่มและหยุดการดาวน์โหลดตามความต้องการ เช่น การเชื่อมต่อเครือข่าย และอื่นๆ ในการดาวน์โหลดเนื้อหา ผู้จัดการมักจะอ่านข้อมูลที่กำลังดาวน์โหลดจากHttpDataSourceและเขียนลงในCacheDownloadIndex: คงสถานะการดาวน์โหลดไว้
การสร้างบริการดาวน์โหลด
ในการสร้าง 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 โดยทั่วไปมีขั้นตอนดังนี้:
- สร้าง
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);
- ไม่บังคับ: ตรวจสอบแทร็กที่เลือกไว้โดยค่าเริ่มต้นโดยใช้
getMappedTrackInfoและgetTrackSelectionsแล้วปรับโดยใช้clearTrackSelectionsreplaceTrackSelectionsและaddTrackSelection - สร้าง
DownloadRequestสำหรับแทร็กที่เลือกโดยเรียกgetDownloadRequestคุณสามารถส่งคำขอไปยังDownloadServiceของคุณเพื่อเพิ่มการดาวน์โหลดได้ ดังที่อธิบายไว้ข้างต้น - ปล่อยตัวช่วยโดยใช้
release()
การเล่นเนื้อหาที่ปรับเปลี่ยนได้ซึ่งดาวน์โหลดมานั้นต้องกำหนดค่าเครื่องเล่นและส่ง MediaItem ที่สอดคล้องกันตามที่อธิบายไว้ข้างต้น
เมื่อสร้าง MediaItem จะต้องตั้งค่า MediaItem.localConfiguration.streamKeys ให้ตรงกับ DownloadRequest เพื่อให้เครื่องเล่นพยายามเล่นเฉพาะชุดย่อยของแทร็กที่ดาวน์โหลดมาเท่านั้น การใช้ Download.request.toMediaItem และ DownloadRequest.toMediaItem เพื่อสร้าง MediaItem จะจัดการเรื่องนี้ให้กับคุณ