ExoPlayer to otwarta biblioteka odtwarzacza multimedialnego dla Android, opracowana przez Google jako alternatywa dla wbudowanego MediaPlayer. ExoPlayer zapewnia odtwarzanie audio i wideo w różnych formatach, w tym DASH, HLS, SmoothStreaming i zwykłych plikach multimedialnych, poprzez jednolity API z obsługą streamingu adaptacyjnego. Według Google I/O (2024), ExoPlayer jest używany w ponad 60 000 aplikacji Android, w tym YouTube, Google TV i Netflix.
Najważniejsze
ExoPlayer to biblioteka do odtwarzania multimediów na Android z otwartym kodem źródłowym (Apache 2.0), której pierwsza wersja została wydana przez Google w 2014 roku. W przeciwieństwie do wbudowanego MediaPlayer, który jest cienką otoczką wokół systemowego kodeka multimedialnego (MediaCodec), ExoPlayer jest zaimplementowany w całości na poziomie Java/Kotlin, co daje programistom pełną kontrolę nad każdym etapem odtwarzania.
Architektonicznie ExoPlayer jest zbudowany na zasadzie modułowej: każdy aspekt odtwarzania jest realizowany przez osobny komponent. Główny moduł (exoplayer-core) zawiera odtwarzacz, rendery, selektory ścieżek i zarządzanie buforem. Dodatkowe moduły (exoplayer-dash, exoplayer-hls, exoplayer-rtsp, exoplayer-smoothstreaming) dodają obsługę odpowiednich protokołów streamingu. Taka architektura pozwala aplikacjom podłączać tylko niezbędne komponenty, minimalizując rozmiar APK.
ExoPlayer 2 (obecna główna wersja) została wydana w 2018 roku i przyniosła gruntowną przebudowę API: wprowadzenie Player Interface, obsługę Compose poprzez AndroidX Media3, ulepszone przetwarzanie błędów i adaptacyjne algorytmy wyboru ścieżek. Począwszy od wersji 2.19 (2024), ExoPlayer stał się częścią biblioteki AndroidX Media, co uprościło integrację i aktualizację poprzez standardowy menedżer zależności Android.
U podstaw ExoPlayer leży komponent ExoPlayerImpl, który zarządza cyklem życia odtwarzania. Zawiera on MediaSource (źródło multimediów), TrackSelector (wybór ścieżek), LoadControl (zarządzanie buforowaniem) i listę Renderer (rendery dla wideo, audio, napisów i metadanych). Każdy komponent może zostać zastąpiony własną implementacją poprzez Builder lub metody fabryczne.
Do renderowania wideo ExoPlayer używa SurfaceView, TextureView lub SphericalSurfaceView (dla wideo 360 stopni). Zalecanym wariantem jest SurfaceView, ponieważ zapewnia przyspieszenie sprzętowe i minimalne zużycie energii. Dla Jetpack Compose biblioteka AndroidX Media3 udostępnia komponent `AndroidView`, opakowujący SurfaceView, oraz eksperymentalny `Media3Compose` z natywną integracją Compose.
ExoPlayer oferuje szeroki zestaw funkcji wykraczających poza zwykły odtwarzacz multimedialny. Główne możliwości obejmują obsługę wszystkich popularnych protokołów streamingu, elastyczny system DRM, adaptacyjne przełączanie bitrate'u (ABR) i narzędzia analityki odtwarzania.
| Funkcja | Opis | Moduł |
|---|---|---|
| DASH | Adaptacyjny streaming zgodny ze standardem MPEG | exoplayer-dash |
| HLS | HTTP Live Streaming od Apple | exoplayer-hls |
| SmoothStreaming | Adaptacyjny streaming od Microsoft | exoplayer-smoothstreaming |
| RTSP | Real-Time Streaming Protocol dla transmisji na żywo | exoplayer-rtsp |
| Widevine | Ochrona treści DRM | exoplayer-drm |
| Concatenation | Łączenie wielu plików multimedialnych | exoplayer-core |
| Ads | Integracja IMA SDK dla reklam | exoplayer-ima |
ABR (Adaptive Bitrate) w ExoPlayer jest zaimplementowany poprzez interfejs AdaptiveTrackSelection. Domyślnie używany jest AdaptiveTrackSelectionFactory, który tworzy DefaultAdaptiveTrackSelection — zrównoważony algorytm uwzględniający przepustowość sieci i rozmiar bufora. Dla specyficznych scenariuszy można podłączyć alternatywne implementacje: BOLA (minimalizacja przełączeń), RandomAdaptiveTrackSelection (testowanie) lub własną implementację poprzez interfejs TrackSelection.
BandwidthMeter to kolejny kluczowy komponent odpowiedzialny za pomiar przepustowości sieci. Domyślnie używany jest DefaultBandwidthMeter, który zbiera statystyki ze wszystkich żądań HTTP odtwarzacza. Programista może go rozszerzyć, aby uwzględnić dodatkowe czynniki: typ sieci (Wi-Fi, 4G, 5G), sygnał stacji bazowej lub koszt transmisji dla użytkownika.
ExoPlayer obsługuje Widevine (L1, L3), PlayReady i ClearKey DRM. Uzyskiwanie licencji konfiguruje się poprzez DrmSessionManager, który współpracuje z serwerem licencji. Dla wideo premium (4K HDR) wymagane jest sprzętowe wsparcie Widevine L1, które zapewnia ochronę na poziomie chipa i zapobiega przechwytywaniu ekranu. Wybór systemu DRM zależy od docelowej platformy: Widevine jest używany na Android i Chromecast, PlayReady na Xbox i Windows, FairPlay na iOS i Apple TV. ExoPlayer automatycznie określa dostępny system DRM na podstawie pliku MPD i nie wymaga ręcznej konfiguracji dla podstawowych scenariuszy.
Podłączenie ExoPlayer do projektu Android wykonuje się przez zależności Gradle. Począwszy od wersji 2.19, ExoPlayer wchodzi w skład AndroidX Media (androidx.media3), co zapewnia zgodność z Jetpack Compose i innymi komponentami AndroidX. Omówimy integrację krok po kroku w Kotlin.
Dodaj moduły ExoPlayer do pliku build.gradle (Module). Minimalny zestaw obejmuje core, ui i moduł dla wymaganego protokołu streamingu. Do odtwarzania treści DASH potrzebny jest exoplayer-dash, do HLS — exoplayer-hls.
dependencies {
implementation "androidx.media3:media3-exoplayer:1.5.0"
implementation "androidx.media3:media3-ui:1.5.0"
implementation "androidx.media3:media3-exoplayer-dash:1.5.0"
implementation "androidx.media3:media3-exoplayer-hls:1.5.0"
}
Tworzenie i konfiguracja odtwarzacza w Activity lub Fragment. ExoPlayer używa PlayerView (lub StyledPlayerView do dostosowania) do wyświetlania wideo i wbudowanych elementów sterowania. Minimalna konfiguracja obejmuje utworzenie odtwarzacza przez ExoPlayer.Builder i przekazanie MediaItem z URI źródła.
class PlayerActivity : AppCompatActivity() {
private var player: ExoPlayer? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_player)
player = ExoPlayer.Builder(this).build()
val playerView = findViewById<StyledPlayerView>(R.id.playerView)
playerView.player = player
val mediaItem = MediaItem
.Builder()
.setUri("https://example.com/video.mp4")
.build()
player?.setMediaItem(mediaItem)
player?.prepare()
}
override fun onDestroy() {
player?.release()
super.onDestroy()
}
}
Dla aplikacji produkcyjnych niezbędna jest obsługa możliwych błędów odtwarzania: awarie sieci, nieobsługiwane formaty, błędy licencjonowania DRM. ExoPlayer udostępnia interfejs Player.Listener z metodami onPlayerError, onPlaybackStateChanged i onPlayWhenReadyChanged. Zaleca się wyświetlanie użytkownikowi informacyjnych komunikatów o błędach z możliwością ponowienia.
Wybór między ExoPlayer a wbudowanym MediaPlayer to częste pytanie przy tworzeniu aplikacji Android. MediaPlayer jest częścią Android SDK od wersji 1.0, podczas gdy ExoPlayer to biblioteka wymagająca podłączenia przez Gradle. Omówimy ich różnice według kluczowych kryteriów.
| Kryterium | ExoPlayer | MediaPlayer |
|---|---|---|
| Architektura | Modułowa, w całości w Java/Kotlin | Otoczka wokół systemowych komponentów C++ |
| Streaming | DASH, HLS, SmoothStreaming, RTSP | Tylko pliki lokalne i HLS (Android 9+) |
| Dostosowanie | Pełne (każdy komponent można zastąpić) | Minimalne (tylko parametry) |
| Aktualizacje | Przez Google Play lub APK (niezależnie od systemu) | Tylko przez aktualizację Android (OTA) |
| Rozmiar APK | +1-3 MB (w zależności od modułów) | 0 (część systemu) |
| Min SDK | API 16+ (Android 4.1) | API 1+ |
| DRM | Widevine, PlayReady, ClearKey | Widevine (ograniczenie) |
ExoPlayer wygrywa w scenariuszach wymagających streamingu (DASH, HLS), dostosowania UI, obsługi DRM i niezależności od wersji Android. MediaPlayer nadaje się do prostych aplikacji odtwarzających lokalne pliki multimedialne, gdzie rozmiar APK jest krytyczny i nie ma wymagań dotyczących streamingu adaptacyjnego. Dla nowoczesnych aplikacji z treściami z sieci ExoPlayer jest jednoznacznym wyborem.
Wyjątek — aplikacje z minimalnym SDK poniżej API 16 (Android 4.1), których praktycznie nie ma już na rynku. Dla wszystkich aplikacji z targetSdk 33+ i minimalnym SDK 21+ ExoPlayer jest nie tylko preferowany, ale wręcz niezbędny do obsługi nowoczesnych formatów, takich jak AV1 i Dolby Vision poprzez odpowiednie rozszerzenia.
Dla aplikacji produkcyjnych podstawowa integracja ExoPlayer często wymaga dodatkowej konfiguracji: zarządzanie buforowaniem, optymalizacja buforowania dla sieci mobilnych, integracja z analityką i dostosowanie wyboru ścieżek audio. Omówimy zaawansowane scenariusze użycia.
CacheDataSourceFactory pozwala buforować pobrane segmenty w lokalnym magazynie, co oszczędza transfer przy ponownym oglądaniu i zapewnia odtwarzanie offline. ExoPlayer używa SimpleCache z biblioteki exoplayer-cas — buforowanie plikowe z obsługą LRU-ewakuacji i limitów rozmiaru.
val cache = SimpleCache(
cacheDir,
LeastRecentlyUsedCacheEvictor(50 * 1024 * 1024),
AppDatabaseProvider(this)
)
val cacheDataSourceFactory = CacheDataSource
.Factory()
.setCache(cache)
val player = ExoPlayer.Builder(this)
.setMediaSourceFactory(
DefaultMediaSourceFactory(cacheDataSourceFactory)
)
.build()
W aplikacjach mobilnych kluczowa jest obsługa przełączania między Wi-Fi a siecią komórkową. ExoPlayer udostępnia ConnectivityManager do śledzenia stanu sieci. W przypadku utraty połączenia zaleca się nie zatrzymywać odtwarzacza, ale przejść do stanu PAUSED z zachowaniem pozycji. Po przywróceniu połączenia — wznowić odtwarzanie z przeładowaniem segmentów.
Wraz z wydaniem AndroidX Media3 biblioteka otrzymała eksperymentalne wsparcie dla Jetpack Compose. Komponent `AndroidView` opakowuje SurfaceView lub PlayerView, zapewniając zgodność z hierarchią Compose. Do głębszej integracji używana jest biblioteka `androidx.media3:media3-ui-compose`, udostępniająca kompatybilne z Compose elementy sterowania odtwarzaczem.
@Composable
fun VideoPlayer(uri: String) {
val context = LocalContext.current
val player = remember {
ExoPlayer.Builder(context).build()
.also { it.setMediaItem(MediaItem.fromUri(uri)) }
}
DisposableEffect(key = null) {
player.prepare()
onDispose { player.release() }
}
AndroidView(
factory = { StyledPlayerView(context).also { it.player = player } }
)
}
Często zadawane pytania
ExoPlayer obsługuje Android API 16+ (Android 4.1 Jelly Bean). Jednak dla DASH i HLS z segmentami fMP4 zaleca się API 21+ (Android 5.0 Lollipop). Moduły DRM (Widevine) wymagają API 19+, a sprzętowe dekodowanie HEVC jest dostępne od API 21.
Tak, ExoPlayer doskonale nadaje się do audio. Do odtwarzania samego dźwięku nie jest wymagany SurfaceView ani PlayerView. ExoPlayer automatycznie wybierze renderer audio i będzie działać w tle. Do odtwarzania w tle zaleca się używanie MediaSessionService do integracji z systemowymi elementami sterowania.
TrackSelector w ExoPlayer zarządza wyborem ścieżek audio. Do przełączania użyj player.getCurrentTracks().groups i player.setTrackSelectionParameters() z podaniem preferowanego języka audio. Dla strumieni DASH i HLS z wieloma ścieżkami audio przełączanie odbywa się bez przerywania odtwarzania.
Tak, ExoPlayer obsługuje wbudowane (w kontenerze MP4, WebM) i zewnętrzne napisy w formatach TTML, SRT, VTT i CEA-608. Do zewnętrznych napisów używany jest SingleSampleMediaSource lub MergingMediaSource do połączenia ze strumieniem wideo. Wyświetlanie stylizowanych napisów konfiguruje się przez CaptionStyleCompat.
Do Low-Latency streamingu skonfiguruj LoadControl z mniejszymi docelowymi wartościami bufora: użyj DefaultLoadControl.Builder().setTargetBufferBytes(1024 * 512).setBufferDurationsMs(500, 2000, 500, 1000) dla HLS LL i DASH LL. Dodatkowo ustaw minPlaybackSpeed = 1.02 dla przyspieszonego wypełniania bufora przy opóźnieniu względem transmisji na żywo.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również