يقدّم ExoPlayer وظيفة لتنزيل الوسائط لتشغيلها بلا إنترنت. في معظم حالات الاستخدام، من المستحسن مواصلة عمليات التنزيل حتى عندما يكون تطبيقك في
الخلفية. في حالات الاستخدام هذه، يجب أن ينشئ تطبيقك فئة فرعية من DownloadService
ويُرسِل أوامر إلى الخدمة لإضافة عمليات التنزيل وإزالتها والتحكّم فيها. يوضِّح الرسم البياني التالي الفئات الرئيسية المعنيّة.
-
DownloadService
: يُغلِّفDownloadManager
ويعيد توجيه الطلبات إليه. وتسمح الخدمة لـ "DownloadManager
" بمواصلة تشغيل التطبيق حتى في الخلفية. -
DownloadManager
: يدير عمليات تنزيل متعدّدة، ويحمّل (ويخزّن) حالتها من (وإلى)DownloadIndex
، ويشغّل عمليات التنزيل ويوقفها استنادًا إلى متطلبات مثل إمكانية الاتصال بالشبكة وما إلى ذلك. لتنزيل المحتوى، سيقرأ المدير عادةً البيانات التي يتم تنزيلها منHttpDataSource
ويكتبها فيCache
. DownloadIndex
: تثبيت حالات التنزيل
إنشاء DownloadService
لإنشاء DownloadService
، ضع فئة فرعية ونفِّذ طرقها التجريدية:
getDownloadManager()
: عرضDownloadManager
المطلوب استخدامهgetScheduler()
: عرضScheduler
اختياري يمكنه إعادة تشغيل الخدمة عند استيفاء المتطلبات اللازمة لمتابعة عمليات التنزيل في انتظار المراجعة يقدّم ExoPlayer آليات التنفيذ التالية:PlatformScheduler
، التي تستخدم Jobscheduler (الحد الأدنى لواجهة برمجة التطبيقات هو 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
لتحديد
بيانات إدارة الحقوق الرقمية والبيانات المخصّصة التي يريد التطبيق ربطها بعملية التنزيل،
على التوالي. يمكن أيضًا تحديد نوع 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()
الذي
يعرض حالة عمليات التنزيل الحالية (أي عمليات التنزيل التي لم تكتمل أو تعذّر إكمالها) فقط. هذه المحاولة مفيدة لتعديل الإشعارات ومكونات واجهة المستخدم الأخرى التي تعرِض مستوى التقدّم وحالة عمليات التنزيل الحالية.
الاستماع إلى الأغاني التي تمّ تنزيلها
يمكنك إضافة مستمِع إلى "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) تحتوي عادةً على عدّة مقاطع صوتية. غالبًا ما تتضمّن عدة مقاطع صوتية المحتوى نفسه بدرجات جودة مختلفة (مثل مقاطع الفيديو ذات الدقة العادية والدقة العالية و4K). وقد تتوفّر أيضًا مقاطع صوتية متعددة من النوع نفسه تحتوي على محتوى مختلف (مثلاً، مقاطع صوتية متعددة بلغات مختلفة).
بالنسبة إلى عمليات تشغيل البث، يمكن استخدام أداة اختيار الأغاني لاختيار
الأغاني التي يتم تشغيلها. وبالمثل، يمكن استخدام DownloadHelper
لتنزيل DownloadHelper
من الأغاني. إنّ الاستخدام النموذجي لـ DownloadHelper
يتّبع الخطوات التالية:
- أنشِئ
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);
- يمكنك اختياريًا فحص المقاطع الصوتية التلقائية المحدَّدة باستخدام
getMappedTrackInfo
وgetTrackSelections
، وإجراء التعديلات باستخدامclearTrackSelections
وreplaceTrackSelections
وaddTrackSelection
. - أنشئ
DownloadRequest
للمقاطع الصوتية المحدّدة من خلال الاتصال بالرقمgetDownloadRequest
. يمكن تمرير الطلب إلىDownloadService
لإضافة عملية التنزيل، كما هو موضَّح أعلاه. - يمكنك إزالة المساعد باستخدام
release()
.
يتطلب تشغيل المحتوى التكيُّفي الذي تم تنزيله ضبط المشغّل و
تمرير MediaItem
المقابل، كما هو موضّح أعلاه.
عند إنشاء MediaItem
، يجب ضبط MediaItem.localConfiguration.streamKeys
لمطابقة تلك المتوفّرة في DownloadRequest
لكي يحاول المشغّل فقط
تشغيل المجموعة الفرعية من المقاطع الصوتية التي تم تنزيلها. سيؤدي استخدام
Download.request.toMediaItem
وDownloadRequest.toMediaItem
لإنشاء
"MediaItem
" إلى حلّ هذه المشكلة.