La fruizione di contenuti multimediali rappresenta una parte significativa del tempo trascorso dagli utenti sui dispositivi mobili, il che richiede un'esperienza di riproduzione impeccabile, altamente efficiente e attenta alla durata della batteria. Inoltre, gli utenti si aspettano che queste esperienze si trasferiscano senza problemi da un dispositivo all'altro, dagli smartphone alle TV Living Room e non solo.
Scopri in che modo gli aggiornamenti dell'architettura in Media3 ed ExoPlayer consentono una riproduzione senza interruzioni su diversi fattori di forma. La discussione si concentra sulla creazione di interfacce adattabili con il toolkit UI Media3 Compose, sull'eliminazione del buffering nei feed utilizzando il nuovo PreloadManager e sull'estensione della tua app agli schermi della TV e ad Android XR con CastPlayer e l'audio spaziale.
Trascrizione
Ok.
Ciao a tutti.
Mi chiamo Toni.
Sono un ingegnere del team Android Media.
Insieme a Kristina e Donovan, parleremo di come Jetpack Media3 può aiutarti con tutte le tue esigenze multimediali su Android.
Media3 è la libreria di supporto di Android per tutto ciò che riguarda i contenuti multimediali, che si tratti di riproduzione con ExoPlayer, condivisione di contenuti multimediali con sessione multimediale e Cast, modifica di contenuti multimediali con Transformer o ispezione di contenuti multimediali, come l'estrazione di frame o metadati.
Media3 copre l'intero spazio in un unico gruppo di librerie.
In quanto libreria di supporto, è integrata nella tua app.
Ciò significa che possiamo garantire la coerenza, in modo che lo stesso codice venga eseguito su tutte le versioni del sistema operativo e su tutti i dispositivi.
Inoltre, semplifica l'aggiornabilità.
Ogni correzione di bug che eseguiamo raggiunge l'API 24 e non è legata a nessun ciclo di release del sistema operativo.
Inoltre, trattandosi di una libreria di supporto, possiamo consentire opzioni di personalizzazione molto complete, che non sarebbero possibili nel framework Android.
Inoltre, in quanto libreria su GitHub, siamo innanzitutto open source.
Ciò significa che ogni modifica che apportiamo è immediatamente disponibile su GitHub.
Abbiamo un Issue Tracker attivo e accettiamo richieste di pull.
La cosa principale da sottolineare qui, in tutti questi casi d'uso, è che ci interessano davvero tutti i livelli di dettaglio, dagli elementi UI di alto livello alle semplici API pronte all'uso, ma anche le opzioni di personalizzazione profonda, un ampio supporto per vari formati e standard e le prestazioni e l'efficienza alla base di tutto.
Media3 copre l'intero spazio.
Oggi cercheremo di approfondire alcuni di questi argomenti.
Parleremo del nostro nuovo modulo Compose UI, del nuovo CastPlayer che semplifica ulteriormente la condivisione con la sessione multimediale, di una pipeline di editing rinnovata con nuovi effetti avanzati, di nuovi strumenti per recuperare metadati e frame, di una nuova tecnologia di streaming, in particolare di video e audio Eclipsa, e di una serie di utilità per la qualità dell'esperienza per il precaricamento, lo scrubbing e il basso consumo della batteria.
Ora passiamo direttamente al primo argomento: come Media3 può aiutare a creare elementi UI per i contenuti multimediali.
Di recente abbiamo pubblicato due nuovi moduli: Media3 UI Compose e Media3 UI Compose Material 3.
Qui puoi vedere un composable del player di alto livello, così come è stato lanciato in Media3 1.10 senza ulteriori modifiche.
@Composable
fun videoScreen() {
Player(viewModel.exoPlayer)
}Funziona subito.
Richiede solo un player Media3.
Nell'esempio qui, si tratta di un ExoPlayer, che di solito si trova nel modello di visualizzazione.
ma potrebbe essere qualcos'altro, come un CastPlayer, un controller multimediale o qualsiasi altro player presente nella tua app.
Vale la pena sottolineare che ExoPlayer è la fonte di riferimento.
Contiene tutte le informazioni e il composable del player si occupa del rendering della UI e del collegamento di tutto.
Ma cosa succede se vuoi personalizzare questa UI, nuovi pulsanti, posizioni diverse, stili diversi, overlay di testo?
Il composable del player è costituito da componenti di base.
Puoi sostituirli.
Puoi personalizzare ogni singolo blocco o utilizzarli in modo indipendente altrove nella tua app.
Ad esempio, puoi personalizzare separatamente la parte superiore, quella centrale e bottomControls.
Puoi anche personalizzare l'aspetto dello shutter, che viene mostrato quando i video non sono in riproduzione, o un overlay di errore.
Ma questo è solo il primo livello.
Il centro predefinito e bottomControls hanno anche slot più dettagliati che puoi personalizzare.
Ad esempio, puoi sostituire il pulsante centrale lasciando invariato tutto il resto o aggiungere elementi sopra o sotto lo slider.
Media3 Compose fornisce tutti i componenti di base necessari per farlo.
Ad esempio, abbiamo una serie di pulsanti, un pulsante di riproduzione/pausa, la modalità repeat, la modalità shuffle, il tasto di disattivazione audio e altri.
Poi ci sono gli indicatori di avanzamento, come il cursore e il testo della posizione mostrati sullo schermo.
Sono disponibili anche diversi container e overlay.
Il composable del player di alto livello che ti ho mostrato è un esempio, ma ce ne sono altri, come overlay di errore o sottotitoli e molti altri.
Inoltre, abbiamo altri componenti utili, come questo mini controller.
Puoi inserirlo in un riquadro inferiore mentre l'utente naviga nell'app.
E non abbiamo ancora finito.
Stiamo ampliando attivamente questa raccolta, ad esempio con nuovi controlli di editing, come un cursore di ritaglio, che saranno disponibili a breve.
Ora vediamo questa personalizzazione in azione per capire come vengono utilizzati questi componenti di base.
Player(viewModel.exoPlayer)Supponiamo di voler spostare il testo della posizione e della durata, che attualmente si trova accanto al cursore, sopra di esso.
Player(
player = viewModel.exoPlayer,
bottomControls = {
PlayerDefaults.BottomControls(
player,
left = {}, right = {},
above = {
PositionAndDurationText(
player, separator = " • ")
},
)
},
)Quindi, possiamo eseguire l'override di questi bottomControls.
Possiamo comunque utilizzare il componente predefinito, ma impostiamo gli slot sinistro e destro su vuoto per farli scomparire.
Invece, posizioniamo il testo della posizione e della durata sopra, nello slot superiore, con un separatore personalizzato per renderlo più interessante.
Ora, supponiamo di voler rimuovere completamente l'overlay centerControls e di voler posizionare un pulsante di riproduzione proprio sopra il cursore con un controllo della velocità e un pulsante di disattivazione audio.
Player(
player = viewModel.exoPlayer,
centerControls = {},
bottomControls = { ...
above = {
Row(Modifier.fillMaxWidth()) {
PositionAndDurationText(...)
Spacer(Modifier.weight(1f))
PlaybackSpeedToggleButton(player)
MuteButton(player)
PlayPauseButton(player)
}
},
)
}E questa è esattamente la stessa idea.
Possiamo impostare centerControls su vuoto per farli scomparire ed espandere lo spazio sopra lo slider con un po' di spazio aggiuntivo e i tre pulsanti che vogliamo mostrare.
Tieni presente che questi pulsanti provengono dalla libreria stessa, quindi non devi inserire codice aggiuntivo.
Non ti serve altro.
Puoi continuare in questo modo anche per applicare lo stile a questi elementi.
Se vuoi modificare l'icona del tasto di disattivazione audio, ad esempio, ma mantenere la sua funzionalità, il funzionamento è lo stesso.
Player(
...
bottomControls = { ...
above = {
...
MuteButton(player,
painter = { painterResource(
if (showMuted)
R.drawable.muted
else
R.drawable.unmuted )})
}
},
)Come puoi vedere qui, puoi definire un painter personalizzato per il pulsante e scegliere le icone.
Il componente fornisce alcuni metadati, quindi tutto ciò che devi fare è, ad esempio, guardare lo stato, come showMuted in questo esempio, per scegliere l'icona giusta.
Questo è il vantaggio principale di Media3 Compose.
Puoi concentrarti sull'aspetto della tua app e Media3 si occuperà del resto.
Legge lo stato dal player, interagisce con il player e gestisce tutte le transizioni e le animazioni intermedie.
Ora passiamo dall'UI alla condivisione di contenuti multimediali.
Puoi condividere lo stato di riproduzione con altre app sullo stesso dispositivo o connesse a dispositivi remoti.
Il modo principale per pubblicare la riproduzione in corso è utilizzare MediaSession.
Rende disponibili ad altri lo stato e i metadati della riproduzione e consente loro di controllarla.
È così che Gemini, la notifica multimediale della UI di sistema, Cast, Wear OS o le app complementari di Android Auto comunicano con la tua app.
MediaSession aggrega un player Media3 ed è semplicissimo crearne uno dopo aver ottenuto un player.
MediaSession.Builder(context, player).build()Anche in questo caso, potrebbe trattarsi di ExoPlayer o di qualsiasi altro player presente nella tua app, proprio come con l'interfaccia utente di Compose.
Se la riproduzione avviene in background, in genere l'hosting viene eseguito all'interno di un MediaSessionService o di un MediaLibraryService, che gestisce automaticamente anche l'intero ciclo di vita e aspetti come le notifiche.
class PlaybackService : MediaSessionService() {
private lateinit var mediaSession:
MediaSession
override fun onGetSession(
info: ControllerInfo) : MediaSession?
= mediaSession
}Se hai un'app multimediale, probabilmente sai già tutto questo.
La novità entusiasmante che voglio mostrarti è il nostro CastPlayer appena riscritto.
Google Cast ti consente di trasferire la riproduzione locale su un dispositivo remoto, come Android TV, Chromecast o smart speaker.
In precedenza pubblicavamo un SDK Cast separato, che ora è in modalità di manutenzione.
Invece, Media3 Cast lo sostituisce completamente perché fornisce un'integrazione molto più semplice e semplificata.
L'elemento più importante qui è CastPlayer, che implementa la stessa interfaccia del player standard di ExoPlayer.
Mentre ExoPlayer è destinato alla riproduzione locale, CastPlayer è un'istanza di delega che può riprodurre contenuti localmente tramite ExoPlayer o da remoto tramite il protocollo Cast.
E si occupa di tutto.
Ascolta le modifiche del dispositivo di output, stabilisce la connessione, sincronizza lo stato e trasferisce la riproduzione quando necessario.
Per questo motivo, l'integrazione di CastPlayer nella tua app è semplicissima.
val player = ExoPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Anziché creare un ExoPlayer e condividerlo con MediaSession in questo modo, utilizzi un CastPlayer e il gioco è fatto.
Cast.getSingletonInstance(context).initialize()
val player = CastPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()L'unica altra cosa che devi fare è inizializzare il sistema Cast prima di utilizzarlo in un punto qualsiasi della tua app.
Tieni presente che questo CastPlayer crea internamente un ExoPlayer per la riproduzione locale, quindi per personalizzare ulteriormente il player locale, puoi anche creare il tuo ExoPlayer e passarlo come player locale a questo CastPlayer.
Cast.getSingletonInstance(context).initialize()
val exoPlayer = ExoPlayer.Builder(context).build()
val player = CastPlayer.Builder(context)
.setLocalPlayer(exoPlayer).build()
mediaSession = MediaSession.Builder(context,player)
.build()E questo è di nuovo lo stesso principio di Media3.
Forniamo i semplici componenti di base dell'API pronti all'uso, ma anche tutte le opzioni di personalizzazione di cui hai bisogno per personalizzarla ulteriormente.
E forse ti starai chiedendo come l'utente passa effettivamente dalla riproduzione locale a quella remota in questa configurazione.
Funziona già in modo predefinito con la finestra di dialogo del selettore di output del sistema nella notifica dei contenuti multimediali.
È il piccolo pulsante nell'angolo.
Ma cosa succede se vuoi mostrare un pulsante Trasmetti nella schermata di riproduzione per renderlo più visibile?
Ricordi la UI di Scrivi personalizzata che abbiamo creato pochi minuti fa?
Player(
exoPlayer,
centerControls = {}
bottomControls = { /* customized */ },
)Media3 Cast fornisce anche un pulsante pronto all'uso che puoi posizionare lì.
È sufficiente aggiungere il MediaRouteButton pronto all'uso al topControls, ad esempio, e non abbiamo bisogno di altri parametri da configurare, tutto qui.
Player(
castPlayer,
centerControls = {}
bottomControls = { /* customized */ },
topControls = {
PlayerDefaults.TopControls(castPlayer) {
MediaRouteButton(
Modifier.align(Alignment.TopEnd))
}
},
)Se fai clic su questo pulsante, si apre la stessa finestra di dialogo del selettore di output di sistema e CastPlayer trasferisce la riproduzione per te.
Inoltre, puoi notare che ho collegato CastPlayer alla UI qui per assicurarmi che tutto ciò che accade da remoto si rifletta nella UI locale.
A questo punto, passo la parola a Kristina per parlare dei casi d'uso dell'editing.
Grazie.
Ciao, ora passiamo all'editing e agli effetti avanzati.
Le API Media3 sono costituite da più componenti.
Innanzitutto, Transformer è il punto di ingresso principale per le nostre API di editing.
Transformer elabora, transcodifica ed esporta i contenuti multimediali in un file.
Poi c'è CompositionPlayer, che esegue il rendering delle anteprime in tempo reale delle tue composizioni multimediali.
In questo modo, puoi visualizzare l'anteprima e perfezionare le modifiche prima dell'esportazione finale.
Poi abbiamo gli effetti.
Ti offre il pieno controllo sull'elaborazione fotogramma per fotogramma ogni volta che applichi semplici effetti visivi o aggiungi overlay grafici.
Infine, abbiamo Muxer.
Funge da fase di output finale nella nostra pipeline, prendendo le tracce audio, video e di metadati elaborate e scrivendole in container MP4 standardizzati.
Nell'ultimo anno abbiamo ripensato e ricostruito da zero la nostra pipeline di editing video e oggi sono felice di condividere i principali miglioramenti.
Perché abbiamo ricostruito la pipeline?
L'architettura legacy presentava quindi diverse sfide.
Innanzitutto, non era in grado di propagare i metadati in tutte le fasi di elaborazione.
Era strettamente accoppiato a OpenGL, il che limitava la flessibilità per i moderni strumenti grafici.
Inoltre, la sua API complessa rendeva la personalizzazione e la manutenzione un ostacolo difficile da superare per gli sviluppatori.
Per affrontare queste sfide, abbiamo creato una nuova pipeline di elaborazione.
Innanzitutto, l'assemblaggio di composizioni complesse con più asset è ora molto più semplice.
In secondo luogo, il disaccoppiamento da OpenGL ti offre la massima libertà di utilizzare framework grafici moderni, come WebGPU, ad esempio.
Infine, Media3 ora gestisce direttamente il decodificatore e l'encoder, garantendo maggiore coerenza ed efficienza su tutti i dispositivi.
Diamo un'occhiata "dietro le quinte" per capire come funziona questa pipeline.
I decoder generano i frame direttamente nei buffer hardware gestiti da Media3.
Il Frame Aggregator coordina i frame in più sequenze, consentendo al processore di frame personalizzato di accedere direttamente alla lettura e all'elaborazione di questi buffer prima di inviarli al codificatore.
L'interfaccia FrameProcessor funge da punto di estensione principale per l'elaborazione a livello di app.
public interface FrameProcessor
extends AutoCloseable {
interface Listener {
void onWakeup();
void onError(
VideoFrameProcessingException exception);
void onFrameProcessed(
Frame frame,
@Nullable SyncFenceWrapper onCompleteFence);
}
interface Factory {
FrameProcessor create(
FrameWriter output,
Executor listenerExecutor,
Listener listener);
}
boolean queue(List<AsyncFrame> frames);
void signalEndOfStream();
}Tramite l'interfaccia del listener, l'applicazione notifica a Media3 ogni volta che un frame è stato elaborato, passando i fence di sincronizzazione, in modo che Media3 sappia esattamente quando i buffer possono essere riutilizzati in sicurezza.
Una nota sulla memoria unificata: se Media3 assume la proprietà dei buffer hardware, l'app può leggere e modificare i dati direttamente sul posto.
Questo approccio zero-copy riduce l'overhead della memoria e mantiene l'elaborazione ultraveloce.
Adobe è uno degli sviluppatori che sta sfruttando le nuove API di editing.
Il team di Adobe Premiere ha utilizzato le API di editing di Media3 come base della propria infrastruttura di editing multimediale per garantire una maggiore compatibilità con l'ecosistema Android, mantenendo al contempo la flessibilità necessaria per utilizzare la propria pipeline personalizzata e WebGPU.
Inoltre, sono felice di annunciare che stiamo rilasciando nuovi effetti nell'ambito della release 1.12 di Media3.
Iniziamo con i miglioramenti video.
Dietro le quinte, la nostra architettura si basa su una configurazione modulare.
La tua applicazione si integra con Play Services per gli effetti Media3, che si interfaccia con il modulo Play Services Enhancement.
Il modulo Video Enhance include funzionalità di mappatura dei toni, riduzione del rumore, rimozione della sfocatura e super risoluzione.
L'integrazione di Video Enhance nella tua pipeline richiede solo poche righe di codice.
val frameProcessorFactory = EnhancementSessionDecorator.Builder(/* context = */ this, baseGlFactory)
.setTonemappingEnabled(true)
.setDeblurAndDenoiseEnabled(true)
.setUpscaleVideoEnabled(true)
.build()
val transformerBuilder = Transformer.Builder(/* context = */ this)
.setFrameProcessorFactory(frameProcessorFactory)Utilizzando EnhancementSessionDecorator.Builder, puoi attivare in modo selettivo funzionalità come la mappatura dei toni, la rimozione della sfocatura o l'upscaling e passare i frameProcessorFactory e gli helper del buffer hardware risultanti in Transformer.Builder.
Un altro effetto interessante in arrivo in Media3 1.12 è l'audio professionale.
Studio Sound analizza automaticamente la traccia audio del video e la separa in singoli elementi, come parlato, rumore ambientale e musica.
I creator possono facilmente ridurre i rumori di disturbo del vento o della folla, mettendo in primo piano dialoghi e musica chiari.
Per integrare Suono professionale, passa il processore audio di Suono professionale all'interno di un oggetto effetto.
val editedMediaItem =
EditedMediaItem.Builder(MediaItem.fromUri(media.uri))
.setEffects(
Effects(
listOf(
soundSeparationAudioProcessor
),
emptyList(), // video effects
)
)
.build()In questo modo, il processore audio viene collegato all'elemento multimediale.
Quando crei il processore audio Studio Sound, collega il callback del listener per ricevere la mappatura dei canali audio analizzati tramite onResult.
val soundSeparationAudioProcessor =
SoundSeparationAudioProcessor(
getApplication(),
mainExecutor,
object : SoundSeparationAudioProcessor.Listener {
override fun onResult(channelToAudioType: SparseIntArray) {
for (i in 0 until channelToAudioType.size()) {
val channelIndex = channelToAudioType.keyAt(i)
val audioType = channelToAudioType.valueAt(i)
val label = toAudioTypeName(audioType)
detectedChannelVolumes.add(ChannelVolume(channelIndex, audioType, label))
}
}
},
)Esegui l'iterazione di ogni canale per estrarre l'indice del canale, il tipo di audio identificato e l'etichetta leggibile quando è disponibile il risultato dell'operazione.
Ascoltiamo l'audio professionale in azione.
Quindi, il primo effetto. Baby, please don't cry.
Stiamo tagliando per la tua cena.
Si sente il pianto del bambino in sottofondo e ora con l'effetto applicato.
Sì, come puoi sentire, non c'era nessun bambino che piangeva in sottofondo grazie all'effetto Audio professionale.
Ora approfondiamo il recupero dei metadati.
Vedremo come Media3 semplifica la lettura dei metadati del container e l'estrazione dei frame.
A partire da MetadataRetriever, si integra perfettamente con le coroutine Kotlin per un codice pulito e non di blocco.
Ha un design più pulito e meno soggetto a errori che utilizza un retriever immutabile per elemento multimediale.
Funziona in modo coerente su tutte le versioni dell'API e su tutti i dispositivi.
Inoltre, supporta un'ampia gamma di formati multimediali pronti all'uso.
Ecco un esempio pratico di utilizzo di MetadataRetriever.
MetadataRetriever.Builder(context, mediaItem).build().use { retriever ->
// 1. Duration (in microseconds)
val durationUs = retriever.retrieveDurationUs().await()
// 2. Track formats & container metadata (reuses the same preparation pass)
val trackGroups = retriever.retrieveTrackGroups().await()
for (i in 0 until trackGroups.length) {
val format = trackGroups[i].getFormat(0)
// Video/Audio properties: format.width, format.height, format.colorInfo …
// Rich metadata: SlowMotionData, MotionPhotoMetadata, Mp4TimestampData …
val metadata = format.metadata
}
// 3. Stream structure & manifest info
val timeline = retriever.retrieveTimeline().await()
}Utilizzando le coroutine Kotlin, puoi recuperare in modo asincrono la durata, ispezionare i formati delle tracce e i metadati, il tutto all'interno di un unico blocco pulito.
Ora parliamo di FrameExtractor.
Rende l'estrazione dei frame video decodificati dall'elemento multimediale rapida ed efficiente.
Gestisce automaticamente le risorse sottostanti come i decoder, consente di applicare effetti visivi o la mappatura tonale HDR e offre un controllo di ricerca granulare per ottenere il frame esatto di cui hai bisogno.
Ecco come appare l'estrazione dei frame nel codice.
FrameExtractor.Builder(context, mediaItem)
.setSeekParameters(SeekParameters.CLOSEST_SYNC) // Fast keyframe seeking
.setEffects(listOf(Presentation.createForHeight(360))) // GPU downscaling
.setExtractHdrFrames(true) // Optional: HLG HDR or UltraHDR
.build()
.use { extractor ->
// Representative thumbnail
val thumbnail = extractor.getThumbnail().await()
// Frame at specific timestamp (bitmap + actual presentationTimeMs)
val frame = extractor.getFrame(positionMs = 5_000L).await()
}Puoi creare un'istanza con opzioni come parametri di ricerca, downscaling della GPU, effetti e supporto HDR, quindi attendere in modo asincrono le miniature e i frame specifici utilizzando chiamate di sospensione pulite.
E ora, Donovan.
Grazie, Kristina.
Ora vorrei parlare di alcuni dei supporti tecnologici di streaming sottostanti che abbiamo aggiunto in Media3, incluso il supporto per formati come Eclipsa Video ed Eclipsa Audio.
Quindi, un impegno fondamentale di Media3 è quello di rimanere al passo con gli standard di streaming, in modo che ogni volta che viene introdotta una nuova specifica, non sia necessario riscrivere la pipeline multimediale.
Ecco alcuni esempi di funzionalità che abbiamo lanciato di recente.
Abbiamo il content steering, che consente di cambiare dinamicamente i percorsi CDN di backend durante lo streaming.
Supportiamo l'inserimento di annunci guidato dal server per l'unione di annunci personalizzati senza interruzioni.
Abbiamo anche introdotto il supporto di AV1 in più punti di accesso, incluso il supporto completo di AV1 HDR.
Offriamo supporto per i video professionali Android per l'acquisizione e l'editing ad alta fedeltà.
Stiamo anche aggiungendo il supporto per le foto in movimento nei formati JPEG e HEIC.
E, naturalmente, stiamo aggiungendo il supporto per Eclipsa Video ed Eclipsa Audio, di cui parlerò più approfonditamente a breve.
La buona notizia è che tutto questo è supportato automaticamente man mano che viene implementato il supporto dei dispositivi.
Ora diamo un'occhiata alla prossima evoluzione di HDR, Eclipsa Video, e al problema principale che sta cercando di risolvere.
I formati HDR precedenti fornivano metadati descrittivi, quindi determinavano l'aspetto della scena, ma non indicavano al display come eseguirne il rendering.
Quindi, per prima cosa, facciamo un esempio.
Per ambienti diversi, esistono consigli diversi per la luminosità media dello schermo, in modo da poter visualizzare i contenuti in modo chiaro e confortevole.
In una stanza poco illuminata, questo intervallo sarà compreso tra 100 e 300 nit.
E poi, naturalmente, man mano che l'ambiente diventa più luminoso, lo schermo deve adattarsi e diventare più luminoso per consentirti di vedere chiaramente i contenuti, fino a quando non visualizzi uno smartphone alla luce diretta del sole, lo schermo potrebbe dover raggiungere 1000, 2000 o anche più nit per consentirti di visualizzare i contenuti.
L'SDR tradizionale opera in un intervallo compreso tra 100 e 300.
Inoltre, l'HDR non è pensato per aumentare la luminosità dello schermo in modo uniforme.
Vuoi espandere la gamma dinamica in modo da avere luci localizzate per elementi come il fuoco e la luce del sole.
Lo smartphone, tuttavia, introduce alcune complicazioni, perché lo portiamo con noi ovunque, in una stanza buia di casa o su una spiaggia luminosa.
Questo, unito al fatto che molte app combinano elementi UI SDR con contenuti SDR e HDR in feed misti, può portare a quell'effetto fastidioso e accecante che a volte si verifica, soprattutto se si scorre lo schermo a tarda notte.
Ora è chiaro perché vale la pena usare l'HDR, giusto?
In questo modo, i nostri contenuti hanno un aspetto fantastico in più ambienti.
Abbiamo anche notato un aumento del coinvolgimento degli utenti quando l'HDR è attivo.
Il problema è che i formati HDR esistenti non tengono conto di questi ambienti.
È qui che Eclipsa Video dà il meglio di sé.
Pausa per le risate.
Grazie.
Grazie.
Quindi, anziché metadati descrittivi, Eclipsa Video fornisce metadati prescrittivi.
Questo si basa su tre pilastri principali.
Il primo pilastro è un'ancora bianca di riferimento.
In questo modo, la luminosità standard viene bloccata sul punto di bianco SDR del display regolato in base all'ambiente, eliminando i picchi di luminosità improvvisi.
Il secondo pilastro è la base di riferimento per la SDR dei creator.
In questo modo, la curva di mappatura tonale esatta del creator viene mantenuta anche in SDR.
In terzo luogo, abbiamo le curve di guadagno adattive dell'headroom, che scalano in modo deterministico le luci per adattarle all'headroom HDR in tempo reale del display, in modo che i contenuti corrispondano all'ambiente in cui vengono visualizzati.
Questa funzionalità è disponibile in ExoPlayer out-of-the-box in Media3 1.11 sui dispositivi con Android 17 e versioni successive.
Inoltre, Eclipsa Video è basato su standard aperti per la compatibilità tra i vari settori.
Non c'è mai stato momento migliore per supportare l'HDR.
Per quanto riguarda l'audio, abbiamo Eclipsa Audio, un formato audio spaziale 3D open source e senza royalty.
Si basa sulla specifica IAMF di Alliance for Open Media.
Combina l'audio Ambisonics basato sulla scena con l'audio basato sui canali in un unico stream con più presentazioni miste.
In questo modo è possibile eseguire manipolazioni simili a quelle degli oggetti, ad esempio per il miglioramento dei dialoghi, e funzionerà su tutto, dalle cuffie stereo alle configurazioni audio surround.
In realtà, in Media3 esistono due percorsi di riproduzione.
Per Android 17 e versioni successive, ExoPlayer e il framework audio di Android eseguiranno automaticamente il rendering di Eclipsa Audio.
Per Android 16 e versioni precedenti, hai ancora un'opzione.
Puoi attivare l'estensione IAMF tramite una libreria di componenti aggiuntivi Media3 facoltativa.
Per quanto riguarda la creazione, puoi scrivere tracce audio IAMF utilizzando i muxer Media3.
Ma supportare formati di alta qualità è solo metà della storia.
Dobbiamo fornire questa qualità in modo efficiente.
Ora vorrei parlare di come Media3 sta rendendo le nostre pipeline più veloci ed efficienti.
Come molte delle cose di cui abbiamo parlato oggi, la maggior parte di questi miglioramenti delle prestazioni è disponibile immediatamente, semplicemente mantenendo aggiornata la versione di Media3.
Un esempio di funzionalità che abbiamo è l'offload audio PCM, che può indirizzare la riproduzione audio a un'unità di elaborazione a basso consumo.
Nella nostra analisi iniziale, abbiamo riscontrato un risparmio energetico fino al 20% per la riproduzione audio con schermo spento.
Oltre all'offload audio PCM, abbiamo la programmazione dinamica, che allinea i risvegli del loop di rendering interno di ExoPlayer con la disponibilità del buffer del decodificatore e di VSYNC.
Abbiamo anche il preriscaldamento del renderer, che ti consente di preriscaldare un renderer video secondario, il che consentirà transizioni istantanee delle playlist e un inserimento degli annunci più semplice.
Abbiamo anche il rilevamento dei player bloccati e il pre-caching, che rileva automaticamente i buffer e i renderer bloccati e limita in modo pulito la memoria pre-memorizzata nella cache.
Un'altra modalità ottimale è la modalità di scrubbing.
Questo vale per le app in cui gli utenti scorrono spesso i contenuti.
Quando attivi la modalità di scorrimento, il trascinamento della barra di ricerca diventa molto più reattivo e fluido, come puoi vedere qui.
Questo con la modalità di scrubbing disattivata, tra l'altro.
A destra, invece, vediamo la modalità di scrubbing attiva.
Grazie.
Grazie.
Okay.
Ora parliamo di Android System Health.
Dobbiamo rispettare le risorse condivise di RAM, CPU e batteria e, man mano che Android rafforza le sue regole relative all'Audio Hardening e ai servizi in primo piano, le app che gestiscono i propri servizi e bitmap non elaborati possono spesso riscontrare alcuni errori.
Alcuni esempi sono le eccezioni di avvio del servizio in primo piano non consentito o le eccezioni di memoria esaurita del binder.
Per questo motivo, abbiamo rafforzato la sessione del servizio multimediale e il gestore delle notifiche multimediali, nonché le nostre pipeline bitmap in diversi modi, tra cui la prevenzione degli intent dei servizi in primo piano obsoleti e la limitazione della memoria delle immagini di copertina e cose simili.
In sostanza, se utilizzi Media3 e segui le indicazioni standard, tutte queste limitazioni relative all'integrità del sistema vengono gestite per te senza alcun impatto.
Infine, vorrei parlare di una funzionalità che sarà disponibile a breve in Media3.
Quando è il momento di codificare o esportare un video, la diversità dell'hardware può essere una delle sfide più grandi.
Molte app utilizzano una configurazione del codec universale, che, come sapete, porta a una qualità e a prestazioni non ottimali.
Ecco perché abbiamo creato CodecDB.
CodecDB si basa su misurazioni video reali e approfondite che utilizzano metriche di qualità standardizzate e benchmarking hardware su dispositivi Android.
Vediamo come funziona.
A sinistra, abbiamo un frame di un video che abbiamo girato su uno smartphone utilizzando una configurazione video molto generica che potremmo utilizzare su molti dispositivi.
A destra, abbiamo utilizzato una configurazione ottimale a livello di dispositivo ottenuta da CodecDB.
Quindi, dove sono le differenze?
Se guardi le aree con un livello di dettaglio elevato, noterai la differenza di qualità maggiore.
Quindi, osserva la nitidezza dei rami degli alberi e degli arbusti o la nitidezza del bordo dell'edificio.
In questo modo, l'esperienza di visualizzazione è molto migliore e sfrutta le funzionalità di un dispositivo specifico utilizzando CodecDB.
L'utilizzo di CodecDB nel codice è piuttosto semplice.
val config = CodecDb.onDevice(context)
.forEncodingFallingBackTo {
constraints -> createFallbackConfig(constraints)
}
.findOptimalSettings()
.forEditing()
.withResolution(1920, 1080, /* frameRate= */ 30.0)
.withAnyCodec()
.targetingQuality(VideoEncoder.QUALITY_LEVEL_VERY_GOOD)
.get()Utilizzerai un pattern di creazione di fasi fluido che ti guiderà attraverso ogni parametro richiesto.
Quindi, specificherai il tuo caso d'uso.
In questo caso, lo utilizziamo per l'editing.
In questo modo, CodecDB saprà che stai utilizzando la pipeline di esportazione o transcodifica di Media3.
Altri casi d'uso che potresti impostare, ad esempio, sono l'acquisizione della fotocamera, se è questo l'uso che ne fai.
Inoltre, trasmetterai la risoluzione e il frame rate target.
Poi, invece di indovinare se quel video 1080p ha bisogno di 8 megabit al secondo o 15 megabit al secondo, indichi a CodecDB quale codec vuoi utilizzare e richiedi un target di qualità percettiva.
In questo caso, utilizziamo il livello di qualità "Molto buona".
Dietro le quinte, CodecDB cerca le curve di benchmark empiriche per quel SoC esatto e restituisce i parametri ideali per codec ed encoder.
Prevediamo di lanciare CodecDB in Media3 molto presto e, quando lo faremo, utilizzerà i tipi nativi di Media3.
Quindi l'integrazione con il codice Media3 esistente dovrebbe essere un gioco da ragazzi.
Ok.
Oggi abbiamo visto molte funzionalità straordinarie in Media3, quindi facciamo un riepilogo.
Oggi hai scoperto i nostri nuovi moduli Compose UI, il nuovo CastPlayer, la pipeline di editing rinnovata e molti nuovi effetti che puoi utilizzare in Media3.
Abbiamo anche visto nuovi strumenti per recuperare metadati e frame.
Abbiamo esaminato il supporto di nuove tecnologie di streaming, il miglioramento delle prestazioni e le utilità.
Media3 è una libreria open source, quindi puoi vedere come abbiamo implementato tutte queste funzionalità nel nostro repository GitHub.
Ad oggi, l'ultima release stabile di Media3 è la 1.11.1.
Puoi anche dare un'occhiata alla versione 1.12.0, attualmente in versione alpha.
Segui questo link breve per accedere alla nostra documentazione per lo sviluppo su Media3.
Grazie mille e buon proseguimento con l'ADS.
@Composable
fun videoScreen() {
Player(viewModel.exoPlayer)
}Player(viewModel.exoPlayer)Player(
player = viewModel.exoPlayer,
bottomControls = {
PlayerDefaults.BottomControls(
player,
left = {}, right = {},
above = {
PositionAndDurationText(
player, separator = " • ")
},
)
},
)Player(
player = viewModel.exoPlayer,
centerControls = {},
bottomControls = { ...
above = {
Row(Modifier.fillMaxWidth()) {
PositionAndDurationText(...)
Spacer(Modifier.weight(1f))
PlaybackSpeedToggleButton(player)
MuteButton(player)
PlayPauseButton(player)
}
},
)
}Player(
...
bottomControls = { ...
above = {
...
MuteButton(player,
painter = { painterResource(
if (showMuted)
R.drawable.muted
else
R.drawable.unmuted )})
}
},
)MediaSession.Builder(context, player).build()class PlaybackService : MediaSessionService() {
private lateinit var mediaSession:
MediaSession
override fun onGetSession(
info: ControllerInfo) : MediaSession?
= mediaSession
}val player = ExoPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Cast.getSingletonInstance(context).initialize()
val player = CastPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Cast.getSingletonInstance(context).initialize()
val exoPlayer = ExoPlayer.Builder(context).build()
val player = CastPlayer.Builder(context)
.setLocalPlayer(exoPlayer).build()
mediaSession = MediaSession.Builder(context,player)
.build()Player(
exoPlayer,
centerControls = {}
bottomControls = { /* customized */ },
)Player(
castPlayer,
centerControls = {}
bottomControls = { /* customized */ },
topControls = {
PlayerDefaults.TopControls(castPlayer) {
MediaRouteButton(
Modifier.align(Alignment.TopEnd))
}
},
)public interface FrameProcessor
extends AutoCloseable {
interface Listener {
void onWakeup();
void onError(
VideoFrameProcessingException exception);
void onFrameProcessed(
Frame frame,
@Nullable SyncFenceWrapper onCompleteFence);
}
interface Factory {
FrameProcessor create(
FrameWriter output,
Executor listenerExecutor,
Listener listener);
}
boolean queue(List<AsyncFrame> frames);
void signalEndOfStream();
}val frameProcessorFactory = EnhancementSessionDecorator.Builder(/* context = */ this, baseGlFactory)
.setTonemappingEnabled(true)
.setDeblurAndDenoiseEnabled(true)
.setUpscaleVideoEnabled(true)
.build()
val transformerBuilder = Transformer.Builder(/* context = */ this)
.setFrameProcessorFactory(frameProcessorFactory)val editedMediaItem =
EditedMediaItem.Builder(MediaItem.fromUri(media.uri))
.setEffects(
Effects(
listOf(
soundSeparationAudioProcessor
),
emptyList(), // video effects
)
)
.build()val soundSeparationAudioProcessor =
SoundSeparationAudioProcessor(
getApplication(),
mainExecutor,
object : SoundSeparationAudioProcessor.Listener {
override fun onResult(channelToAudioType: SparseIntArray) {
for (i in 0 until channelToAudioType.size()) {
val channelIndex = channelToAudioType.keyAt(i)
val audioType = channelToAudioType.valueAt(i)
val label = toAudioTypeName(audioType)
detectedChannelVolumes.add(ChannelVolume(channelIndex, audioType, label))
}
}
},
)MetadataRetriever.Builder(context, mediaItem).build().use { retriever ->
// 1. Duration (in microseconds)
val durationUs = retriever.retrieveDurationUs().await()
// 2. Track formats & container metadata (reuses the same preparation pass)
val trackGroups = retriever.retrieveTrackGroups().await()
for (i in 0 until trackGroups.length) {
val format = trackGroups[i].getFormat(0)
// Video/Audio properties: format.width, format.height, format.colorInfo …
// Rich metadata: SlowMotionData, MotionPhotoMetadata, Mp4TimestampData …
val metadata = format.metadata
}
// 3. Stream structure & manifest info
val timeline = retriever.retrieveTimeline().await()
}FrameExtractor.Builder(context, mediaItem)
.setSeekParameters(SeekParameters.CLOSEST_SYNC) // Fast keyframe seeking
.setEffects(listOf(Presentation.createForHeight(360))) // GPU downscaling
.setExtractHdrFrames(true) // Optional: HLG HDR or UltraHDR
.build()
.use { extractor ->
// Representative thumbnail
val thumbnail = extractor.getThumbnail().await()
// Frame at specific timestamp (bitmap + actual presentationTimeMs)
val frame = extractor.getFrame(positionMs = 5_000L).await()
}val config = CodecDb.onDevice(context)
.forEncodingFallingBackTo {
constraints -> createFallbackConfig(constraints)
}
.findOptimalSettings()
.forEditing()
.withResolution(1920, 1080, /* frameRate= */ 30.0)
.withAnyCodec()
.targetingQuality(VideoEncoder.QUALITY_LEVEL_VERY_GOOD)
.get()