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 ของ PlatformSchedulerWorkManagerScheduler
ซึ่งใช้ 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
// 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
// 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
ตัวอย่างต่อไปนี้แสดงวิธีสร้างคำขอดาวน์โหลด
val downloadRequest = DownloadRequest.Builder(contentId, contentUri).build()
DownloadRequest downloadRequest = new DownloadRequest.Builder(contentId, contentUri).build();
ในตัวอย่างนี้ contentId
คือตัวระบุที่ไม่ซ้ำกันสำหรับเนื้อหา ในกรณีง่ายๆ contentUri
มักใช้เป็น contentId
ได้ แต่แอปสามารถใช้รูปแบบรหัสใดก็ได้ที่เหมาะกับกรณีการใช้งานมากที่สุด DownloadRequest.Builder
ยังมีตัวตั้งค่าที่ไม่บังคับบางรายการด้วย เช่น setKeySetId
และ setData
สามารถใช้เพื่อตั้งค่า DRM และข้อมูลที่กำหนดเองที่แอปต้องการเชื่อมโยงกับการดาวน์โหลดได้ นอกจากนี้ คุณยังระบุประเภท MIME ของเนื้อหาได้โดยใช้ setMimeType
เพื่อเป็นการบอกใบ้ในกรณีที่ไม่สามารถอนุมานประเภทเนื้อหาจาก contentUri
เมื่อสร้างแล้ว คุณจะส่งคำขอไปยัง DownloadService
เพื่อเพิ่มการดาวน์โหลดได้ ดังนี้
DownloadService.sendAddDownload(
context,
MyDownloadService::class.java,
downloadRequest,
/* foreground= */ false
)
DownloadService.sendAddDownload(
context, MyDownloadService.class, downloadRequest, /* foreground= */ false);
ในตัวอย่างนี้ MyDownloadService
คือคลาสย่อย DownloadService
ของแอป และพารามิเตอร์ foreground
จะควบคุมว่าจะเริ่มต้นบริการในเบื้องหน้าหรือไม่ หากแอปอยู่ในเบื้องหน้าอยู่แล้ว ปกติแล้วคุณควรตั้งค่าพารามิเตอร์ foreground
เป็น false
เนื่องจาก DownloadService
จะแสดงตัวเองในเบื้องหน้าหากพิจารณาว่ามีงานให้ทำ
กำลังนำรายการที่ดาวน์โหลดออก
คุณนำการดาวน์โหลดออกได้โดยส่งคำสั่งนำออกไปยัง DownloadService
โดยที่ contentId
จะระบุการดาวน์โหลดที่จะนำออก
DownloadService.sendRemoveDownload(
context,
MyDownloadService::class.java,
contentId,
/* foreground= */ false
)
DownloadService.sendRemoveDownload(
context, MyDownloadService.class, contentId, /* foreground= */ false);
นอกจากนี้คุณยังนำข้อมูลที่ดาวน์โหลดไว้ทั้งหมดออกได้ด้วย DownloadService.sendRemoveAllDownloads
การเริ่มและหยุดการดาวน์โหลด
การดาวน์โหลดจะดำเนินการต่อเมื่อเป็นไปตามเงื่อนไข 4 ข้อต่อไปนี้เท่านั้น
- การดาวน์โหลดไม่มีเหตุผลในการหยุด
- การดาวน์โหลดไม่หยุดชั่วคราว
- เป็นไปตามข้อกำหนดสำหรับการดาวน์โหลด ข้อกำหนดสามารถระบุข้อจำกัดเกี่ยวกับประเภทเครือข่ายที่อนุญาต รวมถึงระบุว่าอุปกรณ์ควรไม่มีการใช้งานหรือเชื่อมต่อกับที่ชาร์จ
- ไม่มีการดาวน์โหลดพร้อมกันเกินจำนวนสูงสุด
เงื่อนไขทั้งหมดนี้ควบคุมได้โดยการส่งคำสั่งไปยัง DownloadService
การตั้งค่าและการล้างเหตุผลที่การดาวน์โหลดหยุด
คุณตั้งค่าเหตุผลที่การดาวน์โหลดรายการเดียวหรือทั้งหมดหยุดลงได้ ดังนี้
// 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
)
// 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
ดังนั้นเหตุผลดังกล่าวจะยังคงอยู่หากกระบวนการสมัครถูกหยุดและเริ่มอีกครั้งในภายหลัง
การหยุดดาวน์โหลดทั้งหมดชั่วคราวแล้วกลับมาดำเนินการอีกครั้ง
คุณหยุดการดาวน์โหลดทั้งหมดชั่วคราวและกลับมาดาวน์โหลดต่อได้โดยทำดังนี้
// Pause all downloads.
DownloadService.sendPauseDownloads(
context,
MyDownloadService::class.java,
/* foreground= */ false
)
// Resume all downloads.
DownloadService.sendResumeDownloads(
context,
MyDownloadService::class.java,
/* foreground= */ false
)
// 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
// Set the download requirements.
DownloadService.sendSetRequirements(
context, MyDownloadService::class.java, requirements, /* foreground= */ false)
// 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
เพื่อรับแจ้งเมื่อการดาวน์โหลดปัจจุบันมีการเปลี่ยนแปลงสถานะได้โดยทำดังนี้
downloadManager.addListener(
object : DownloadManager.Listener { // Override methods of interest here.
}
)
downloadManager.addListener(
new DownloadManager.Listener() {
// Override methods of interest here.
});
ดูตัวอย่างที่เป็นรูปธรรมได้ที่ DownloadManagerListener
ในคลาส DownloadTracker
ของแอปเดโม
การเล่นเนื้อหาที่ดาวน์โหลด
การเล่นเนื้อหาที่ดาวน์โหลดจะคล้ายกับการเล่นเนื้อหาออนไลน์ ยกเว้นว่าระบบจะอ่านข้อมูลจากCache
การดาวน์โหลดแทนการอ่านผ่านเครือข่าย
หากต้องการเล่นเนื้อหาที่ดาวน์โหลด ให้สร้าง CacheDataSource.Factory
โดยใช้อินสแตนซ์ Cache
เดียวกับที่ใช้สำหรับการดาวน์โหลด และแทรกเนื้อหาดังกล่าวลงใน DefaultMediaSourceFactory
เมื่อสร้างโปรแกรมเล่น โดยทำดังนี้
// 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()
// 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
แต่ละรายการได้ ซึ่งสามารถส่งไปยังโปรแกรมเล่นโดยตรงโดยทำดังนี้
val mediaSource =
ProgressiveMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(MediaItem.fromUri(contentUri))
player.setMediaSource(mediaSource)
player.prepare()
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.forMediaItem
โปรดเตรียมผู้ช่วยเหลือและรอให้ระบบติดต่อกลับval downloadHelper =
DownloadHelper.forMediaItem(
context,
MediaItem.fromUri(contentUri),
DefaultRenderersFactory(context),
dataSourceFactory
)
downloadHelper.prepare(callback)DownloadHelper downloadHelper =
DownloadHelper.forMediaItem(
context,
MediaItem.fromUri(contentUri),
new DefaultRenderersFactory(context),
dataSourceFactory);
downloadHelper.prepare(callback); - (ไม่บังคับ) ตรวจสอบแทร็กเริ่มต้นที่เลือกโดยใช้
getMappedTrackInfo
และgetTrackSelections
และทำการปรับโดยใช้clearTrackSelections
,replaceTrackSelections
และaddTrackSelection
- สร้าง
DownloadRequest
สำหรับแทร็กที่คุณเลือกโดยเรียกใช้getDownloadRequest
คุณสามารถส่งคำขอไปยังDownloadService
เพื่อเพิ่มการดาวน์โหลดตามที่อธิบายไว้ข้างต้น - ปล่อยตัวช่วยโดยใช้
release()
การเล่นเนื้อหาแบบปรับเปลี่ยนได้ซึ่งดาวน์โหลดไว้ต้องกำหนดค่าโปรแกรมเล่นและส่ง MediaItem
ที่เกี่ยวข้องตามที่อธิบายไว้ข้างต้น
เมื่อสร้าง MediaItem
คุณต้องตั้งค่า MediaItem.localConfiguration.streamKeys
ให้ตรงกับใน DownloadRequest
เพื่อให้โปรแกรมเล่นพยายามเล่นเฉพาะกลุ่มย่อยของแทร็กที่ดาวน์โหลดเท่านั้น การใช้ Download.request.toMediaItem
และ DownloadRequest.toMediaItem
เพื่อสร้าง MediaItem
จะจัดการเรื่องนี้ให้คุณ