ExoPlayer는 오프라인 재생을 위해 미디어를 다운로드하는 기능을 제공합니다. 대부분의 경우 앱이 백그라운드에 있을 때도 다운로드를 계속하는 것이 좋습니다. 이러한 사용 사례의 경우 앱은 DownloadService
를 서브클래스로 분류하고 서비스에 명령어를 전송하여 다운로드를 추가, 삭제, 제어해야 합니다. 다음 다이어그램은 관련된 기본 클래스를 보여줍니다.
DownloadService
:DownloadManager
를 래핑하고 명령어를 전달합니다. 이 서비스를 사용하면 앱이 백그라운드에 있을 때도DownloadManager
를 계속 실행할 수 있습니다.DownloadManager
: 여러 다운로드를 관리하고DownloadIndex
에서 상태를 로드(및 저장)하고 네트워크 연결과 같은 요구사항에 따라 다운로드를 시작하고 중지합니다. 콘텐츠를 다운로드하기 위해 관리자는 일반적으로HttpDataSource
에서 다운로드 중인 데이터를 읽고Cache
에 씁니다.DownloadIndex
: 다운로드 상태를 유지합니다.
DownloadService 만들기
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>
구체적인 예는 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
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
로 전송합니다. 적응형 스트림의 경우 DownloadRequest
를 빌드하는 데 도움이 되는 DownloadHelper
를 사용합니다. 다음 예는 다운로드 요청을 만드는 방법을 보여줍니다.
Kotlin
val downloadRequest = DownloadRequest.Builder(contentId, contentUri).build()
Java
DownloadRequest downloadRequest = new DownloadRequest.Builder(contentId, contentUri).build();
이 예에서 contentId
는 콘텐츠의 고유 식별자입니다. 간단한 사례에서는 contentUri
를 contentId
로 사용할 수 있는 경우가 많지만 앱은 사용 사례에 가장 적합한 ID 스키마를 자유롭게 사용할 수 있습니다. DownloadRequest.Builder
에는 선택적 setter도 있습니다. 예를 들어 setKeySetId
와 setData
는 각각 앱이 다운로드와 연결할 DRM 및 맞춤 데이터를 설정하는 데 사용할 수 있습니다. 콘텐츠의 MIME 유형을 contentUri
에서 콘텐츠 유형을 추론할 수 없는 경우에 관한 힌트로 setMimeType
를 사용하여 지정할 수도 있습니다.
생성된 후에는 요청을 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
은 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 )
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
를 만들 때 DownloadManager.setRequirements()
를 호출하여 설정할 수 있습니다. 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
상태가 됩니다.
다운로드 쿼리
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. } )
Java
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()
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.request.toMediaItem
를 사용하여 Download
에서, 또는 DownloadRequest.toMediaItem
를 사용하여 DownloadRequest
에서 직접 가져올 수 있습니다.
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)에는 일반적으로 여러 미디어 트랙이 포함됩니다. 동일한 콘텐츠가 서로 다른 품질로 포함된 트랙 (예: SD, HD, 4K 동영상 트랙)이 여러 개 있는 경우가 많습니다. 다른 콘텐츠를 포함하는 동일한 유형의 여러 트랙 (예: 서로 다른 언어의 여러 오디오 트랙)이 있을 수도 있습니다.
스트리밍 재생의 경우 트랙 선택기를 사용하여 재생할 트랙을 선택할 수 있습니다. 마찬가지로 다운로드의 경우 DownloadHelper
를 사용하여 다운로드할 트랙을 선택할 수 있습니다. DownloadHelper
의 일반적인 사용법은 다음과 같습니다.
DownloadHelper.forMediaItem
메서드 중 하나를 사용하여DownloadHelper
를 빌드합니다. 도우미를 준비하고 콜백을 기다립니다.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);
- 원하는 경우
getMappedTrackInfo
및getTrackSelections
를 사용하여 선택된 기본 트랙을 검사하고clearTrackSelections
,replaceTrackSelections
,addTrackSelection
를 사용하여 조정합니다. getDownloadRequest
를 호출하여 선택한 트랙의DownloadRequest
를 만듭니다. 위에서 설명한 대로 요청을DownloadService
에 전달하여 다운로드를 추가할 수 있습니다.release()
를 사용하여 도우미를 해제합니다.
다운로드한 적응형 콘텐츠를 재생하려면 위에서 설명한 대로 플레이어를 구성하고 상응하는 MediaItem
를 전달해야 합니다.
MediaItem
를 빌드할 때 플레이어가 다운로드된 트랙의 하위 집합만 재생하려고 하도록 MediaItem.localConfiguration.streamKeys
는 DownloadRequest
의 항목과 일치하도록 설정해야 합니다. Download.request.toMediaItem
및 DownloadRequest.toMediaItem
를 사용하여 MediaItem
를 빌드하면 이 작업이 자동으로 처리됩니다.