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