public abstract class

MediaSessionService

extends Service

 java.lang.Object

↳Service

↳androidx.media2.session.MediaSessionService

Subclasses:

MediaLibraryService

Gradle dependencies

compile group: 'androidx.media2', name: 'media2-session', version: '1.2.1'

  • groupId: androidx.media2
  • artifactId: media2-session
  • version: 1.2.1

Artifact androidx.media2:media2-session:1.2.1 it located at Google repository (https://maven.google.com/)

Overview

Base class for media session services, which is the service containing MediaSession.

It's highly recommended for an app to use this if it wants to keep media playback in the background.

Here are the benefits of using MediaSessionService.

  • Another app can know that your app supports MediaSession even when your app isn't running.
  • Another app can start playback of your app even when your app isn't running.
For example, user's voice command can start playback of your app even when it's not running.

To extend this class, adding followings directly to your AndroidManifest.xml.

 <service android:name="component_name_of_your_implementation" >
   <intent-filter>
     <action android:name="androidx.media2.session.MediaSessionService" />
   </intent-filter>
 </service>

You may also declare

android.media.browse.MediaBrowserService
for compatibility with android.support.v4.media.MediaBrowserCompat. This service can handle it automatically.

It's recommended for an app to have a single MediaSessionService declared in the manifest. Otherwise, your app might be shown twice in the list of the Auto/Wearable, or another app fails to pick the right session service when it wants to start the playback of this app. If you want to provide multiple sessions here, take a look at Supporting Multiple Sessions.

Topics covered here:

  1. Service Lifecycle
  2. Permissions
  3. Supporting Multiple Sessions

Service Lifecycle

Session service is a bound service. When a MediaController is created for the session service, the controller binds to the session service. MediaSessionService would be called inside of the MediaSessionService.onBind(Intent).

After the binding, session's MediaSession.SessionCallback.onConnect(MediaSession, MediaSession.ControllerInfo) will be called to accept or reject connection request from a controller. If the connection is rejected, the controller will unbind. If it's accepted, the controller will be available to use and keep binding.

When playback is started for this session service, MediaSessionService.onUpdateNotification(MediaSession) is called for the playback's session and service would become a foreground service. It's needed to keep playback after the controller is destroyed. The session service becomes background service when all playbacks are stopped. Apps targeting API or later must request the permission in order to make the service foreground.

The service is destroyed when the all sessions are closed, or no media controller is binding to the session while the service is not running as a foreground service.

Permissions

Any app can bind to the session service with controller, but the controller can be used only if the session service accepted the connection request through MediaSession.SessionCallback.onConnect(MediaSession, MediaSession.ControllerInfo).

Supporting Multiple Sessions

Generally speaking, multiple sessions aren't necessary for most media apps. One exception is if your app can play multiple media content at the same time, but only for the playback of video-only media or remote playback, since audio focus policy recommends not playing multiple audio content at the same time. Also keep in mind that multiple media sessions would make Android Auto and Bluetooth device with display to show your apps multiple times, because they list up media sessions, not media apps.

However, if you're capable of handling multiple playback and want to keep their sessions while the app is in the background, create multiple sessions and add to this service with MediaSessionService.addSession(MediaSession).

Note that MediaController can be created with SessionToken for connecting any session in this service. In that case, MediaSessionService will be called to know which session to handle incoming connection request. Pick the best session among added sessions, or create new one and return from the MediaSessionService.

Summary

Fields
public static final java.lang.StringSERVICE_INTERFACE

The that must be declared as handled by the service.

Constructors
publicMediaSessionService()

Methods
public final voidaddSession(MediaSession session)

Adds a session to this service.

public final java.util.List<MediaSession>getSessions()

Gets the list of MediaSessions that you've added to this service via MediaSessionService.addSession(MediaSession) or MediaSessionService.

public IBinderonBind(Intent intent)

Default implementation for MediaSessionService to handle incoming binding request.

public voidonCreate()

Called by the system when the service is first created.

public voidonDestroy()

Called by the system to notify that it is no longer used and is being removed.

public abstract MediaSessiononGetSession(MediaSession.ControllerInfo controllerInfo)

Called when a MediaController is created with the this service's SessionToken.

public intonStartCommand(Intent intent, int flags, int startId)

public MediaSessionService.MediaNotificationonUpdateNotification(MediaSession session)

Called when notification UI needs update.

public final voidremoveSession(MediaSession session)

Removes a session from this service.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final java.lang.String SERVICE_INTERFACE

The that must be declared as handled by the service.

Constructors

public MediaSessionService()

Methods

public void onCreate()

Called by the system when the service is first created. Do not call this method directly.

Override this method if you need your own initialization. Derived classes MUST call through to the super class's implementation of this method.

public abstract MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo)

Called when a MediaController is created with the this service's SessionToken. Return the session for telling the controller which session to connect. Return null to reject the connection from this controller.

Session service automatically maintains the returned session. In other words, session returned here will be added here and removed when the session is closed. You don't need to manually call MediaSessionService.addSession(MediaSession) nor MediaSessionService.removeSession(MediaSession).

There are two special cases where the MediaSession.ControllerInfo.getPackageName() returns non-existent package name:

  • When the service is being started through the media button intent, the method will return . If you want to allow the service being started by the media button events, do not return null.
  • When the legacy or android.support.v4.media.MediaBrowserCompat tries to connect, the method will return MediaBrowserServiceCompat.SERVICE_INTERFACE. If you want to allow the service being bound by the legacy media browsers, do not return null.
For those special cases, the values returned by MediaSession.ControllerInfo.getUid() and MediaSession.ControllerInfo.getConnectionHints() have no meaning.

This method is always called on the main thread.

Parameters:

controllerInfo: information of the controller which is trying to connect

Returns:

a MediaSession instance for the controller to connect to, or null to reject connection

See also: MediaSession.Builder, MediaSessionService.getSessions()

public final void addSession(MediaSession session)

Adds a session to this service. This is not necessary for most media apps. See Supporting Multiple Sessions for detail.

Added session will be removed automatically when it's closed, or removed when MediaSessionService.removeSession(MediaSession) is called.

Parameters:

session: a session to be added.

See also: MediaSessionService.removeSession(MediaSession)

public final void removeSession(MediaSession session)

Removes a session from this service. This is not necessary for most media apps. See Supporting Multiple Sessions for detail.

Parameters:

session: a session to be removed.

See also: MediaSessionService.addSession(MediaSession)

public MediaSessionService.MediaNotification onUpdateNotification(MediaSession session)

Called when notification UI needs update. Override this method to show or cancel your own notification UI.

This would be called on MediaSession's callback executor when player state is changed, or when the current media item of the session is changed.

With the notification returned here, the service becomes foreground service when the playback is started. Apps targeting API or later must request the permission in order to use this API. It becomes background service after the playback is stopped.

Parameters:

session: a session that needs notification update

Returns:

a MediaSessionService.MediaNotification. Can be null

public final java.util.List<MediaSession> getSessions()

Gets the list of MediaSessions that you've added to this service via MediaSessionService.addSession(MediaSession) or MediaSessionService.

Returns:

sessions

public IBinder onBind(Intent intent)

Default implementation for MediaSessionService to handle incoming binding request. If the request is for getting the session, the intent will have action MediaSessionService.SERVICE_INTERFACE.

Override this method if this service also needs to handle binder requests other than MediaSessionService.SERVICE_INTERFACE. Derived classes MUST call through to the super class's implementation of this method.

Parameters:

intent:

Returns:

Binder

public int onStartCommand(Intent intent, int flags, int startId)

public void onDestroy()

Called by the system to notify that it is no longer used and is being removed. Do not call this method directly.

Override this method if you need your own clean up. Derived classes MUST call through to the super class's implementation of this method.

Source

/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.media2.session;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.media.MediaBrowserServiceCompat;
import androidx.media2.session.MediaSession.ControllerInfo;

import java.util.List;

/**
 * Base class for media session services, which is the service containing {@link MediaSession}.
 * <p>
 * It's highly recommended for an app to use this if it wants to keep media playback in the
 * background.
 * <p>
 * Here are the benefits of using {@link MediaSessionService}.
 * <ul>
 * <li>Another app can know that your app supports {@link MediaSession} even when your app
 * isn't running.
 * <li>Another app can start playback of your app even when your app isn't running.
 * </ul>
 * For example, user's voice command can start playback of your app even when it's not running.
 * <p>
 * To extend this class, adding followings directly to your {@code AndroidManifest.xml}.
 * <pre>
 * &lt;service android:name="component_name_of_your_implementation" &gt;
 *   &lt;intent-filter&gt;
 *     &lt;action android:name="androidx.media2.session.MediaSessionService" /&gt;
 *   &lt;/intent-filter&gt;
 * &lt;/service&gt;</pre>
 * <p>
 * You may also declare <pre>android.media.browse.MediaBrowserService</pre> for compatibility with
 * {@link android.support.v4.media.MediaBrowserCompat}. This service can handle it automatically.
 * <p>
 * It's recommended for an app to have a single {@link MediaSessionService} declared in the
 * manifest. Otherwise, your app might be shown twice in the list of the Auto/Wearable, or another
 * app fails to pick the right session service when it wants to start the playback of this app.
 * If you want to provide multiple sessions here, take a look at
 * <a href="#MultipleSessions">Supporting Multiple Sessions</a>.
 * <p>
 * Topics covered here:
 * <ol>
 * <li><a href="#ServiceLifecycle">Service Lifecycle</a>
 * <li><a href="#Permissions">Permissions</a>
 * <li><a href="#MultipleSessions">Supporting Multiple Sessions</a>
 * </ol>
 * <div>
 * <h3 id="ServiceLifecycle">Service Lifecycle</h3>
 * <p>
 * Session service is a bound service. When a {@link MediaController} is created for the
 * session service, the controller binds to the session service.
 * {@link #onGetSession(ControllerInfo)} would be called inside of the {@link #onBind(Intent)}.
 * <p>
 * After the binding, session's
 * {@link MediaSession.SessionCallback#onConnect(MediaSession, MediaSession.ControllerInfo)}
 * will be called to accept or reject connection request from a controller. If the connection is
 * rejected, the controller will unbind. If it's accepted, the controller will be available to use
 * and keep binding.
 * <p>
 * When playback is started for this session service, {@link #onUpdateNotification(MediaSession)}
 * is called for the playback's session and service would become a foreground service. It's needed
 * to keep playback after the controller is destroyed. The session service becomes background
 * service when all playbacks are stopped. Apps targeting API
 * {@link android.os.Build.VERSION_CODES#P} or later must request the permission
 * {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to make the service foreground.
 * <p>
 * The service is destroyed when the all sessions are closed, or no media controller is binding to
 * the session while the service is not running as a foreground service.
 * <h3 id="Permissions">Permissions</h3>
 * <p>
 * Any app can bind to the session service with controller, but the controller can be used only if
 * the session service accepted the connection request through
 * {@link MediaSession.SessionCallback#onConnect(MediaSession, MediaSession.ControllerInfo)}.
 * <h3 id="MultipleSessions">Supporting Multiple Sessions</h3>
 * Generally speaking, multiple sessions aren't necessary for most media apps. One exception is if
 * your app can play multiple media content at the same time, but only for the playback of
 * video-only media or remote playback, since
 * <a href="{@docRoot}guide/topics/media-apps/audio-focus.html">audio focus policy</a> recommends
 * not playing multiple audio content at the same time. Also keep in mind that multiple media
 * sessions would make Android Auto and Bluetooth device with display to show your apps multiple
 * times, because they list up media sessions, not media apps.
 * <p>
 * However, if you're capable of handling multiple playback and want to keep their sessions while
 * the app is in the background, create multiple sessions and add to this service with
 * {@link #addSession(MediaSession)}.
 * <p>
 * Note that {@link MediaController} can be created with {@link SessionToken} for
 * connecting any session in this service. In that case, {@link #onGetSession(ControllerInfo)} will
 * be called to know which session to handle incoming connection request. Pick the best session
 * among added sessions, or create new one and return from the
 * {@link #onGetSession(ControllerInfo)}.
 * </div>
 */
public abstract class MediaSessionService extends Service {
    /**
     * The {@link Intent} that must be declared as handled by the service.
     */
    public static final String SERVICE_INTERFACE = "androidx.media2.session.MediaSessionService";

    private final MediaSessionServiceImpl mImpl;

    public MediaSessionService() {
        super();
        // Note: This service doesn't have valid context at this moment.
        mImpl = createImpl();
    }

    MediaSessionServiceImpl createImpl() {
        return new MediaSessionServiceImplBase();
    }

    /**
     * Called by the system when the service is first created. Do not call this method directly.
     * <p>
     * Override this method if you need your own initialization. Derived classes MUST call through
     * to the super class's implementation of this method.
     */
    @CallSuper
    @Override
    public void onCreate() {
        super.onCreate();
        mImpl.onCreate(this);
    }

    /**
     * Called when a {@link MediaController} is created with the this service's
     * {@link SessionToken}. Return the session for telling the controller which session to
     * connect. Return {@code null} to reject the connection from this controller.
     * <p>
     * Session service automatically maintains the returned session. In other words, session
     * returned here will be added here and removed when the session is closed.  You don't need to
     * manually call {@link #addSession(MediaSession)} nor {@link #removeSession(MediaSession)}.
     * <p>
     * There are two special cases where the {@link ControllerInfo#getPackageName()} returns
     * non-existent package name:
     * <ul>
     *     <li>
     *         When the service is being started through the media button intent, the method will
     *         return {@link Intent#ACTION_MEDIA_BUTTON}. If you want to allow the service being
     *         started by the media button events, do not return {@code null}.
     *     </li>
     *     <li>
     *         When the legacy {@link android.media.browse.MediaBrowser} or
     *         {@link android.support.v4.media.MediaBrowserCompat} tries to connect, the method will
     *         return {@link MediaBrowserServiceCompat#SERVICE_INTERFACE}. If you want to allow the
     *         service being bound by the legacy media browsers, do not return {@code null}.
     *     </li>
     * </ul>
     * For those special cases, the values returned by {@link ControllerInfo#getUid()} and
     * {@link ControllerInfo#getConnectionHints()} have no meaning.
     * <p>
     * This method is always called on the main thread.
     *
     * @param controllerInfo information of the controller which is trying to connect
     * @return a {@link MediaSession} instance for the controller to connect to, or {@code null}
     *         to reject connection
     * @see MediaSession.Builder
     * @see #getSessions()
     */
    @Nullable
    public abstract MediaSession onGetSession(@NonNull ControllerInfo controllerInfo);

    /**
     * Adds a session to this service. This is not necessary for most media apps. See
     * <a href="#MultipleSessions">Supporting Multiple Sessions</a> for detail.
     * <p>
     * Added session will be removed automatically when it's closed, or removed when
     * {@link #removeSession} is called.
     *
     * @param session a session to be added.
     * @see #removeSession(MediaSession)
     */
    public final void addSession(@NonNull MediaSession session) {
        if (session == null) {
            throw new NullPointerException("session shouldn't be null");
        }
        if (session.isClosed()) {
            throw new IllegalArgumentException("session is already closed");
        }
        mImpl.addSession(session);
    }

    /**
     * Removes a session from this service. This is not necessary for most media apps. See
     * <a href="#MultipleSessions">Supporting Multiple Sessions</a> for detail.
     *
     * @param session a session to be removed.
     * @see #addSession(MediaSession)
     */
    public final void removeSession(@NonNull MediaSession session) {
        if (session == null) {
            throw new NullPointerException("session shouldn't be null");
        }
        mImpl.removeSession(session);
    }

    /**
     * Called when notification UI needs update. Override this method to show or cancel your own
     * notification UI.
     * <p>
     * This would be called on {@link MediaSession}'s callback executor when player state is
     * changed, or when the current media item of the session is changed.
     * <p>
     * With the notification returned here, the service becomes foreground service when the playback
     * is started. Apps targeting API {@link android.os.Build.VERSION_CODES#P} or later must request
     * the permission {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use
     * this API. It becomes background service after the playback is stopped.
     *
     * @param session a session that needs notification update
     * @return a {@link MediaNotification}. Can be {@code null}
     */
    @Nullable
    public MediaNotification onUpdateNotification(@NonNull MediaSession session) {
        if (session == null) {
            throw new NullPointerException("session shouldn't be null");
        }
        return mImpl.onUpdateNotification(session);
    }

    /**
     * Gets the list of {@link MediaSession}s that you've added to this service via
     * {@link #addSession} or {@link #onGetSession(ControllerInfo)}.
     *
     * @return sessions
     */
    @NonNull
    public final List<MediaSession> getSessions() {
        return mImpl.getSessions();
    }

    /**
     * Default implementation for {@link MediaSessionService} to handle incoming binding
     * request. If the request is for getting the session, the intent will have action
     * {@link #SERVICE_INTERFACE}.
     * <p>
     * Override this method if this service also needs to handle binder requests other than
     * {@link #SERVICE_INTERFACE}. Derived classes MUST call through to the super class's
     * implementation of this method.
     *
     * @param intent
     * @return Binder
     */
    @CallSuper
    @Override
    @Nullable
    public IBinder onBind(@NonNull Intent intent) {
        return mImpl.onBind(intent);
    }

    @CallSuper
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return mImpl.onStartCommand(intent, flags, startId);
    }

    /**
     * Called by the system to notify that it is no longer used and is being removed. Do not call
     * this method directly.
     * <p>
     * Override this method if you need your own clean up. Derived classes MUST call through
     * to the super class's implementation of this method.
     */
    @CallSuper
    @Override
    public void onDestroy() {
        super.onDestroy();
        mImpl.onDestroy();
    }

    /**
     * Returned by {@link #onUpdateNotification(MediaSession)} for making session service
     * foreground service to keep playback running in the background. It's highly recommended to
     * show media style notification here.
     */
    public static class MediaNotification {
        private final int mNotificationId;
        private final Notification mNotification;

        /**
         * Default constructor
         *
         * @param notificationId notification id to be used for
         *      {@link NotificationManager#notify(int, Notification)}.
         * @param notification a notification to make session service foreground service. Media
         *      style notification is recommended here.
         */
        public MediaNotification(int notificationId, @NonNull Notification notification) {
            if (notification == null) {
                throw new NullPointerException("notification shouldn't be null");
            }
            mNotificationId = notificationId;
            mNotification = notification;
        }

        /**
         * Gets the id of the id.
         *
         * @return the notification id
         */
        public int getNotificationId() {
            return mNotificationId;
        }

        /**
         * Gets the notification.
         *
         * @return the notification
         */
        @NonNull
        public Notification getNotification() {
            return mNotification;
        }
    }

    interface MediaSessionServiceImpl {
        void onCreate(MediaSessionService service);
        int onStartCommand(Intent intent, int flags, int startId);
        IBinder onBind(Intent intent);
        void onDestroy();
        void addSession(MediaSession session);
        void removeSession(MediaSession session);
        MediaNotification onUpdateNotification(MediaSession session);
        List<MediaSession> getSessions();
    }
}