ExoPlayer zapewnia funkcjonalność umożliwiającą pobieranie multimediów w celu odtwarzania ich w trybie offline. W większości przypadków pobieranie powinno być kontynuowane nawet wtedy, gdy aplikacja działa w tle. W takich przypadkach aplikacja powinna utworzyć podklasę DownloadService i wysyłać polecenia do usługi w celu dodawania, usuwania i kontrolowania pobrań. Poniższy diagram przedstawia główne klasy, które są zaangażowane w ten proces.
DownloadService: OpakowujeDownloadManageri przekazuje do niego polecenia. Usługa umożliwia działanieDownloadManagernawet wtedy, gdy aplikacja działa w tle.DownloadManager: Zarządza wieloma pobraniami, ładując (i przechowując) ich stany z (i do)DownloadIndex, rozpoczynając i zatrzymując pobieranie na podstawie wymagań, takich jak łączność sieciowa itd. Aby pobrać zawartość, menedżer zazwyczaj odczyta dane pobierane zHttpDataSourcei zapisze je wCache.DownloadIndex: Utrwala stany pobranych plików.
Tworzenie usługi pobierania
Aby utworzyć DownloadService, utwórz jego podklasę i zaimplementuj jego metody abstrakcyjne:
getDownloadManager(): ZwracaDownloadManager, który ma zostać użyty.getScheduler(): Zwraca opcjonalnyScheduler, który może ponownie uruchomić usługę, gdy zostaną spełnione wymagania dotyczące kontynuacji oczekujących pobrań. ExoPlayer udostępnia te implementacje:PlatformScheduler, który używa JobScheduler (minimalna wersja API to 21). Wymagania dotyczące uprawnień aplikacji można znaleźć w dokumentacji Javadoc PlatformScheduler.WorkManagerScheduler, który używa WorkManager.
getForegroundNotification(): zwraca powiadomienie, które ma być wyświetlane, gdy usługa działa na pierwszym planie. Możesz użyćDownloadNotificationHelper.buildProgressNotification, aby utworzyć powiadomienie w domyślnym stylu.
Na koniec zdefiniuj usługę w pliku 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>
Konkretny przykład znajdziesz w DemoDownloadService i AndroidManifest.xml w aplikacji demonstracyjnej ExoPlayera.
Tworzenie obiektu DownloadManager
Ten fragment kodu pokazuje, jak utworzyć instancję DownloadManager, która może być zwracana przez getDownloadManager() w DownloadService:
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);
Konkretny przykład znajdziesz w sekcji DemoUtil w aplikacji demonstracyjnej.
Dodawanie pobierania
Aby dodać plik do pobrania, utwórz DownloadRequest i wyślij go do DownloadService. W przypadku strumieni adaptacyjnych użyj DownloadHelper, aby ułatwić
utworzenie DownloadRequest. Ten przykład pokazuje, jak utworzyć żądanie pobierania:
Kotlin
val downloadRequest = DownloadRequest.Builder(contentId, contentUri).build()
Java
DownloadRequest downloadRequest = new DownloadRequest.Builder(contentId, contentUri).build();
W tym przykładzie contentId jest unikatowym identyfikatorem zawartości. W prostych przypadkach contentUri można często używać jako contentId, jednak aplikacje mogą swobodnie używać dowolnego schematu identyfikatorów, który najlepiej odpowiada ich potrzebom. DownloadRequest.Builder ma również kilka opcjonalnych setterów. Na przykład setKeySetId i setData można użyć do ustawienia DRM i niestandardowych danych, które aplikacja chce powiązać z pobieraniem. Typ MIME zawartości można również określić za pomocą setMimeType, co stanowi wskazówkę w przypadkach, gdy typu zawartości nie można wywnioskować z contentUri.
Po utworzeniu żądanie można wysłać do DownloadService w celu dodania pobierania:
Kotlin
DownloadService.sendAddDownload( context, MyDownloadService::class.java, downloadRequest, /* foreground= */ false )
Java
DownloadService.sendAddDownload( context, MyDownloadService.class, downloadRequest, /* foreground= */ false);
W tym przykładzie MyDownloadService to podklasa DownloadService aplikacji, a parametr foreground określa, czy usługa zostanie uruchomiona na pierwszym planie. Jeśli aplikacja jest już na pierwszym planie, parametr foreground powinien być zwykle ustawiony na false, ponieważ DownloadService przejdzie na pierwszy plan, jeśli uzna, że ma coś do zrobienia.
Usuwam pobrane
Pobieranie można usunąć, wysyłając polecenie usuwania do DownloadService, gdzie contentId identyfikuje pobieranie do usunięcia:
Kotlin
DownloadService.sendRemoveDownload( context, MyDownloadService::class.java, contentId, /* foreground= */ false )
Java
DownloadService.sendRemoveDownload( context, MyDownloadService.class, contentId, /* foreground= */ false);
Możesz też usunąć wszystkie pobrane dane za pomocą funkcji DownloadService.sendRemoveAllDownloads.
Rozpoczynanie i zatrzymywanie pobierania
Pobieranie będzie kontynuowane tylko wtedy, gdy spełnione zostaną cztery warunki:
- Pobieranie nie ma powodu zatrzymania.
- Pobieranie nie jest wstrzymywane.
- Wymagania dotyczące dalszego pobierania są spełnione. Wymagania mogą określać ograniczenia dotyczące dozwolonych typów sieci, a także to, czy urządzenie powinno być bezczynne, czy podłączone do ładowarki.
- Maksymalna liczba równoległych pobrań nie została przekroczona.
Wszystkimi tymi warunkami można sterować, wysyłając polecenia do DownloadService.
Ustawianie i usuwanie przyczyn zatrzymania pobierania
Można ustawić powód zatrzymania jednego lub wszystkich pobrań:
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 może być dowolną wartością różną od zera (Download.STOP_REASON_NONE = 0 to specjalna wartość oznaczająca, że pobieranie nie jest zatrzymywane). Aplikacje, które mają wiele powodów zatrzymania pobierania, mogą używać różnych wartości, aby śledzić przyczynę zatrzymania każdego pobierania. Ustawianie i czyszczenie przyczyny zatrzymania dla wszystkich pobrań działa w taki sam sposób jak ustawianie i czyszczenie przyczyny zatrzymania dla pojedynczego pobierania, z tą różnicą, że contentId należy ustawić na null.
Jeśli powód zatrzymania pobierania jest różny od zera, pobieranie będzie miało stan Download.STATE_STOPPED. Powody zatrzymania są zapisywane w DownloadIndex i zostają zachowane, jeśli proces aplikacji zostanie zamknięty i uruchomiony ponownie.
Wstrzymywanie i wznawianie wszystkich pobrań
Każde pobieranie można wstrzymać i wznowić w następujący sposób:
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);
Po wstrzymaniu pobierania będzie ono miało stan Download.STATE_QUEUED.
W przeciwieństwie do ustawiania przyczyn zatrzymania, to podejście nie powoduje utrwalenia żadnych zmian stanu. Ma to wpływ tylko na stan środowiska wykonawczego DownloadManager.
Ustawianie wymagań dotyczących postępu pobierania
Requirements można użyć do określenia ograniczeń, które muszą zostać spełnione, aby można było pobrać pliki. Wymagania można ustawić, wywołując funkcję
DownloadManager.setRequirements() podczas tworzenia funkcji DownloadManager, jak w przykładzie powyżej. Można je też zmieniać dynamicznie, wysyłając polecenie do 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);
Jeśli pobieranie nie może być kontynuowane, ponieważ nie są spełnione wymagania, będzie ono w stanie Download.STATE_QUEUED. Zapytanie dotyczące niespełnionych wymagań możesz wysłać za pomocą DownloadManager.getNotMetRequirements().
Ustawianie maksymalnej liczby równoległych pobrań
Maksymalną liczbę równoległych pobrań można ustawić, wywołując funkcję
DownloadManager.setMaxParallelDownloads(). Zwykle robi się to podczas tworzenia DownloadManager, jak w przykładzie powyżej.
Jeśli nie można kontynuować pobierania, ponieważ osiągnięto już maksymalną liczbę równoległych operacji pobierania, będzie ono miało stan Download.STATE_QUEUED.
Wykonywanie zapytań dotyczących pobranych plików
Możesz wysłać zapytanie do DownloadIndex DownloadManager, aby sprawdzić stan wszystkich pobrań, w tym tych, które zostały zakończone lub nie powiodły się. DownloadIndex
Można je uzyskać, dzwoniąc pod numer DownloadManager.getDownloadIndex(). Kursor, który iteruje po wszystkich pobranych plikach, można uzyskać, wywołując funkcję DownloadIndex.getDownloads(). Stan pojedynczego pobierania można też sprawdzić, wywołując funkcję DownloadIndex.getDownload().
DownloadManager udostępnia też DownloadManager.getCurrentDownloads(), które zwraca stan tylko bieżących (czyli nieukończonych lub nieudanych) pobrań. Ta metoda jest przydatna do aktualizowania powiadomień i innych komponentów interfejsu, które wyświetlają postęp i stan bieżących pobrań.
Słuchanie pobranych plików
Możesz dodać detektor do DownloadManager, aby otrzymywać powiadomienia o zmianach stanu bieżących pobrań:
Kotlin
downloadManager.addListener( object : DownloadManager.Listener { // Override methods of interest here. } )
Java
downloadManager.addListener( new DownloadManager.Listener() { // Override methods of interest here. });
Konkretny przykład znajdziesz w klasie DownloadTracker w DownloadManagerListener w aplikacji demonstracyjnej.
Odtwarzanie pobranych treści
Odtwarzanie pobranych treści jest podobne do odtwarzania treści online, z tą różnicą, że dane są odczytywane z pobranego pliku Cache, a nie z sieci.
Aby odtworzyć pobraną zawartość, utwórz CacheDataSource.Factory przy użyciu tej samej instancji Cache, która została użyta do pobrania, i wstrzyknij ją do DefaultMediaSourceFactory podczas tworzenia odtwarzacza:
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();
Jeśli ta sama instancja odtwarzacza będzie również używana do odtwarzania niepobranych treści, należy skonfigurować CacheDataSource.Factory jako tylko do odczytu, aby uniknąć pobierania tych treści podczas odtwarzania.
Po skonfigurowaniu odtwarzacza za pomocą CacheDataSource.Factory będzie on miał dostęp do pobranej zawartości i będzie mógł ją odtwarzać. Aby odtworzyć pobrany plik, wystarczy przesłać odpowiedni MediaItem do odtwarzacza. MediaItem można uzyskać z Download za pomocą Download.request.toMediaItem lub bezpośrednio z DownloadRequest za pomocą DownloadRequest.toMediaItem.
Konfiguracja MediaSource
W powyższym przykładzie pamięć podręczna pobierania jest dostępna do odtwarzania wszystkich MediaItem. Możesz też udostępnić pamięć podręczną pobierania poszczególnym instancjom MediaSource, które można przekazać bezpośrednio do odtwarzacza:
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();
Pobieranie i odtwarzanie strumieni adaptacyjnych
Strumienie adaptacyjne (np. DASH, SmoothStreaming i HLS) zwykle zawierają wiele ścieżek multimedialnych. Często istnieje wiele ścieżek zawierających te same treści w różnej jakości (np. ścieżki wideo w jakości SD, HD i 4K). Mogą również istnieć wiele utworów tego samego typu, ale zawierających różną treść (np. wiele utworów audio w różnych językach).
W przypadku odtwarzania strumieniowego można użyć selektora utworów, aby wybrać, które utwory mają zostać odtworzone. Podobnie, przy pobieraniu można użyć DownloadHelper, aby wybrać, które utwory zostaną pobrane. Typowe zastosowanie DownloadHelper wygląda następująco:
- Zbuduj
DownloadHelperprzy użyciu instancjiDownloadHelper.Factory. Przygotuj pomocnika i czekaj na oddzwonienie.Kotlin
val downloadHelper = DownloadHelper.Factory() .setRenderersFactory(DefaultRenderersFactory(context)) .setDataSourceFactory(dataSourceFactory) .create(MediaItem.fromUri(contentUri)) downloadHelper.prepare(callback)
Java
DownloadHelper downloadHelper = new DownloadHelper.Factory() .setRenderersFactory(new DefaultRenderersFactory(context)) .setDataSourceFactory(dataSourceFactory) .create(MediaItem.fromUri(contentUri)); downloadHelper.prepare(callback);
- Opcjonalnie możesz sprawdzić domyślnie wybrane ścieżki za pomocą ikon
getMappedTrackInfoigetTrackSelectionsoraz wprowadzić zmiany za pomocą ikonclearTrackSelections,replaceTrackSelectionsiaddTrackSelection. - Utwórz
DownloadRequestdla wybranych utworów, wywołującgetDownloadRequest. Żądanie można przekazać doDownloadServicew celu dodania pobierania, jak opisano powyżej. - Zwolnij metodę pomocniczą za pomocą
release().
Aby odtworzyć pobraną zawartość adaptacyjną, należy skonfigurować odtwarzacz i przekazać odpowiedni parametr MediaItem, jak opisano powyżej.
Podczas tworzenia pliku MediaItem należy ustawić parametr MediaItem.localConfiguration.streamKeys tak, aby odpowiadał parametrom w pliku DownloadRequest. W ten sposób odtwarzacz będzie próbował odtworzyć tylko ten podzbiór utworów, który został pobrany. Użycie Download.request.toMediaItem i DownloadRequest.toMediaItem do zbudowania MediaItem zajmie się tym za Ciebie.