ExoPlayer는 오프라인 재생을 위해 미디어를 다운로드하는 기능을 제공합니다. 대부분의 사용 사례에서는 앱이 백그라운드에 있을 때에도 다운로드가 계속 진행되는 것이 바람직합니다. 이러한 사용 사례의 경우 앱은 DownloadService를 하위 클래스로 만들고 서비스에 명령을 전송하여 다운로드를 추가, 제거, 제어해야 합니다. 다음 다이어그램은 관련된 주요 클래스를 보여줍니다.
DownloadService:DownloadManager를 래핑하고 여기에 명령을 전달합니다. 이 서비스를 사용하면 앱이 백그라운드에 있을 때에도DownloadManager가 계속 실행될 수 있습니다.DownloadManager: 여러 다운로드를 관리하고DownloadIndex에서 다운로드 상태를 로드(및 저장)하고, 네트워크 연결 등의 요구 사항에 따라 다운로드를 시작 및 중지합니다. 콘텐츠를 다운로드하려면 관리자는 일반적으로HttpDataSource에서 다운로드되는 데이터를 읽고Cache에 씁니다.DownloadIndex: 다운로드 상태를 유지합니다.
DownloadService 만들기
DownloadService를 만들려면 이를 하위 클래스화하고 추상 메서드를 구현합니다.
getDownloadManager(): 사용할DownloadManager을 반환합니다.getScheduler(): 보류 중인 다운로드 진행에 필요한 요구 사항이 충족되면 서비스를 다시 시작할 수 있는 선택적Scheduler를 반환합니다. ExoPlayer는 다음 구현을 제공합니다.PlatformScheduler: JobScheduler를 사용합니다 (최소 API는 21). 앱 권한 요구 사항은 PlatformScheduler javadoc을 참조하세요.- WorkManager를 사용하는
WorkManagerScheduler.
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 데모 앱의 DemoDownloadService 및 AndroidManifest.xml을 참고하세요.
DownloadManager 만들기
다음 코드 스니펫은 DownloadService에서 getDownloadManager()에 의해 반환될 수 있는 DownloadManager를 인스턴스화하는 방법을 보여줍니다.
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는 콘텐츠의 고유 식별자입니다. 간단한 경우 contentUri를 contentId로 사용할 수도 있지만, 앱은 사용 사례에 가장 적합한 ID 체계를 자유롭게 사용할 수 있습니다. DownloadRequest.Builder에는 몇 가지 선택적 세터도 있습니다. 예를 들어, setKeySetId 및 setData는 앱이 다운로드와 연결하려는 DRM 및 사용자 지정 데이터를 각각 설정하는 데 사용할 수 있습니다. 콘텐츠의 MIME 유형은 contentUri에서 콘텐츠 유형을 추론할 수 없는 경우에 대한 힌트로 setMimeType를 사용하여 지정할 수도 있습니다.
생성된 후 요청을 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에 삭제 명령어를 전송하여 다운로드를 삭제할 수 있습니다. 여기서 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는 다운로드가 중지되지 않는다는 것을 의미하는 특수 값입니다). 다운로드를 중단하는 데 여러 가지 이유가 있는 앱은 각 다운로드가 중단된 이유를 추적하기 위해 서로 다른 값을 사용할 수 있습니다. 모든 다운로드의 중지 이유를 설정하고 지우는 것은 단일 다운로드의 중지 이유를 설정하고 지우는 것과 동일한 방식으로 작동합니다. 단, contentId는 null로 설정해야 합니다.
다운로드에 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 상태가 됩니다.
다운로드 쿼리
DownloadManager의 DownloadIndex는 완료되었거나 실패한 다운로드를 포함하여 모든 다운로드의 상태를 쿼리할 수 있습니다. DownloadIndex는 DownloadManager.getDownloadIndex()를 호출하여 얻을 수 있습니다. 모든 다운로드를 반복하는 커서는 DownloadIndex.getDownloads()를 호출하여 얻을 수 있습니다. 또는 DownloadIndex.getDownload()를 호출하여 단일 다운로드 상태를 쿼리할 수 있습니다.
DownloadManager는 현재 다운로드 상태 (즉, 완료되지 않았거나 실패한)만 반환하는 DownloadManager.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에서 데이터를 읽는다는 점이 다릅니다.
다운로드한 콘텐츠를 재생하려면 다운로드에 사용된 것과 동일한 Cache 인스턴스를 사용하여 CacheDataSource.Factory를 만들고 플레이어를 빌드할 때 이를 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를 플레이어에 전달하기만 하면 됩니다. Download.request.toMediaItem를 사용하여 Download에서 MediaItem를 얻을 수 있고, 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의 일반적인 사용 단계는 다음과 같습니다.
DownloadHelper.Factory인스턴스를 사용하여DownloadHelper를 빌드합니다. 도우미를 준비하고 콜백을 기다립니다.Kotlin
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);
- 선택적으로
getMappedTrackInfo및getTrackSelections를 사용하여 기본 선택된 트랙을 검사하고clearTrackSelections,replaceTrackSelections및addTrackSelection를 사용하여 조정합니다. getDownloadRequest를 호출하여 선택한 트랙에 대한DownloadRequest를 만듭니다. 위에서 설명한 대로 다운로드를 추가하려면 요청을DownloadService에 전달하면 됩니다.release()를 사용하여 도우미를 해제합니다.
다운로드한 적응형 콘텐츠를 재생하려면 위에서 설명한 대로 플레이어를 구성하고 해당 MediaItem를 전달해야 합니다.
MediaItem를 빌드할 때 MediaItem.localConfiguration.streamKeys을 DownloadRequest의 트랙과 일치하도록 설정해야 플레이어가 다운로드된 트랙의 하위 집합만 재생하려고 합니다. Download.request.toMediaItem와 DownloadRequest.toMediaItem를 사용하여 MediaItem를 빌드하면 이 문제가 해결됩니다.