در حال دانلود رسانه

ExoPlayer قابلیت دانلود رسانه برای پخش آفلاین را فراهم می کند. در بیشتر موارد استفاده، مطلوب است که دانلودها حتی زمانی که برنامه شما در پس‌زمینه است ادامه یابد. برای این موارد استفاده، برنامه شما باید DownloadService را زیر کلاس قرار دهد و دستوراتی را برای افزودن، حذف و کنترل دانلودها به سرویس ارسال کند. نمودار زیر کلاس های اصلی را نشان می دهد که درگیر هستند.

کلاس های دانلود رسانه. جهت های پیکان جریان داده ها را نشان می دهد.

  • DownloadService : یک DownloadManager را می پیچد و دستورات را به آن ارسال می کند. این سرویس به DownloadManager اجازه می دهد حتی زمانی که برنامه در پس زمینه است به کار خود ادامه دهد.
  • DownloadManager : دانلودهای متعدد را مدیریت می کند، حالات آنها را از (و به) DownloadIndex بارگیری (و ذخیره) می کند، دانلودها را بر اساس الزاماتی مانند اتصال به شبکه و غیره شروع و متوقف می کند. برای دانلود محتوا، مدیر معمولاً داده‌های در حال دانلود را از HttpDataSource می‌خواند و آن را در Cache می‌نویسد.
  • DownloadIndex : وضعیت دانلودها را حفظ می کند.

ایجاد سرویس دانلود

برای ایجاد یک DownloadService ، آن را زیر کلاس بندی کنید و متدهای انتزاعی آن را پیاده سازی کنید:

  • getDownloadManager() : DownloadManager مورد استفاده را برمی‌گرداند.
  • getScheduler() : یک Scheduler اختیاری را برمی‌گرداند که می‌تواند در صورت برآورده شدن الزامات مورد نیاز برای دانلودهای معلق برای پیشرفت، سرویس را مجدداً راه‌اندازی کند. ExoPlayer این پیاده سازی ها را ارائه می دهد:
    • PlatformScheduler ، که از JobScheduler استفاده می کند (حداقل API 21 است). برای اطلاع از الزامات مجوز برنامه به PlatformScheduler javadocs مراجعه کنید.
    • 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 شما برگردانده شود:

کاتلین

// 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 حذف کنید.

شروع و توقف دانلودها

دانلود فقط در صورتی پیشرفت می کند که چهار شرط وجود داشته باشد:

  • دانلود دلیل توقف ندارد.
  • دانلودها متوقف نمی شوند.
  • الزامات برای دانلودها برای پیشرفت برآورده شده است. الزامات می‌تواند محدودیت‌هایی را در انواع شبکه مجاز مشخص کند، همچنین اینکه آیا دستگاه باید بی‌حرکت باشد یا به شارژر متصل باشد.
  • از حداکثر تعداد دانلودهای موازی تجاوز نمی شود.

همه این شرایط را می توان با ارسال دستورات به 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 می تواند هر مقدار غیر صفر باشد ( Download.STOP_REASON_NONE = 0 یک مقدار ویژه است به این معنی که دانلود متوقف نمی شود). برنامه‌هایی که دلایل متعددی برای توقف دانلود دارند، می‌توانند از مقادیر مختلفی برای پیگیری علت توقف هر بارگیری استفاده کنند. تنظیم و پاک کردن دلیل توقف برای همه دانلودها مانند تنظیم و پاک کردن دلیل توقف برای یک بارگیری عمل می کند، با این تفاوت که contentId باید روی null تنظیم شود.

وقتی دانلودی دلیل توقف غیر صفر داشته باشد، در حالت 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() بدست آورد. سپس با فراخوانی DownloadIndex.getDownloads() می‌توان مکان‌نمایی را که روی همه دانلودها تکرار می‌شود، به دست آورد. از طرف دیگر، وضعیت یک بارگیری را می توان با فراخوانی DownloadIndex.getDownload() پرس و جو کرد.

DownloadManager همچنین DownloadManager.getCurrentDownloads() را ارائه می دهد که فقط وضعیت دانلودهای فعلی (یعنی تکمیل نشده یا ناموفق) را برمی گرداند. این روش برای به‌روزرسانی اعلان‌ها و سایر مؤلفه‌های رابط کاربری که پیشرفت و وضعیت دانلودهای فعلی را نمایش می‌دهند مفید است.

گوش دادن به دانلودها

می‌توانید یک شنونده به 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 این مراحل را دنبال می کند:

  1. با استفاده از یکی از متدهای DownloadHelper.forMediaItem یک DownloadHelper بسازید. کمک کننده را آماده کنید و منتظر تماس باشید.

    کاتلین

    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);
    
  2. به صورت اختیاری، آهنگ‌های انتخابی پیش‌فرض را با استفاده از getMappedTrackInfo و getTrackSelections بررسی کنید و با استفاده از clearTrackSelections ، replaceTrackSelections و addTrackSelection تنظیمات را انجام دهید.
  3. با فراخوانی getDownloadRequest یک DownloadRequest برای آهنگ های انتخاب شده ایجاد کنید. همانطور که در بالا توضیح داده شد، می توان درخواست را برای افزودن دانلود به DownloadService ارسال کرد.
  4. کمک کننده را با استفاده از release() رها کنید.

پخش محتوای تطبیقی ​​دانلود شده به پیکربندی پخش‌کننده و ارسال MediaItem مربوطه، همانطور که در بالا توضیح داده شد، نیاز دارد.

هنگام ساخت MediaItem ، MediaItem.localConfiguration.streamKeys باید طوری تنظیم شود که در DownloadRequest مطابقت داشته باشد تا پخش کننده فقط سعی کند زیرمجموعه آهنگ های دانلود شده را پخش کند. استفاده از Download.request.toMediaItem و DownloadRequest.toMediaItem برای ساخت MediaItem این کار را برای شما انجام خواهد داد.