미디어 다운로드

ExoPlayer는 오프라인 재생을 위해 미디어를 다운로드하는 기능을 제공합니다. 최대 사용 사례의 경우 앱이 있습니다. 이러한 사용 사례의 경우 앱은 DownloadService를 서브클래스로 분류하고 서비스에 명령어를 전송해 다운로드를 추가, 삭제 및 제어합니다. 이 다음 다이어그램은 관련된 기본 클래스를 보여줍니다.

미디어를 다운로드하기 위한 클래스입니다. 화살표 방향은 데이터의 흐름을 나타냅니다.

  • DownloadService: DownloadManager를 래핑하고 명령어를 전달합니다. 이 서비스를 사용하면 앱이 다음 환경에 있더라도 DownloadManager가 계속 실행될 수 있습니다. 만들 수 있습니다.
  • DownloadManager: 여러 다운로드를 관리하고 로드 (및 저장)합니다. 상태를 기반으로 하여 다운로드를 시작하고 중단하며, 이때 DownloadIndex에서 요구사항(예: 네트워크 연결 등)을 기반으로 합니다. 이 관리자는 일반적으로 HttpDataSource)하고 Cache에 씁니다.
  • DownloadIndex: 다운로드 상태를 유지합니다.

DownloadService 만들기

DownloadService를 만들려면 서브클래스를 만들고 서브클래스를 구현합니다. 추상 메서드:

  • getDownloadManager(): 사용할 DownloadManager를 반환합니다.
  • getScheduler(): 선택적 Scheduler를 반환하며, 이를 다시 시작할 수 있습니다. 서비스 ExoPlayer는 다음 구현을 제공합니다. <ph type="x-smartling-placeholder">
      </ph>
    • 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>

ExoPlayer에서 DemoDownloadServiceAndroidManifest.xml를 참고하세요. 데모 앱에서 구체적인 예를 확인해 보세요.

DownloadManager 생성

다음 코드 스니펫은 DownloadManager를 인스턴스화하는 방법을 보여줍니다. DownloadServicegetDownloadManager()에서 반환할 수 있습니다.

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

자바

// 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()

자바

DownloadRequest downloadRequest = new DownloadRequest.Builder(contentId, contentUri).build();

이 예에서 contentId는 콘텐츠의 고유 식별자입니다. 간단한 경우 contentUricontentId로 자주 사용할 수 있지만 앱은 무료로 사용할 수 있습니다. 사용 사례에 가장 적합한 ID 스키마입니다. DownloadRequest.Builder의 기타 항목 일부 선택적 setter를 반환합니다. 예를 들어 setKeySetIdsetData를 사용하여 다음을 수행할 수 있습니다. 앱이 다운로드와 연결하려는 DRM 및 맞춤 데이터를 설정하고 각각 1개의 값으로 사용합니다. 콘텐츠의 MIME 유형은 setMimeType를 사용하여 지정할 수도 있습니다. 를 contentUri에서 콘텐츠 유형을 추론할 수 없는 경우를 위한 힌트로 사용합니다.

생성된 후에는 요청을 DownloadService로 전송하여 다운로드:

Kotlin

DownloadService.sendAddDownload(
  context,
  MyDownloadService::class.java,
  downloadRequest,
  /* foreground= */ false
)

자바

DownloadService.sendAddDownload(
    context, MyDownloadService.class, downloadRequest, /* foreground= */ false);

이 예에서 MyDownloadService는 앱의 DownloadService 서브클래스이고 foreground 매개변수는 다음에서 서비스를 시작할지 여부를 제어합니다. 포그라운드에서 실행되는 것으로 간주됩니다. 앱이 이미 포그라운드에 있으면 foreground 매개변수는 일반적으로 false로 설정되어야 합니다. DownloadService가 스스로 포그라운드에 들어가야 할 작업이 있다고 판단하는 경우.

다운로드 항목 삭제 중

DownloadService에 remove 명령어를 전송하여 다운로드를 삭제할 수 있습니다. 여기서 contentId는 삭제할 다운로드를 식별합니다.

Kotlin

DownloadService.sendRemoveDownload(
  context,
  MyDownloadService::class.java,
  contentId,
  /* foreground= */ false
)

자바

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
)

자바

// 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는 0이 아닌 값일 수 있습니다 (Download.STOP_REASON_NONE = 0: 다운로드가 중지되지 않음을 나타내는 특수 값). 앱이 다운로드를 중지하는 이유는 여러 가지가 있는데, 추적에 서로 다른 값을 사용할 수 있습니다. 각 다운로드가 중지된 이유도 알 수 있습니다. 모든 인스턴스의 중지 이유 설정 및 삭제 다운로드는 개별 앱에 대한 중지 이유를 설정하고 삭제하는 것과 contentIdnull로 설정해야 한다는 점을 제외하고 단일 다운로드입니다.

다운로드의 중지 이유가 0이 아닌 경우 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
)

자바

// 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를 만들 때 DownloadManager.setRequirements() 의 예시를 참조하세요. 또한 명령어를 전송하여 동적으로 변경할 수도 있습니다. DownloadService로 변경합니다.

Kotlin

// 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 상태가 됩니다.

다운로드 쿼리

DownloadManagerDownloadIndex를 통해 모든 완료 또는 실패한 다운로드를 포함하여 DownloadIndex DownloadManager.getDownloadIndex()를 호출하여 가져올 수 있습니다. 그런 다음 DownloadIndex.getDownloads() 반면에 단일 다운로드의 상태는 DownloadIndex.getDownload()를 호출하여 쿼리할 수 있습니다.

DownloadManagerDownloadManager.getCurrentDownloads()도 제공합니다. 는 현재 (완료되지 않음 또는 실패) 다운로드의 상태만 반환합니다. 이 메서드는 알림 및 UI 구성요소를 업데이트하는 데 유용합니다. 진행 상황 및 현재 다운로드의 상태를 보여줍니다.

다운로드 항목 듣기

DownloadManager에 리스너를 추가하여 현재 실행 중일 때 알림을 받을 수 있습니다. 다운로드 상태 변경:

Kotlin

downloadManager.addListener(
  object : DownloadManager.Listener { // Override methods of interest here.
  }
)

자바

downloadManager.addListener(
    new DownloadManager.Listener() {
      // Override methods of interest here.
    });

데모 앱의 DownloadTracker 클래스에서 DownloadManagerListener를 참고하세요. 구체적인 예입니다.

다운로드한 콘텐츠 재생

오프라인 저장한 콘텐츠의 재생은 온라인 콘텐츠 재생과 비슷하지만 다음과 같은 점이 다릅니다. 네트워크를 통해서가 아니라 다운로드 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()

자바

// 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.request.toMediaItem를 사용하여 Download에서 가져올 수 있습니다. DownloadRequest.toMediaItem를 사용하여 DownloadRequest에서 직접

MediaSource 구성

앞의 예에서는 다운로드 캐시를 통해 모든 MediaItem 또한 사용자가 컴퓨터에서 다운로드 캐시를 사용할 수 있도록 플레이어에 직접 전달할 수 있는 개별 MediaSource 인스턴스:

Kotlin

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를 빌드합니다. 메서드를 참조하세요. 도우미를 준비하고 콜백을 기다립니다.

    Kotlin

    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, replaceTrackSelectionsaddTrackSelection
  3. 다음을 호출하여 선택한 트랙의 DownloadRequest를 만듭니다. getDownloadRequest입니다. 요청은 DownloadService로 전달되어 위에서 설명한 대로 다운로드를 추가합니다.
  4. release()를 사용하여 도우미를 해제합니다.

다운로드한 적응형 콘텐츠를 재생하려면 플레이어 및 위에서 설명한 대로 상응하는 MediaItem를 전달합니다.

MediaItem 빌드 시 MediaItem.localConfiguration.streamKeys는 다음과 같아야 합니다. DownloadRequest의 값과 일치하도록 플레이어는 오프라인 저장한 트랙의 하위 집합을 재생할 수 있습니다. 사용 Download.request.toMediaItemDownloadRequest.toMediaItem를 사용하여 MediaItem에서 처리해 드립니다.