App Tutorial

Media Controller Android: Guida Completa con Java ed Esempi Pratici

Se stai cercando di capire come implementare un Media Controller Android efficace nella tua app, questa guida pratica ti accompagnerà passo dopo passo. Scoprirai cos’è il MediaController, come si integra con MediaPlayer per gestire la riproduzione audio e video e in quali casi conviene usarlo. Tutti gli esempi sono scritti in Java e pensati per essere seguiti direttamente in Android Studio, così puoi provarli subito nel tuo progetto. Continua a leggere per imparare a creare controlli multimediali stabili, intuitivi e pronti per le versioni moderne di Android.

MediaController in Android con Java: guida completa

In questa pagina vediamo in dettaglio come usare MediaController in Android con Java per controllare la riproduzione audio/video, integrandolo con MediaPlayer e gestendo correttamente ciclo di vita, errori e best practice per Android 8.0 (API 26) e successivi.

1. Introduzione: cos’è il MediaController e quando usarlo

MediaController è un widget di Android che fornisce una barra di controllo standard per la riproduzione multimediale: pulsanti di play/pause, avanti/indietro, barra di avanzamento (seek bar) e timer. È pensato per essere collegato a un oggetto che implementa l’interfaccia MediaController.MediaPlayerControl, tipicamente un MediaPlayer o un wrapper personalizzato.

Usare il MediaController è utile quando vuoi:

  • Integrare rapidamente controlli di trasporto standard (play/pause/seek) senza progettare una UI personalizzata.
  • Associare i controlli a un componente video (es. SurfaceView o VideoView).
  • Ridurre il codice di gestione dei pulsanti delegando la logica all’interfaccia MediaPlayerControl.

Puoi comunque usare MediaPlayer in modo standalone, senza MediaController, quando:

  • Vuoi un design completamente personalizzato dei controlli (pulsanti custom, gesture, animazioni).
  • Stai implementando solo audio in background e controlli via notifiche e comandi di sistema (MediaSession).
  • Gestisci la riproduzione tramite servizi o componenti senza interfaccia utente diretta.

2. Concetti chiave: MediaController, MediaPlayer, MediaSession

MediaPlayer

MediaPlayer è la classe di base di Android per riprodurre file audio e video da diverse sorgenti (risorse locali, file system, URL remoti, ContentProvider). Gestisce lo stato interno (Idle, Initialized, Preparing, Prepared, Started, Paused, Stopped, End) e fornisce metodi come start(), pause(), stop(), seekTo(), getDuration(), getCurrentPosition().

MediaController

MediaController è un componente UI che mostra i controlli di trasporto e invia i comandi a un oggetto che implementa MediaController.MediaPlayerControl. Non riproduce direttamente media: delega tutto a questo “player” sottostante (tipicamente un MediaPlayer o una view come VideoView che già implementa l’interfaccia).

L’interfaccia MediaPlayerControl definisce metodi come:

  • start(), pause(), seekTo(int pos)
  • getDuration(), getCurrentPosition()
  • isPlaying(), canPause(), canSeekForward(), canSeekBackward()

MediaSession / MediaSessionCompat (cenni)

MediaSession (e la variante compat MediaSessionCompat) è l’API moderna per esporre la riproduzione multimediale al sistema: notifiche multimediali, controlli su lockscreen, integrazione con Google Assistant e dispositivi esterni (auricolari con tasti, automotive, ecc.).

In questa guida ci concentriamo su MediaController e MediaPlayer per una semplice app locale. In uno scenario di produzione, soprattutto per audio in background, è consigliabile usare MediaSession/MediaBrowserService o la libreria Media3/ExoPlayer.

3. Setup del progetto

Per usare MediaPlayer e MediaController non servono librerie esterne: sono parte della SDK Android standard. Dobbiamo però verificare la configurazione minima del progetto.

build.gradle (Module: app)

Esempio di configurazione base (valori indicativi):

android {
    namespace "com.example.audioplayer"
    compileSdk 34

    defaultConfig {
        applicationId "com.example.audioplayer"
        minSdk 21
        targetSdk 34
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

// Nessuna dipendenza speciale richiesta per MediaPlayer/MediaController
// Assicurati solo di avere le dipendenze di base per AppCompat/Material se usi quelle librerie.

dependencies {
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.12.0'
}

Permessi in AndroidManifest.xml

Se vuoi riprodurre file da storage esterno (es. selezionando un file audio dall’archivio locale), devi gestire i permessi di lettura. Con le API recenti è preferibile usare il Storage Access Framework (ACTION_OPEN_DOCUMENT), che non richiede permessi a livello di manifest. Per semplicità, qui useremo ancora READ_EXTERNAL_STORAGE (valuta di usare approcci più moderni in produzione).

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.audioplayer">

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:label="AudioPlayer"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.Light.DarkActionBar">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
</manifest>

4. Esempi di codice Java: MediaPlayer + MediaController

4.1 Creazione e configurazione di un MediaPlayer

Un esempio minimale per creare un MediaPlayer da un file locale e prepararlo in modo asincrono:

MediaPlayer mediaPlayer = new MediaPlayer();
try {
    // Sostituisci con il percorso reale del tuo file
    String filePath = "/sdcard/Music/track.mp3";
    mediaPlayer.setDataSource(filePath);

    // Preparazione asincrona per non bloccare il thread UI
    mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(MediaPlayer mp) {
            // Pronto per partire
            mp.start();
        }
    });

    mediaPlayer.prepareAsync();

} catch (IOException e) {
    e.printStackTrace();
    // Gestione errori: mostra un messaggio all'utente, log, ecc.
}

4.2 Collegare MediaController a MediaPlayer (MediaPlayerControl)

Per collegare MediaController al tuo player, la tua Activity (o una classe dedicata) deve implementare MediaController.MediaPlayerControl. Esempio scheletrico:

public class MainActivity extends AppCompatActivity
        implements MediaController.MediaPlayerControl {

    private MediaPlayer mediaPlayer;
    private MediaController mediaController;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mediaPlayer = new MediaPlayer();
        mediaController = new MediaController(this);

        // Collega il controller a questa Activity che implementa MediaPlayerControl
        mediaController.setMediaPlayer(this);
        mediaController.setAnchorView(findViewById(R.id.root_view));
    }

    // Implementazione dei metodi richiesti da MediaPlayerControl

    @Override
    public void start() {
        mediaPlayer.start();
    }

    @Override
    public void pause() {
        mediaPlayer.pause();
    }

    @Override
    public int getDuration() {
        return mediaPlayer != null && mediaPlayer.isPlaying() ? mediaPlayer.getDuration() : 0;
    }

    @Override
    public int getCurrentPosition() {
        return mediaPlayer != null ? mediaPlayer.getCurrentPosition() : 0;
    }

    @Override
    public void seekTo(int pos) {
        if (mediaPlayer != null) {
            mediaPlayer.seekTo(pos);
        }
    }

    @Override
    public boolean isPlaying() {
        return mediaPlayer != null && mediaPlayer.isPlaying();
    }

    @Override
    public int getBufferPercentage() {
        // Per file locali puoi tornare sempre 100
        return 100;
    }

    @Override
    public boolean canPause() {
        return true;
    }

    @Override
    public boolean canSeekBackward() {
        return true;
    }

    @Override
    public boolean canSeekForward() {
        return true;
    }

    @Override
    public int getAudioSessionId() {
        return mediaPlayer != null ? mediaPlayer.getAudioSessionId() : 0;
    }
}

4.3 Controlli di trasporto: play, pause, seek, durata

Una volta configurato il MediaController, puoi mostrarlo e lasciare che gestisca i comandi standard:

// Mostra i controlli di trasporto
mediaController.setEnabled(true);
mediaController.show(); // Puoi passare un timeout in millisecondi, es: show(5000)

I metodi di MediaPlayerControl verranno chiamati automaticamente dal controller quando l’utente interagisce con i pulsanti o la seek bar: non devi implementare manualmente i pulsanti play/pause se ti basta la UI standard.

4.4 Gestione del ciclo di vita: onPause, onResume, onDestroy

È fondamentale rilasciare le risorse multimediali nei metodi del ciclo di vita dell’Activity per evitare memory leak e blocchi dell’audio in background:

@Override
protected void onPause() {
    super.onPause();
    // Metti in pausa la riproduzione quando l'Activity non è più in foreground
    if (mediaPlayer != null && mediaPlayer.isPlaying()) {
        mediaPlayer.pause();
    }
}

@Override
protected void onResume() {
    super.onResume();
    // Se vuoi riprendere automaticamente, puoi farlo qui
    // In molti casi è meglio lasciare il controllo all'utente
}

@Override
protected void onDestroy() {
    super.onDestroy();
    // Rilascia completamente il MediaPlayer per liberare risorse
    if (mediaPlayer != null) {
        mediaPlayer.reset();
        mediaPlayer.release();
        mediaPlayer = null;
    }
}

5. Mini app di esempio: “AudioPlayer” completa

Vediamo ora una mini app completa in Java che usa MediaPlayer e MediaController per riprodurre un file audio scelto dall’utente. L’interfaccia contiene una SurfaceView (per mostrare come si integrerebbe con il video) e un bottone per caricare l’audio.

5.1 Layout: activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <SurfaceView
        android:id="@+id/surface_view"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_alignParentTop="true"
        android:layout_marginBottom="16dp" />

    <Button
        android:id="@+id/btn_select_audio"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Seleziona audio"
        android:layout_below="@id/surface_view" />

</RelativeLayout>

5.2 Activity: MainActivity.java

La MainActivity implementa MediaPlayerControl, gestisce il permesso di lettura storage (runtime), inizializza MediaPlayer e MediaController, e usa un Intent.ACTION_GET_CONTENT per far scegliere un file audio all’utente.

package com.example.audioplayer;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.MediaController;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import java.io.IOException;

public class MainActivity extends AppCompatActivity
        implements MediaController.MediaPlayerControl, SurfaceHolder.Callback {

    private static final int REQUEST_CODE_PICK_AUDIO = 1001;
    private static final int REQUEST_CODE_PERMISSION_READ = 2001;

    private MediaPlayer mediaPlayer;
    private MediaController mediaController;
    private SurfaceView surfaceView;
    private SurfaceHolder surfaceHolder;
    private Button btnSelectAudio;

    private Uri currentAudioUri; // URI del file audio selezionato

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Inizializzazione vista SurfaceView (utile per video; per audio rimane comunque valida)
        surfaceView = findViewById(R.id.surface_view);
        surfaceHolder = surfaceView.getHolder();
        surfaceHolder.addCallback(this); // Registriamo il callback per sapere quando la Surface è pronta

        // Bottone per selezionare l'audio
        btnSelectAudio = findViewById(R.id.btn_select_audio);
        btnSelectAudio.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Quando l'utente clicca, verifichiamo il permesso e apriamo il selettore file
                checkPermissionAndPickAudio();
            }
        });

        // Creiamo il MediaController e lo colleghiamo a questa Activity (MediaPlayerControl)
        mediaController = new MediaController(this);
        mediaController.setMediaPlayer(this);
        // Ancoriamo i controlli alla root view del layout
        mediaController.setAnchorView(findViewById(R.id.root_view));
    }

    /**
     * Verifica il permesso READ_EXTERNAL_STORAGE e se concesso apre il selettore di file audio.
     */
    private void checkPermissionAndPickAudio() {
        // Dalla API 23 in poi, i permessi "dangerous" vanno richiesti a runtime
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            int permission = ContextCompat.checkSelfPermission(this,
                    Manifest.permission.READ_EXTERNAL_STORAGE);

            if (permission != PackageManager.PERMISSION_GRANTED) {
                // Permesso non ancora concesso: lo richiediamo
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                        REQUEST_CODE_PERMISSION_READ);
                return;
            }
        }
        // Se siamo qui, il permesso è già stato concesso o non è richiesto (API < 23)
        openAudioPicker();
    }

    /**
     * Apre un Intent per permettere all'utente di scegliere un file audio dal dispositivo.
     */
    private void openAudioPicker() {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("audio/*"); // Filtriamo solo i file audio
        startActivityForResult(Intent.createChooser(intent, "Seleziona file audio"),
                REQUEST_CODE_PICK_AUDIO);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == REQUEST_CODE_PICK_AUDIO && resultCode == RESULT_OK && data != null) {
            // L'utente ha selezionato un file: ne otteniamo l'URI
            currentAudioUri = data.getData();
            if (currentAudioUri != null) {
                // Inizializziamo o re-inizializziamo il MediaPlayer con il nuovo file
                initMediaPlayer(currentAudioUri);
            }
        }
    }

    /**
     * Inizializza il MediaPlayer con l'URI fornito e prepara la riproduzione.
     */
    private void initMediaPlayer(Uri audioUri) {
        // Se esiste già un MediaPlayer, lo rilasciamo
        releaseMediaPlayer();

        mediaPlayer = new MediaPlayer();

        try {
            // Impostiamo la sorgente dati usando l'URI scelto dall'utente
            mediaPlayer.setDataSource(this, audioUri);

            // Collegamento opzionale alla Surface per il video (per audio non è necessario)
            if (surfaceHolder != null) {
                mediaPlayer.setDisplay(surfaceHolder);
            }

            // Listener chiamato quando il file è pronto alla riproduzione
            mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    // Abilitiamo il MediaController e lo mostriamo
                    mediaController.setEnabled(true);
                    mediaController.show();
                    // Avviamo immediatamente la riproduzione
                    mp.start();
                }
            });

            // Listener per completamento della traccia
            mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mp) {
                    // Alla fine della traccia riportiamo la posizione a 0 e aggiorniamo il controller
                    mp.seekTo(0);
                    mediaController.show();
                    Toast.makeText(MainActivity.this,
                            "Riproduzione terminata", Toast.LENGTH_SHORT).show();
                }
            });

            // Listener per errori di riproduzione
            mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
                @Override
                public boolean onError(MediaPlayer mp, int what, int extra) {
                    // Gestione semplificata degli errori
                    Toast.makeText(MainActivity.this,
                            "Errore nella riproduzione (" + what + ")",
                            Toast.LENGTH_LONG).show();
                    // Resettiamo e rilasciamo il player per evitare stati inconsistenti
                    releaseMediaPlayer();
                    return true; // Indichiamo che l'errore è stato gestito
                }
            });

            // Preparazione asincrona: non blocca il thread UI
            mediaPlayer.prepareAsync();

        } catch (IOException e) {
            // Eccezione in caso di problemi con la sorgente dati
            e.printStackTrace();
            Toast.makeText(this, "Impossibile riprodurre il file selezionato",
                    Toast.LENGTH_LONG).show();
            releaseMediaPlayer();
        }
    }

    /**
     * Rilascia il MediaPlayer se esiste, liberando le risorse native.
     */
    private void releaseMediaPlayer() {
        if (mediaPlayer != null) {
            try {
                mediaPlayer.reset(); // Resettiamo lo stato interno
                mediaPlayer.release(); // Rilasciamo le risorse native
            } catch (IllegalStateException e) {
                // Può capitare se il player è in stato inconsistente
                e.printStackTrace();
            }
            mediaPlayer = null;
        }
    }

    /**
     * Callback del risultato della richiesta di permessi runtime.
     */
    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == REQUEST_CODE_PERMISSION_READ) {
            if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Permesso concesso: apriamo il selettore audio
                openAudioPicker();
            } else {
                // Permesso negato: notifichiamo l'utente
                Toast.makeText(this,
                        "Permesso di lettura storage negato",
                        Toast.LENGTH_LONG).show();
            }
        }
    }

    // ===================== Implementazione MediaPlayerControl =====================

    @Override
    public void start() {
        if (mediaPlayer != null) {
            mediaPlayer.start();
        }
    }

    @Override
    public void pause() {
        if (mediaPlayer != null && mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
        }
    }

    @Override
    public int getDuration() {
        if (mediaPlayer != null) {
            try {
                return mediaPlayer.getDuration();
            } catch (IllegalStateException e) {
                return 0;
            }
        }
        return 0;
    }

    @Override
    public int getCurrentPosition() {
        if (mediaPlayer != null) {
            try {
                return mediaPlayer.getCurrentPosition();
            } catch (IllegalStateException e) {
                return 0;
            }
        }
        return 0;
    }

    @Override
    public void seekTo(int pos) {
        if (mediaPlayer != null) {
            try {
                mediaPlayer.seekTo(pos);
            } catch (IllegalStateException e) {
                // Ignoriamo o logghiamo l'errore
            }
        }
    }

    @Override
    public boolean isPlaying() {
        if (mediaPlayer != null) {
            try {
                return mediaPlayer.isPlaying();
            } catch (IllegalStateException e) {
                return false;
            }
        }
        return false;
    }

    @Override
    public int getBufferPercentage() {
        // Per file locali possiamo considerare 100% bufferizzato
        return 100;
    }

    @Override
    public boolean canPause() {
        return true;
    }

    @Override
    public boolean canSeekBackward() {
        return true;
    }

    @Override
    public boolean canSeekForward() {
        return true;
    }

    @Override
    public int getAudioSessionId() {
        if (mediaPlayer != null) {
            return mediaPlayer.getAudioSessionId();
        }
        return 0;
    }

    // ===================== Gestione ciclo di vita Activity =====================

    @Override
    protected void onPause() {
        super.onPause();
        // Se l'utente lascia l'Activity, mettiamo in pausa la riproduzione
        if (mediaPlayer != null && mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // Alla distruzione dell'Activity rilasciamo completamente il player
        releaseMediaPlayer();
    }

    // ===================== Callback SurfaceHolder (per SurfaceView) =====================

    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        // La Surface è stata creata: possiamo collegarla al MediaPlayer se esiste
        surfaceHolder = holder;
        if (mediaPlayer != null) {
            mediaPlayer.setDisplay(surfaceHolder);
        }
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
        // Cambiamenti nella Surface (rotazione, resize, ecc.)
    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
        // La Surface non è più disponibile: scolleghiamo il display dal MediaPlayer
        if (mediaPlayer != null) {
            mediaPlayer.setDisplay(null);
        }
        surfaceHolder = null;
    }
}

6. Gestione degli errori e stati del MediaPlayer

MediaPlayer è una macchina a stati finiti. Alcune operazioni sono permesse solo in determinati stati; chiamare un metodo nello stato sbagliato può generare IllegalStateException. È buona pratica:

  • Registrare OnErrorListener per intercettare errori di riproduzione.
  • Registrare OnCompletionListener per sapere quando la traccia è terminata.
  • Usare try/catch attorno ai metodi che possono lanciare IllegalStateException (es. getDuration(), getCurrentPosition(), start() in stati non validi).

Esempio di registrazione listener (già visto sopra):

mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
    @Override
    public void onCompletion(MediaPlayer mp) {
        // Esegui logica di fine riproduzione, es. passare al brano successivo
    }
});

mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        // Gestione custom: log, messaggi, fallback
        return true; // true indica che l'errore è stato gestito
    }
});

Alcuni codici di errore tipici (campo what):

  • MediaPlayer.MEDIA_ERROR_UNKNOWN
  • MediaPlayer.MEDIA_ERROR_SERVER_DIED

In generale, alla ricezione di un errore critico è consigliabile reset() + release() del MediaPlayer e chiedere all’utente di riprovare.

7. Best practice: risorse, rotazione, compatibilità API 26+

Rilascio risorse

Sempre:

  • Creare il MediaPlayer solo quando serve (es. dopo che l’utente ha scelto un file).
  • Chiamare reset() e release() in onDestroy() o quando l’Activity non userà più il player.
  • Evitare di mantenere riferimenti statici a MediaPlayer o MediaController nell’Activity.

Gestione rotazione schermo

La rotazione schermo distrugge e ricrea l’Activity per default. Per non interrompere la riproduzione puoi:

  • Usare un ViewModel o un Service per mantenere il MediaPlayer oltre il ciclo di vita dell’Activity.
  • Gestire manualmente i cambi di configurazione (meno consigliato) con android:configChanges nel manifest.
  • Salvare lo stato (posizione corrente) in onSaveInstanceState() e riposizionare con seekTo() dopo la ricreazione.

Compatibilità con versioni recenti (API 26+)

Con le versioni moderne di Android, tieni presente:

  • I limiti all’esecuzione in background e ai servizi (Foreground Service con notifica obbligatoria per riproduzione persistente).
  • I cambiamenti alla gestione dei permessi di storage (Scoped Storage da Android 10 in poi) — valuta l’uso di MediaStore e Storage Access Framework.
  • Per scenari complessi, considera l’adozione di Media3 (ExoPlayer) che offre gestione avanzata di buffering, DRM, playlist, ecc.

8. Conclusioni e risorse ufficiali

In questa guida abbiamo visto come usare MediaController in combinazione con MediaPlayer per creare una semplice app “AudioPlayer” in Java: dalla configurazione del progetto alla gestione del ciclo di vita, dei permessi e degli errori. Abbiamo anche accennato a MediaSession e alle best practice per le versioni recenti di Android.

Per approfondire e rimanere aggiornato sulle API multimediali ti consiglio di consultare la documentazione ufficiale:

Partendo da questi esempi puoi estendere l’app con playlist, notifiche multimediali, controlli su lockscreen e streaming da rete, adottando quando opportuno MediaSession e le API moderne di Media3.