1. Code
  2. Mobile Development
  3. Android Development

Audio de Fondo en Android Con MediaSessionCompat

Uno de los usos más populares para dispositivos móviles es reproducción de audio mediantes servicios de música en streaming, podcasts descargadoso cualquier otro número de fuentes de audio. Mientras que es una característica bastante común, es difícil de implementar, con muchas piezas diferentes que necesitan integrarse correctamente para poder dar a tu usuario una experiencia Android completa.
Scroll to top

Spanish (Español) translation by Rafael Chavarría (you can also view the original English article)

Uno de los usos más populares para dispositivos móviles es reproducción de audio mediantes servicios de música en streaming, podcasts descargadoso cualquier otro número de fuentes de audio. Mientras que es una característica bastante común, es difícil de implementar, con muchas piezas diferentes que necesitan integrarse correctamente para poder dar a tu usuario una experiencia Android completa.

En este tutorial aprenderás acerca de MediaSessionCompat desde la librería de soporte de Android, y cómo puede ser usada para crear un servicio apropiado de audio de fondo para tus usuarios.

Configuración

La primera cosa que necesitarás es incluír la librería de soporte de Android en tu proyecto. Esto se puede hacer agregando la siguiente línea en el archivo build.gradle de tu módulo bajo el nodo de dependencias.

1
compile 'com.android.support:support-v13:24.2.1'

Después de que has sincronizado tu proyecto, crea una nueva clase Java. Para este ejemplo llamaré a la clase BackgroundAudioService. Esta clase necesitará extender a MediaBrowserServiceCompat. También implementaremos las siguientes interfaces: MediaPlayer.OnCompletionListener y AudioManager.OnAudioFocusChangeListener.

Ahora que tu implementación de MediaBrowserServiceCompat está creada, tomemos un momento para actualizar el AndroidManifest.xml antes de regersar a esta clase. En la parte superior de la clase, necesitarás crear la petición del permiso WAKE_LOCK.

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

Después, dentro del nodo application, declara tu nuevo servicio con los siguientes objetos intent-filter. Estos permitirán a tu servicio interceptar botones de control, eventos de audífonos y exploración de medios para dispositivos, tales como Android Auto (aunque no haremos nada con Android Auto para este tutorial, algún soporte básico para este es aún requerido por MediaBrowserServiceCompat).

1
<service android:name=".BackgroundAudioService">
2
    <intent-filter>
3
        <action android:name="android.intent.action.MEDIA_BUTTON" />
4
        <action android:name="android.media.AUDIO_BECOMING_NOISY" />
5
        <action android:name="android.media.browse.MediaBrowserService" />
6
    </intent-filter>
7
</service>

Finalmente, necesitarás declarar el uso de MediaButtonReceiver desde la librería de soporte de Android. Esto te permitirá interceptar interacciones de botones de control de medios y eventos de audífonos en dispositivos corriendo KitKat y anteriores.

1
<receiver android:name="android.support.v4.media.session.MediaButtonReceiver">
2
    <intent-filter>
3
        <action android:name="android.intent.action.MEDIA_BUTTON" />
4
        <action android:name="android.media.AUDIO_BECOMING_NOISY" />
5
    </intent-filter>
6
</receiver>

Ahora que tu archivo AndroidManifest.xml está terminado, puedes cerrarlo. También vamos a crear otra clase llamada MediaStyleHelper, la cual fue escrita por Ian Lake, Desarrollador Defensor en Google, para limpiar la creación de estilo de notificaciones de medios.

1
public class MediaStyleHelper {
2
    /**

3
     * Build a notification using the information from the given media session. Makes heavy use

4
     * of {@link MediaMetadataCompat#getDescription()} to extract the appropriate information.

5
     * @param context Context used to construct the notification.

6
     * @param mediaSession Media session to get information.

7
     * @return A pre-built notification with information from the given media session.

8
     */
9
    public static NotificationCompat.Builder from(
10
            Context context, MediaSessionCompat mediaSession) {
11
        MediaControllerCompat controller = mediaSession.getController();
12
        MediaMetadataCompat mediaMetadata = controller.getMetadata();
13
        MediaDescriptionCompat description = mediaMetadata.getDescription();
14
15
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
16
        builder
17
                .setContentTitle(description.getTitle())
18
                .setContentText(description.getSubtitle())
19
                .setSubText(description.getDescription())
20
                .setLargeIcon(description.getIconBitmap())
21
                .setContentIntent(controller.getSessionActivity())
22
                .setDeleteIntent(
23
                        MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP))
24
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
25
        return builder;
26
    }
27
}

Una vez creada, procede a cerrar el archivo. Nos concentraremos en el servicio de audio de fondo en la siguiente sección.

Construyendo el Servicio de Audio de Fondo

Ahora es tiempo de adentrarse en el núcleo de la creación de tu app de medios. Hay algunas cuantas variables que querrás declarar primero para esta aplicación de muestra: un MediaPlayer la reproducción y un objeto MediaSessionCompat que administrará metadatos y controles/estados de reproducción.

1
private MediaPlayer mMediaPlayer;
2
private MediaSessionCompat mMediaSessionCompat;

Adicionalmente, necesitarás un BroadcastReceiver que escuche cambios en el estado de los audífonos. Para mantener las cosas simples, este receptor pausará el MediaPlayer, si está reproduciéndose.

1
private BroadcastReceiver mNoisyReceiver = new BroadcastReceiver() {
2
    @Override
3
    public void onReceive(Context context, Intent intent) {
4
        if( mMediaPlayer != null && mMediaPlayer.isPlaying() ) {
5
            mMediaPlayer.pause();
6
        }
7
    }
8
};

Para la variable final, necesitarás crear un objeto MediaSessionCompat.Callback, el cuál es usado para manejar el estado de reproducción cuando ocurren actiones de sesión de medios.

1
private MediaSessionCompat.Callback mMediaSessionCallback = new MediaSessionCompat.Callback() {
2
    
3
    @Override
4
    public void onPlay() {
5
        super.onPlay();
6
    }
7
8
    @Override
9
    public void onPause() {
10
        super.onPause();
11
    }
12
13
    @Override
14
    public void onPlayFromMediaId(String mediaId, Bundle extras) {
15
        super.onPlayFromMediaId(mediaId, extras);
16
    }
17
};

Revisitaremos cada uno de los métodos anteriores más adelante en este tutorial, ya que serán usados para manejar operaciones en nuesta app de medios.

Hay dos métodos que también necesitaremos declarar, aunque no necesitarán hacer nada para los propósitos de este tutorial: onGetRoot() y onLoadChildren(). Puedes usar el siguiente código para tus valores por defecto.

1
@Nullable
2
@Override
3
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
4
    if(TextUtils.equals(clientPackageName, getPackageName())) {
5
        return new BrowserRoot(getString(R.string.app_name), null);
6
    }
7
8
    return null;
9
}
10
11
//Not important for general audio service, required for class

12
@Override
13
public void onLoadChildren(@NonNull String parentId, @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
14
    result.sendResult(null);
15
}

Por último, querrás anular el método onStartCommand(), el cuál es el punto de entrada a tu Service. Este método tomará el Intent que se pasa al Service y lo manda a la clase MediaButtonReceiver.

1
@Override
2
public int onStartCommand(Intent intent, int flags, int startId) {
3
    MediaButtonReceiver.handleIntent(mMediaSessionCompat, intent);
4
    return super.onStartCommand(intent, flags, startId);
5
}

Inicializando Todas las Cosas

Ahora que tus variables base están creadas, es tiempo de inicializar todo. Haremos esto llamando varios métodos de ayuda en onCreate().

1
@Override
2
public void onCreate() {
3
    super.onCreate();
4
5
    initMediaPlayer();
6
    initMediaSession();
7
    initNoisyReceiver();
8
}

El primer método, initMediaPlayer(), inicializará el objeto MediaPlayer que creamos al inicio de la clase, solicitar un bloqueo de inicio parcial (por lo que requerimos ese permiso en AndroidManifest.xml), y fijamos el volumen del reproductor.

1
private void initMediaPlayer() {
2
    mMediaPlayer = new MediaPlayer();
3
    mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
4
    mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
5
    mMediaPlayer.setVolume(1.0f, 1.0f);
6
}

El siguiente método, initMediaSession(), es donde inicializamos el objeto MediaSessionCompat y lo enlazamos a los métodos de botones y control que nos permiten manejar la reproducción y entrada del usuario. Este método inicia creando un objeto ComponentName que apunta a la clase MediaButtonReceiver de la librería de soporte de Android y la usa para crear un nuevo MediaSessionCompat. Entonces pasamos el objeto MediaSession.Callback que creamos anteriormente para este y fijamos las banderas necesarias para recibir señales de botones de entrada y control. Después, creamos un nuevo Intent para manejar botones de entrada de medios en dispositivos pre-Lollipop y fijamos el media session token para nuestro servicio.

1
private void initMediaSession() {
2
    ComponentName mediaButtonReceiver = new ComponentName(getApplicationContext(), MediaButtonReceiver.class);
3
    mMediaSessionCompat = new MediaSessionCompat(getApplicationContext(), "Tag", mediaButtonReceiver, null);
4
5
    mMediaSessionCompat.setCallback(mMediaSessionCallback);
6
    mMediaSessionCompat.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS );
7
8
    Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
9
    mediaButtonIntent.setClass(this, MediaButtonReceiver.class);
10
    PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0);
11
    mMediaSessionCompat.setMediaButtonReceiver(pendingIntent);
12
13
    setSessionToken(mMediaSessionCompat.getSessionToken());
14
}

Finalmente, registraremos el BroadcastReceiver que creamos al inicio de la clase para que podamos escuchar eventos de cambios en audífonos.

1
private void initNoisyReceiver() {
2
    //Handles headphones coming unplugged. cannot be done through a manifest receiver

3
    IntentFilter filter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
4
    registerReceiver(mNoisyReceiver, filter);
5
}

Manejando Audio Focus

Ahora que has termindado de inicializar los objetos BroadcastReceiver, MediaSessionCompat y MediaPlayer, es tiempo de echar un vistazo al manejo de audio focus.

Mientras que podemos pensar que nuestras propias apps de audio son las más importantes por el momento, otras apps en el dispositivo estarán compitiendo por hacer sus propios sonidos, tales como la notificación de email o un juego móvil. Para poder trabajar con estas distintas situaciones, el sitema Android usa audio focus para determinar como debería ser manejado el audio.

EL primer caso que querremos manejar es comenzar a reproducir e intentar recibir la atención del dispositivo. En tu objeto MediaSessionCompat.Callback, ve al método onPlay() y agrega la siguiente verificación de condición.

1
@Override
2
public void onPlay() {
3
    super.onPlay();
4
    if( !successfullyRetrievedAudioFocus() ) {
5
        return;
6
    }
7
}

El código anterior llamará al método de ayuda que intenta atraer atención y si no puede, simplemente regresa. En la app real, quisieras manejar reproducción fallida de audio de mejor manera. successfullyRetrievedAudioFocus() obtendrá una referencia al AudioManager del sistema e intentará solicitar atención de audio para reproducir música. Entonces regresará un boolean representando si la petición fue exitosa o no.

1
private boolean successfullyRetrievedAudioFocus() {
2
    AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
3
4
    int result = audioManager.requestAudioFocus(this,
5
            AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
6
    
7
    return result == AudioManager.AUDIOFOCUS_GAIN;
8
}

Notarás que también estamos pasando this al método requestAudioFocus(), lo que asocia al OnAudioFocusChangeListener con nuestro servicio. Hay algunos cuantos estados diferentes que querrás escuchar para ser un "buen ciudadano" en el ecosistema de la app del dispositivo.

  • AudioManager.AUDIOFOCUS_LOSS: Esto ocurre cuando otra app ha solicitado atención de audio. Cuando esto sucede, deberías detener la reproducción de audio en tu app.
  • AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: Este estado es ingresado cuando otra aplicación quiere reproducir audio, pero solo anticipa necesitar foco por un periodo corto de tiempo. Puedes usar este estado para pauser tu reproducción de audio.
  • AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:Cuando la atención de audio es solicitada, pero arroja un estado 'can duck', significa que puedes continuar tu reproducciónm pero deberías bajar el volumen un poco. Esto puede ocurrir cunado un sonido de notificación es reproducida por el dispositivo.
  • AudioManager.AUDIOFOCUS_GAIN: El último estado que discutiremos es AUDIOFOCUS_GAIN. Este es el estado cuando la reproducción acoplable de audio se ha completado y tu app puede reanudar sus niveles previos.

Un callback onAudioFocusChange() pudiera verse como esto:

1
@Override
2
public void onAudioFocusChange(int focusChange) {
3
    switch( focusChange ) {
4
        case AudioManager.AUDIOFOCUS_LOSS: {
5
            if( mMediaPlayer.isPlaying() ) {
6
                mMediaPlayer.stop();
7
            }
8
            break;
9
        }
10
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: {
11
            mMediaPlayer.pause();
12
            break;
13
        }
14
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
15
            if( mMediaPlayer != null ) {
16
                mMediaPlayer.setVolume(0.3f, 0.3f);
17
            }
18
            break;
19
        }
20
        case AudioManager.AUDIOFOCUS_GAIN: {
21
            if( mMediaPlayer != null ) {
22
                if( !mMediaPlayer.isPlaying() ) {
23
                    mMediaPlayer.start();
24
                }
25
                mMediaPlayer.setVolume(1.0f, 1.0f);
26
            }
27
            break;
28
        }
29
    }
30
}

Entendiendo el MediaSessionCompat.Callback

Ahora que tienes una estructura general para tu Service, es tiempo de entrar en el MediaSessionCompat.Callback. En la última sección agregaste un poco a onPlay() para revisar si la atención de audio fue otorgada. Debajo de la declaración condicional, querrás fijar el objeto MediaSessionCompat a activo, darle un estado de STATE_PLAYING y asignar las acciones apropiadas necesarias para crear botones de pausa en controles de bloqueo de pantalla pre-Lollipop, notificaciones de teléfono y Android Wear.

1
@Override
2
public void onPlay() {
3
    super.onPlay();
4
    if( !successfullyRetrievedAudioFocus() ) {
5
        return;
6
    }
7
8
    mMediaSessionCompat.setActive(true);
9
    setMediaPlaybackState(PlaybackStateCompat.STATE_PLAYING);
10
    
11
    ...
12
}

El método setMediaPlaybackState() anterior es un método de ayuda que crea un objeto PlaybackStateCompat.Builder y provee las acciones y estado apropiados y después construye y asocia un PlaybackStateCompat con tu objeto MediaSessionCompat.

1
private void setMediaPlaybackState(int state) {
2
    PlaybackStateCompat.Builder playbackstateBuilder = new PlaybackStateCompat.Builder();
3
    if( state == PlaybackStateCompat.STATE_PLAYING ) {
4
        playbackstateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_PAUSE);
5
    } else {
6
        playbackstateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_PLAY);
7
    }
8
    playbackstateBuilder.setState(state, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 0);
9
    mMediaSessionCompat.setPlaybackState(playbackstateBuilder.build());
10
}

Es importante que notes que necesitarás tanto la bandera ACTION_PLAY_PAUSE y la ACTION_PAUSE or ACTION_PLAY en tus acciones para tener controles apropiados en Android Wear.

Media notification on Android WearMedia notification on Android WearMedia notification on Android Wear

De regreso en onPlay(), querrás mostrar una notificación de reproducción que está asociada con tu objeto MediaSessionCompat al usar la clase MediaStyHelper que definimos anteriormente y después mostrar esa notificación.

1
private void showPlayingNotification() {
2
    NotificationCompat.Builder builder = MediaStyleHelper.from(BackgroundAudioService.this, mMediaSessionCompat);
3
    if( builder == null ) {
4
        return;
5
    }
6
7
8
    builder.addAction(new NotificationCompat.Action(android.R.drawable.ic_media_pause, "Pause", MediaButtonReceiver.buildMediaButtonPendingIntent(this, PlaybackStateCompat.ACTION_PLAY_PAUSE)));
9
    builder.setStyle(new NotificationCompat.MediaStyle().setShowActionsInCompactView(0).setMediaSession(mMediaSessionCompat.getSessionToken()));
10
    builder.setSmallIcon(R.mipmap.ic_launcher);
11
    NotificationManagerCompat.from(BackgroundAudioService.this).notify(1, builder.build());
12
}

Finalmente, empezarás el MediaPlayer al final de onPlay().

1
@Override
2
public void onPlay() {
3
    super.onPlay();
4
5
    ...
6
7
    showPlayingNotification();
8
    mMediaPlayer.start();
9
}
Media control notification on an Android Nougat deviceMedia control notification on an Android Nougat deviceMedia control notification on an Android Nougat device

Cuando el callback recibe un comando de pausa, onPause() será llamado. Aquí pausarás el MediaPlayer, fijarás el estado a STATE_PAUSED y mostrarás una notificación pausada.

1
@Override
2
public void onPause() {
3
    super.onPause();
4
5
    if( mMediaPlayer.isPlaying() ) {
6
        mMediaPlayer.pause();
7
        setMediaPlaybackState(PlaybackStateCompat.STATE_PAUSED);
8
        showPausedNotification();
9
    }
10
}

Nuestro método ayudante showPausedNotification() trabajará de manera similar al método showPlayNotification().

1
private void showPausedNotification() {
2
    NotificationCompat.Builder builder = MediaStyleHelper.from(this, mMediaSessionCompat);
3
    if( builder == null ) {
4
        return;
5
    }
6
7
    builder.addAction(new NotificationCompat.Action(android.R.drawable.ic_media_play, "Play", MediaButtonReceiver.buildMediaButtonPendingIntent(this, PlaybackStateCompat.ACTION_PLAY_PAUSE)));
8
    builder.setStyle(new NotificationCompat.MediaStyle().setShowActionsInCompactView(0).setMediaSession(mMediaSessionCompat.getSessionToken()));
9
    builder.setSmallIcon(R.mipmap.ic_launcher);
10
    NotificationManagerCompat.from(this).notify(1, builder.build());
11
}

El siguiente método en el callback que discutiremos, onPlayFromMediaId(), toma un String y un Bundle como parámetros. Este es el método callback que puedes usar para cambiar pistas de audio/contenido dentro de tu aplicación.

Para este tutorial, simplemente aceptaremos un ID de recurso en bruto e intentaremos reproducir eso y después reinicializar los metadatos de la sesión. Mientras que es permitido pasar el Bundle a este método, puedes usarlo para personalizar otros aspectos de tu reproducción de medios, como fijar una un sonido de fondo personalizado de fondo para una pista.

1
@Override
2
public void onPlayFromMediaId(String mediaId, Bundle extras) {
3
    super.onPlayFromMediaId(mediaId, extras);
4
5
    try {
6
        AssetFileDescriptor afd = getResources().openRawResourceFd(Integer.valueOf(mediaId));
7
        if( afd == null ) {
8
            return;
9
        }
10
11
        try {
12
            mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
13
14
        } catch( IllegalStateException e ) {
15
            mMediaPlayer.release();
16
            initMediaPlayer();
17
            mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
18
        }
19
20
        afd.close();
21
        initMediaSessionMetadata();
22
23
    } catch (IOException e) {
24
        return;
25
    }
26
27
    try {
28
        mMediaPlayer.prepare();
29
    } catch (IOException e) {}
30
31
    //Work with extras here if you want

32
}

Ahora que hemos discutido los dos métodos principales en este callback que usarás en tus apps, es importante saber que hay otros métodos opcionales que puedes usar para personalizar tu servicio. Algunos métodos incluyen onSeekTo(), el cuál te permite cambiar la posición de reproducción de tu contenido y onCommand(), el cuál acepta un String denotando el tipo de comando, un Bundle para información extra acerca del comando y un callback ResultReceiver, el cuál te permitirá enviar comandos pesonalizados a tu Service.

1
@Override
2
public void onCommand(String command, Bundle extras, ResultReceiver cb) {
3
    super.onCommand(command, extras, cb);
4
    if( COMMAND_EXAMPLE.equalsIgnoreCase(command) ) {
5
        //Custom command here

6
    }
7
}
8
9
@Override
10
public void onSeekTo(long pos) {
11
    super.onSeekTo(pos);
12
}

Desbaratando

Cuando nuestro archivo de audio se ha completado, querremos decidir cuál será la siguiente acción. Mientras que tal vez quieras reproducir la siguiente pista en tu app, mantendremos las cosas simples y liberaremos el MediaPlayer.

1
@Override
2
public void onCompletion(MediaPlayer mediaPlayer) {
3
    if( mMediaPlayer != null ) {
4
        mMediaPlayer.release();
5
    }
6
}

Finalmente, vamos a querer hacer unas cuantas cosas en el método onDestroy() de nuestro Service. Primero, obtenemos una referencia del servicio de sistema AudioManager y llamamos abandonAudioFocus() con nuestro AudioFocusChangeListener como parámetro, el cuál notificará a otras aplicaciones en el dispositivo que estarás cediendo la atención del audio. Después, anular el registro de BroadcastReceiver que se configuró para escuchar cambios de audífonos y liberar el objeto MediaSessionCompat. Finalmente, deberás querer cancelar la notificación de control de reproducción.

1
@Override
2
public void onDestroy() {
3
    super.onDestroy();
4
    AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
5
    audioManager.abandonAudioFocus(this);
6
    unregisterReceiver(mNoisyReceiver);
7
    mMediaSessionCompat.release();
8
    NotificationManagerCompat.from(this).cancel(1);
9
}

En este punto, deberías tener un Service básico de audio de fondo usando MediaSessionCompat para control de reproducción a través de dispositivos. Mientras que mucho se ha visto involucrado en crear el servicio, deberías poder controlar la reproducción desde tu app, una notificación, controles en dispositivos pre-Lollipop (Lollipop y posteriores usarán una notifiación en la pantalla de bloqueo), y desde dispositivos periféricos, como Android Wear, una vez que el Service ha sido iniciado.

Media lock screen controls on Android Kit KatMedia lock screen controls on Android Kit KatMedia lock screen controls on Android Kit Kat

Iniciando y Controlando Contenido desde una Activity

Mientras que la mayoría de los controles serán automáticos, aún tendrás un poco de trabajo para iniciar y controlar una sesión de medios desde los controles de tu app. Mínimo, vas a querer objetos MediaBrowserCompat.ConnectionCallback, MediaControllerCompat.Callback, MediaBrowserCompat y MediaControllerCompat creados en tu app.

MediaControllerCompat.Callback tendrá un método llamado onPlaybackStateChanged() que recibe cambios en el estado de reproducción y que puede ser usado para mantener tu UI en sincronía.

1
private MediaControllerCompat.Callback mMediaControllerCompatCallback = new MediaControllerCompat.Callback() {
2
3
    @Override
4
    public void onPlaybackStateChanged(PlaybackStateCompat state) {
5
        super.onPlaybackStateChanged(state);
6
        if( state == null ) {
7
            return;
8
        }
9
        
10
        switch( state.getState() ) {
11
            case PlaybackStateCompat.STATE_PLAYING: {
12
                mCurrentState = STATE_PLAYING;
13
                break;
14
            }
15
            case PlaybackStateCompat.STATE_PAUSED: {
16
                mCurrentState = STATE_PAUSED;
17
                break;
18
            }
19
        }
20
    }
21
};

MediaBrowserCompat.ConnectionCallback tiene un método onConnected() que será llamado cuando un nuevo objeto MediaBrowserCompat es creado y conectado. Puedes usar esto para inicializar tu objeto MediaControllerCompat, enlazarlo a tu MediaControllerCompat.Callback y asociarlo con MediaSessionCompat desde tu Service. Una vez que está completo, puedes iniciar tu reproducción de audio desde este método.

1
private MediaBrowserCompat.ConnectionCallback mMediaBrowserCompatConnectionCallback = new MediaBrowserCompat.ConnectionCallback() {
2
3
    @Override
4
    public void onConnected() {
5
        super.onConnected();
6
        try {
7
            mMediaControllerCompat = new MediaControllerCompat(MainActivity.this, mMediaBrowserCompat.getSessionToken());
8
            mMediaControllerCompat.registerCallback(mMediaControllerCompatCallback);
9
            setSupportMediaController(mMediaControllerCompat);
10
            getSupportMediaController().getTransportControls().playFromMediaId(String.valueOf(R.raw.warner_tautz_off_broadway), null);
11
12
        } catch( RemoteException e ) {
13
14
        }
15
    }
16
};

Notarás que el siguiente código usa getSupportMediaController().getTransportControls() para comunicarse con la sesión de medios. Usando la misma técnica, puedes llamar tu onPlay() y onPause() en el objeto MediaSessionCompat.Callback de tu servicio de audio.

1
if( mCurrentState == STATE_PAUSED ) {
2
    getSupportMediaController().getTransportControls().play();
3
    mCurrentState = STATE_PLAYING;
4
} else {
5
    if( getSupportMediaController().getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING ) {
6
        getSupportMediaController().getTransportControls().pause();
7
    }
8
9
    mCurrentState = STATE_PAUSED;
10
}

Cuando termines con tu reproducción de audio, puedes pausar el servicio de audio y desconectar tu objeto MediaBrowserCompat, el cual haremos en este tutorial cuando esta Activity se destruya.

1
@Override
2
protected void onDestroy() {
3
    super.onDestroy();
4
    if( getSupportMediaController().getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING ) {
5
        getSupportMediaController().getTransportControls().pause();
6
    }
7
8
    mMediaBrowserCompat.disconnect();
9
}

Concluyendo

Whew! Como puedes ver, hay muchas piezas móviles involucradas en crear y usar un servicio de audio de fondo de manera correcta.

En este tutorial, has creado un Service que reproduce un simple archivo de audio, escucha por cambios en la atención del audio y enlaza a MediaSessionCompat para proveer control universal de reproducción en dispositivos Android, incluyendo auriculares y Android Wear. Si te encuentras con bloqueos mientras trabajas con este tutorial, te recomiendo ampliamente revisar el código asociado al proyecto Android en el GitHub de Envato Tuts+.

Y revisa algunos de nuestros otros cursos y tutoriales de Android aquí en Envato Tuts+!.