جارٍ تنزيل الوسائط

يوفّر ExoPlayer وظيفة تنزيل الوسائط لتشغيلها بلا اتصال بالإنترنت. في معظم فمن المستحسن استمرار التنزيلات حتى عندما يكون التطبيق الخلفية. في حالات الاستخدام هذه، يجب أن يشتمل تطبيقك على الفئة الفرعية DownloadService و إرسال أوامر إلى الخدمة لإضافة عمليات التنزيل وإزالتها والتحكم فيها. تشير رسالة الأشكال البيانية الفئات الرئيسية المشاركة في المخطط البياني التالي.

صفوف لتنزيل الوسائط تشير اتجاهات الأسهم إلى تدفق البيانات.

  • DownloadService: يلوف DownloadManager ويعيد توجيه الأوامر إليه. تشير رسالة الأشكال البيانية إنّ الخدمة تتيح مواصلة تشغيل DownloadManager حتى عندما يكون التطبيق مفعلاً. الخلفية.
  • DownloadManager: إدارة عمليات التنزيل المتعددة وتحميلها (وتخزينها) من (وإلى) DownloadIndex، بدءًا من عمليات التنزيل وإيقافها استنادًا إلى على متطلبات مثل الاتصال بالشبكة، وما إلى ذلك. لتنزيل سيقرأ المدير البيانات التي يتم تنزيلها من HttpDataSource، وكتابته في Cache.
  • DownloadIndex: تثبيت حالات عمليات التنزيل

إنشاء DownloadService

لإنشاء DownloadService، ضع فئة فرعية ونفِّذها الطرق التجريدية:

  • getDownloadManager(): تعرض القيمة DownloadManager المطلوب استخدامها.
  • getScheduler(): تعرض قيمة Scheduler اختيارية يمكن إعادة تشغيلها. الخدمة عند استيفاء المتطلبات اللازمة لعمليات التنزيل المعلّقة. يوفّر ExoPlayer آليات التنفيذ التالية:
    • PlatformScheduler، التي تستخدم Jobscheduler (الحد الأدنى لواجهة برمجة التطبيقات هو 21). عرض مستندات جافا 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 اتبع هذه الخطوات:

  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" هذه المسألة نيابةً عنك.