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.
SurfaceViewoVideoView). - 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
OnErrorListenerper intercettare errori di riproduzione. - Registrare
OnCompletionListenerper sapere quando la traccia è terminata. - Usare
try/catchattorno ai metodi che possono lanciareIllegalStateException(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_UNKNOWNMediaPlayer.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
MediaPlayersolo quando serve (es. dopo che l’utente ha scelto un file). - Chiamare
reset()erelease()inonDestroy()o quando l’Activity non userà più il player. - Evitare di mantenere riferimenti statici a
MediaPlayeroMediaControllernell’Activity.
Gestione rotazione schermo
La rotazione schermo distrugge e ricrea l’Activity per default. Per non interrompere la riproduzione puoi:
- Usare un
ViewModelo unServiceper mantenere ilMediaPlayeroltre il ciclo di vita dell’Activity. - Gestire manualmente i cambi di configurazione (meno consigliato) con
android:configChangesnel manifest. - Salvare lo stato (posizione corrente) in
onSaveInstanceState()e riposizionare conseekTo()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
MediaStoreeStorage 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:
- MediaController (Android Docs)
- MediaPlayer (Android Docs)
- Guida all’uso di MediaPlayer
- Media3 e ExoPlayer
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.