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

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

کلاس‌هایی برای دانلود رسانه. جهت‌های فلش، جریان داده‌ها را نشان می‌دهند.

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

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

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

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

حذف دانلودها

یک دانلود را می‌توان با ارسال دستور remove به 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() را ارائه می‌دهد که فقط وضعیت دانلودهای فعلی (یعنی دانلودهای تکمیل نشده یا ناموفق) را برمی‌گرداند. این متد برای به‌روزرسانی اعلان‌ها و سایر اجزای رابط کاربری که پیشرفت و وضعیت دانلودهای فعلی را نمایش می‌دهند، مفید است.

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

شما می‌توانید یک شنونده (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 دریافت کرد.

پیکربندی مدیاسورس

مثال قبلی، کش دانلود را برای پخش تمام 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.Factory یک DownloadHelper بسازید. helper را آماده کنید و منتظر فراخوانی مجدد بمانید.

    کاتلین

    val downloadHelper =
         DownloadHelper.Factory()
          .setRenderersFactory(DefaultRenderersFactory(context))
          .setDataSourceFactory(dataSourceFactory)
          .create(MediaItem.fromUri(contentUri))
    downloadHelper.prepare(callback)

    جاوا

    DownloadHelper downloadHelper =
       new DownloadHelper.Factory()
            .setRenderersFactory(new DefaultRenderersFactory(context))
            .setDataSourceFactory(dataSourceFactory)
            .create(MediaItem.fromUri(contentUri));
    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 این کار را برای شما انجام می‌دهد.