Android媒体播放框架 MediaSessionCompat与MediaControllerCompat

MediaSessionCompat

出自:https://blog.csdn.net/Jason_Lee155/article/details/116324582

服务端

java 复制代码
package com.allen.rxhttputils.test;

import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaBrowserServiceCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class MusicService extends MediaBrowserServiceCompat {
    private MediaSessionCompat mSession;
    private PlaybackStateCompat mPlaybackState;
    private MediaPlayer mMediaPlayer;

    @Override
    public void onCreate() {
        super.onCreate();

        mPlaybackState = new PlaybackStateCompat.Builder()
                .setState(PlaybackStateCompat.STATE_NONE, 0, 1.0f)
                .build();

        mSession = new MediaSessionCompat(this, "MusicService");
        mSession.setCallback(SessionCallback);//设置回调
        mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
        mSession.setPlaybackState(mPlaybackState);

        //设置token后会触发MediaBrowserCompat.ConnectionCallback的回调方法
        //表示MediaBrowser与MediaBrowserService连接成功

        setSessionToken(mSession.getSessionToken());

        mMediaPlayer = new MediaPlayer();
        mMediaPlayer.setOnPreparedListener(PreparedListener);
        mMediaPlayer.setOnCompletionListener(CompletionListener);
    }

    /**
     * 它用于定义媒体浏览服务(Media Browser Service)的根节点(Root)相关信息,
     * 包括根媒体 ID(Media ID)和自定义的浏览权限(Custom Browsing Permissions)。
     * 这个方法主要被MediaBrowser(在客户端)调用,以确定客户端是否能够成功连接到媒体浏览服务并开始浏览媒体资源。
     */
    @Nullable
    @Override
    public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
        return new BrowserRoot(MEDIA_ID_ROOT, null);
    }

    @Override
    public void onLoadChildren(@NonNull String parentId, @NonNull final Result<List<MediaBrowserCompat.MediaItem>> result) {
        //将信息从当前线程中移除,允许后续调用sendResult方法
        result.detach();

        //我们模拟获取数据的过程,真实情况应该是异步从网络或本地读取数据
        MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
                .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, "" + R.raw.jinglebells)
                .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "圣诞歌")
                .build();
        ArrayList<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>();
        mediaItems.add(createMediaItem(metadata));

        //向Browser发送数据
        result.sendResult(mediaItems);
    }

    private MediaBrowserCompat.MediaItem createMediaItem(MediaMetadataCompat metadata) {
        return new MediaBrowserCompat.MediaItem(
                metadata.getDescription(),
                MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
        );
    }

    /**
     * 响应控制器指令的回调
     */
    private android.support.v4.media.session.MediaSessionCompat.Callback SessionCallback = new MediaSessionCompat.Callback() {
        /**
         * 响应MediaController.getTransportControls().play
         */
        @Override
        public void onPlay() {
            Log.e(TAG, "onPlay");
            if (mPlaybackState.getState() == PlaybackStateCompat.STATE_PAUSED) {
                mMediaPlayer.start();
                mPlaybackState = new PlaybackStateCompat.Builder()
                        .setState(PlaybackStateCompat.STATE_PLAYING, 0, 1.0f)
                        .build();
                mSession.setPlaybackState(mPlaybackState);
            }
        }

        /**
         * 响应MediaController.getTransportControls().onPause
         */
        @Override
        public void onPause() {
            if (mPlaybackState.getState() == PlaybackStateCompat.STATE_PLAYING) {
                mMediaPlayer.pause();
                mPlaybackState = new PlaybackStateCompat.Builder()
                        .setState(PlaybackStateCompat.STATE_PAUSED, 0, 1.0f)
                        .build();
                mSession.setPlaybackState(mPlaybackState);
            }
        }

        /**
         * 响应MediaController.getTransportControls().playFromUri
         *  这里可以根据传入的uri来加载和播放媒体
         * @param uri
         * @param extras
         */
        @Override
        public void onPlayFromUri(Uri uri, Bundle extras) {
            try {
                switch (mPlaybackState.getState()) {
                    case PlaybackStateCompat.STATE_PLAYING:
                    case PlaybackStateCompat.STATE_PAUSED:
                    case PlaybackStateCompat.STATE_NONE:
                        mMediaPlayer.reset();
                        mMediaPlayer.setDataSource(MusicService.this, uri);
                        mMediaPlayer.prepare();//准备同步
                        mPlaybackState = new PlaybackStateCompat.Builder()
                                .setState(PlaybackStateCompat.STATE_CONNECTING, 0, 1.0f)
                                .build();
                        mSession.setPlaybackState(mPlaybackState);
                        //我们可以保存当前播放音乐的信息,以便客户端刷新UI
                        mSession.setMetadata(new MediaMetadataCompat.Builder()
                                .putString(MediaMetadataCompat.METADATA_KEY_TITLE, extras.getString("title"))
                                .build()
                        );
                        break;
                    case PlaybackStateCompat.STATE_BUFFERING:
                        break;
                    case PlaybackStateCompat.STATE_CONNECTING:
                        break;
                    case PlaybackStateCompat.STATE_ERROR:
                        break;
                    case PlaybackStateCompat.STATE_FAST_FORWARDING:
                        break;
                    case PlaybackStateCompat.STATE_REWINDING:
                        break;
                    case PlaybackStateCompat.STATE_SKIPPING_TO_NEXT:
                        break;
                    case PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS:
                        break;
                    case PlaybackStateCompat.STATE_SKIPPING_TO_QUEUE_ITEM:
                        break;
                    case PlaybackStateCompat.STATE_STOPPED:
                        break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onPlayFromSearch(String query, Bundle extras) {
        }
    };

    /**
     * 监听MediaPlayer.prepare()
     */
    private MediaPlayer.OnPreparedListener PreparedListener = new MediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(MediaPlayer mediaPlayer) {
            mMediaPlayer.start();
            mPlaybackState = new PlaybackStateCompat.Builder()
                    .setState(PlaybackStateCompat.STATE_PLAYING, 0, 1.0f)
                    .build();
            mSession.setPlaybackState(mPlaybackState);
        }
    };

    /**
     * 监听播放结束的事件
     */
    private MediaPlayer.OnCompletionListener CompletionListener = new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mediaPlayer) {
            mPlaybackState = new PlaybackStateCompat.Builder()
                    .setState(PlaybackStateCompat.STATE_NONE, 0, 1.0f)
                    .build();
            mSession.setPlaybackState(mPlaybackState);
            mMediaPlayer.reset();
        }

    };


}

客户端

java 复制代码
package com.allen.rxhttputils.test;

import android.content.ComponentName;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.allen.rxhttputils.R;

import java.util.List;

public class DemoActivity extends AppCompatActivity {
    private static final String TAG = "DemoActivity";
    private MediaBrowserCompat mBrowser;
    private MediaControllerCompat mController;
    private Button btnPlay;
    private TextView textTitle;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBrowser = new MediaBrowserCompat(
                this,
                new ComponentName(this, MusicService.class),//绑定服务端
                browserConnectionCallback,//设置连接回调
                null
        );
        btnPlay = (Button) findViewById(R.id.btn_play);
        textTitle = (TextView) findViewById(R.id.text_title);
    }

    public void clickEvent(View view) {
        switch (view.getId()) {
            case R.id.btn_play:
                if (mController != null) {
                    handlerPlayEvent();
                }
                break;
        }
    }

    /**
     * 处理播放按钮事件
     */
    private void handlerPlayEvent() {
        switch (mController.getPlaybackState().getState()) {
            case PlaybackStateCompat.STATE_PLAYING:
                mController.getTransportControls().pause();
                break;
            case PlaybackStateCompat.STATE_PAUSED:
                mController.getTransportControls().play();
                break;
            default:
                mController.getTransportControls().playFromSearch("", null);
                break;
        }
    }


    /**
     * 媒体控制器控制播放过程中的回调接口,可以用来根据播放状态更新UI
     */
    private final MediaControllerCompat.Callback ControllerCallback =
            new MediaControllerCompat.Callback() {
                /***
                 * 音乐播放状态改变的回调
                 * @param state
                 */
                @Override
                public void onPlaybackStateChanged(PlaybackStateCompat state) {
                    switch (state.getState()) {
                        case PlaybackStateCompat.STATE_NONE://无任何状态
                            textTitle.setText("");
                            btnPlay.setText("开始");
                            break;
                        case PlaybackStateCompat.STATE_PAUSED:
                            btnPlay.setText("开始");
                            break;
                        case PlaybackStateCompat.STATE_PLAYING:
                            btnPlay.setText("暂停");
                            break;
                        case PlaybackStateCompat.STATE_BUFFERING:
                            break;
                        case PlaybackStateCompat.STATE_CONNECTING:
                            break;
                        case PlaybackStateCompat.STATE_ERROR:
                            break;
                        case PlaybackStateCompat.STATE_FAST_FORWARDING:
                            break;
                        case PlaybackStateCompat.STATE_REWINDING:
                            break;
                        case PlaybackStateCompat.STATE_SKIPPING_TO_NEXT:
                            break;
                        case PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS:
                            break;
                        case PlaybackStateCompat.STATE_SKIPPING_TO_QUEUE_ITEM:
                            break;
                        case PlaybackStateCompat.STATE_STOPPED:
                            break;
                    }
                }

                /**
                 * 播放音乐改变的回调
                 * @param metadata
                 */
                @Override
                public void onMetadataChanged(MediaMetadataCompat metadata) {
                    textTitle.setText(metadata.getDescription().getTitle());
                }
            };

    private Uri rawToUri(int id) {
        String uriStr = "android.resource://" + getPackageName() + "/" + id;
        return Uri.parse(uriStr);
    }

    @Override
    protected void onStart() {
        super.onStart();
        //Browser发送连接请求
        mBrowser.connect();
    }

    @Override
    protected void onStop() {
        super.onStop();
        mBrowser.disconnect();
    }

    /**
     * 连接状态的回调接口,连接成功时会调用onConnected()方法
     */
    private MediaBrowserCompat.ConnectionCallback browserConnectionCallback =
            new MediaBrowserCompat.ConnectionCallback() {
                @Override
                public void onConnected() {
                    Log.e(TAG, "onConnected------");
                    //必须在确保连接成功的前提下执行订阅的操作
                    if (mBrowser.isConnected()) {
                        //mediaId即为MediaBrowserService.onGetRoot的返回值
                        //若Service允许客户端连接,则返回结果不为null,其值为数据内容层次结构的根ID
                        //若拒绝连接,则返回null
                        String mediaId = mBrowser.getRoot();

                        //Browser通过订阅的方式向Service请求数据,发起订阅请求需要两个参数,其一为mediaId
                        //而如果该mediaId已经被其他Browser实例订阅,则需要在订阅之前取消mediaId的订阅者
                        //虽然订阅一个 已被订阅的mediaId 时会取代原Browser的订阅回调,但却无法触发onChildrenLoaded回调

                        //ps:虽然基本的概念是这样的,但是Google在官方demo中有这么一段注释...
                        // This is temporary: A bug is being fixed that will make subscribe
                        // consistently call onChildrenLoaded initially, no matter if it is replacing an existing
                        // subscriber or not. Currently this only happens if the mediaID has no previous
                        // subscriber or if the media content changes on the service side, so we need to
                        // unsubscribe first.
                        //大概的意思就是现在这里还有BUG,即只要发送订阅请求就会触发onChildrenLoaded回调
                        //所以无论怎样我们发起订阅请求之前都需要先取消订阅
                        mBrowser.unsubscribe(mediaId);
                        //之前说到订阅的方法还需要一个参数,即设置订阅回调SubscriptionCallback
                        //当Service获取数据后会将数据发送回来,此时会触发SubscriptionCallback.onChildrenLoaded回调
                        mBrowser.subscribe(mediaId, browserSubscriptionCallback);
                    }
                    if (mBrowser.isConnected()) {
                        try {
                            mController = new MediaControllerCompat(DemoActivity.this, mBrowser.getSessionToken());
                            //注册回调
                            mController.registerCallback(ControllerCallback);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                }

                @Override
                public void onConnectionFailed() {
                    Log.e(TAG, "连接失败!");
                }
            };
    /**
     * 向媒体服务器(MediaBrowserService)发起数据订阅请求的回调接口
     */
    private final MediaBrowserCompat.SubscriptionCallback browserSubscriptionCallback =
            new MediaBrowserCompat.SubscriptionCallback() {
                @Override
                public void onChildrenLoaded(@NonNull String parentId,
                                             @NonNull List<MediaBrowserCompat.MediaItem> children) {
                    Log.e(TAG, "onChildrenLoaded------");
                    //children 即为Service发送回来的媒体数据集合
//                    for (MediaBrowserCompat.MediaItem item:children){
//                        Log.e(TAG,item.getDescription().getTitle().toString());
//                        list.add(item);
//                    }
                    //在onChildrenLoaded可以执行刷新列表UI的操作
//                    demoAdapter.notifyDataSetChanged();
                }
            };
}

接口

MediaBrowserServiceCompat (服务端)

  1. 媒体浏览服务提供

    • 作为服务端核心组件MediaBrowserServiceCompat是一个兼容类,主要用于创建媒体浏览服务。在媒体播放应用的架构中,它扮演着服务端的角色,为客户端提供媒体资源的浏览服务。例如,在一个音乐播放应用中,它能够让客户端获取音乐库中的歌曲列表、专辑列表等信息。
    • 与媒体会话协同工作 :它通常和MediaSessionCompat紧密配合。MediaSessionCompat用于管理媒体播放的状态(如播放、暂停、停止等)和元数据(如歌曲标题、艺术家姓名等),而MediaBrowserServiceCompat则侧重于提供媒体资源的组织结构和浏览功能,两者共同构建了完整的媒体播放服务体系。
  2. 支持跨进程通信

    • 进程隔离与安全通信 :在Android系统中,为了安全和系统资源管理的考虑,不同的组件(如服务和活动)可能运行在不同的进程中。MediaBrowserServiceCompat允许跨进程通信,使得客户端(如一个Activity)能够安全地与运行在独立进程中的媒体服务进行交互。例如,媒体服务可以在后台进程中持续运行,管理媒体资源和播放状态,而客户端可以在前台进程中与用户交互,通过MediaBrowserServiceCompat提供的接口来获取媒体资源信息和控制播放。
  3. 资源管理与权限控制

    • 媒体资源的加载与管理MediaBrowserServiceCompat负责管理媒体资源的加载和提供。例如,它可以从本地存储、网络或者其他数据源获取音乐、视频等媒体文件的相关信息,并将这些信息以合适的方式组织起来,以便客户端进行浏览。在onLoadChildren方法中,可以实现加载媒体资源子项的逻辑,比如加载某个专辑下的所有歌曲。
    • 权限控制机制 :通过onGetRoot方法,它能够实现权限控制。可以根据客户端的不同(如不同的应用包名或者用户权限级别)来返回不同的根媒体ID和权限设置,从而决定客户端是否能够浏览媒体资源以及能够浏览哪些部分的资源。例如,对于系统自带的音乐播放器应用,可能赋予完全的媒体资源浏览权限;而对于第三方应用,可能只允许浏览部分公开的媒体资源。
  4. 标准化媒体服务接口

    • 遵循系统标准MediaBrowserServiceCompat遵循Android系统的媒体服务标准接口。这使得开发的媒体服务能够更好地与系统的其他组件(如系统的媒体控制中心、通知栏媒体控制等)集成。当用户通过系统的媒体控制按钮(如耳机线控或者通知栏的播放/暂停按钮)操作媒体播放时,系统能够正确地将这些操作转发到符合标准接口的MediaBrowserServiceCompat服务中进行处理。
    • 方便第三方开发与集成 :对于第三方开发者来说,使用MediaBrowserServiceCompat可以方便地开发出符合系统规范的媒体服务。其他应用可以通过标准的MediaBrowser接口来与这个媒体服务进行交互,从而实现媒体资源共享和播放控制。例如,一个第三方音乐服务可以通过这个方式让其他应用(如智能家居控制应用)能够浏览和控制音乐播放。
  5. onGetRoot方法

    • 作用

      • 这个方法用于定义媒体浏览服务的根节点信息,包括根媒体ID和自定义的浏览权限。它是客户端与媒体浏览服务建立连接时首先调用的方法,决定了客户端是否能够成功连接并开始浏览媒体资源。
    • 参数含义

      • clientPackageName:这是请求连接的客户端应用的包名。通过这个参数,服务端可以识别不同的客户端应用。例如,你可以根据包名来为特定的应用提供特殊的权限或根媒体ID。
      • clientUid:表示请求连接的客户端应用的用户ID。在多用户系统或者一些需要精细权限控制的场景下,这个参数很有用。服务端可以根据用户ID来判断是否允许该用户访问媒体资源,或者提供不同级别的访问权限。
      • rootHints :这是一个Bundle类型的参数,包含了一些关于根节点的提示信息。不过在实际应用中,这个参数可能经常为null。它可以用于传递一些额外的信息,比如系统或者其他组件对根节点的一些特殊要求或建议。
    • 返回值

      • 方法需要返回一个BrowserRoot对象。BrowserRoot包含两个重要部分:
        • 根媒体ID(mediaId):这是一个字符串,用于唯一标识媒体浏览服务的根节点。客户端会使用这个ID来开始浏览媒体资源。例如,在一个音频书籍应用中,根媒体ID可能是"audiobook_library",代表整个音频书籍库。
        • 自定义的浏览权限(extras) :这是一个Bundle对象,用于传递自定义的权限信息。例如,你可以在这个Bundle中设置一个布尔值来表示是否允许客户端浏览所有媒体资源(如"allow_full_browse":true),或者设置一个整数来表示客户端的浏览级别(如"browse_level":1代表基础浏览,2代表高级浏览等)。如果不需要自定义权限,这个参数可以设置为null
    • 示例代码

      • 以下是一个简单的onGetRoot方法实现,用于一个简单的视频播放服务:
      kotlin 复制代码
      override fun onGetRoot(clientPackageName: String, clientUid: Int, rootHints: Bundle?): BrowserRoot? {
          // 假设所有客户端都可以访问根媒体资源,返回一个固定的根媒体ID和null(表示没有自定义权限)
          return BrowserRoot("video_library_root", null)
      }
      • 更复杂的示例可能会根据clientPackageName或者clientUid来返回不同的根媒体ID或者权限设置,如下所示:
      kotlin 复制代码
      override fun onGetRoot(clientPackageName: String, clientUid: Int, rootHints: Bundle?): BrowserRoot? {
          if (clientPackageName == "com.example.premium_app") {
              // 对于高级应用,返回一个具有高级权限的根媒体ID和相关权限设置
              val extras = Bundle()
              extras.putBoolean("allow_full_browse", true)
              extras.putBoolean("allow_download", true)
              return BrowserRoot("premium_video_library_root", extras)
          } else {
              // 对于其他应用,返回一个普通的根媒体ID和没有特殊权限的设置
              return BrowserRoot("video_library_root", null)
          }
      }
  6. onLoadChildren方法

    • 作用

      • 这个方法用于加载和返回给定媒体ID(通常是父媒体ID)下的子媒体资源列表。当客户端想要浏览媒体资源的子项时,就会调用这个方法。例如,在音乐播放应用中,如果客户端已经获取了专辑的媒体ID,调用onLoadChildren方法可以获取该专辑下的所有歌曲列表。
    • 参数含义

      • parentId :这是一个字符串,表示父媒体资源的ID。客户端通过这个ID来请求获取其下的子媒体资源。例如,在一个包含音乐、视频和图片的多媒体应用中,当parentId是"music_library"时,可能需要返回音乐库中的所有专辑或歌曲列表;当parentId是一个专辑的ID时,就需要返回该专辑下的歌曲。
      • result :这是一个Result<List<MediaBrowser.MediaItem>>类型的参数,用于将加载的子媒体资源列表返回给客户端。通过调用result.sendResult方法,可以将获取到的媒体资源列表发送回客户端。
    • 示例代码

      • 以下是一个简单的在音乐播放服务中onLoadChildren方法的示例,用于返回给定专辑ID下的歌曲列表:
      kotlin 复制代码
      override fun onLoadChildren(parentId: String, result: Result<List<MediaBrowser.MediaItem>>) {
          if (parentId == "album_123") {
              // 假设已经有一个方法getSongsByAlbumId可以获取专辑下的歌曲列表
              val songList = getSongsByAlbumId(parentId)
              val mediaItems = mutableListOf<MediaBrowser.MediaItem>()
              for (song in songList) {
                  val mediaItem = MediaBrowser.MediaItem(
                      MediaDescriptionCompat.Builder()
                         .setTitle(song.title)
                         .setMediaId(song.mediaId)
                         .build(),
                      MediaBrowser.MediaItem.FLAG_PLAYABLE
                  )
                  mediaItems.add(mediaItem)
              }
              result.sendResult(mediaItems)
          } else {
              // 如果parentId不匹配,返回一个空列表
              result.sendResult(emptyList())
          }
      }
      • 在这个示例中,首先检查parentId是否是指定的专辑ID("album_123")。如果是,通过getSongsByAlbumId方法获取歌曲列表,然后将每首歌曲转换为MediaBrowser.MediaItem对象,并添加到mediaItems列表中。最后,通过result.sendResult将这个列表发送回客户端。如果parentId不匹配,直接返回一个空列表。

MediaSessionCompat

  1. 媒体播放状态管理

    • 统一状态控制MediaSessionCompat在媒体应用中充当了一个集中管理媒体播放状态的角色。它能够表示媒体是处于播放、暂停、停止、缓冲等多种状态。例如,在一个音乐播放器应用中,当用户点击播放按钮,应用可以通过MediaSessionCompat将播放状态更新为"正在播放",系统的其他组件(如通知栏媒体控制)就能获取到这个状态变化并做出相应的显示。
    • 状态同步与通知 :它可以跨组件和跨进程同步媒体播放状态。比如,媒体播放服务(运行在后台进程)和用户界面(运行在前台进程)可以通过MediaSessionCompat来保持播放状态的同步。当服务端的播放状态改变时,如从暂停变为播放,能够及时通知到所有与该媒体会话相关的组件,使得用户界面可以相应地更新播放/暂停按钮的显示状态。
  2. 媒体元数据传递

    • 元数据存储与共享MediaSessionCompat用于存储和传递媒体元数据,这些元数据包括歌曲标题、艺术家姓名、专辑封面、媒体时长等信息。以音频播放应用为例,当一首歌曲开始播放时,应用可以将歌曲的标题、歌手名字等元数据通过MediaSessionCompat设置好,这样系统的媒体中心或者其他连接的设备(如智能手表显示音乐信息)就可以获取这些元数据并展示给用户。
    • 支持多种媒体类型 :无论是音频、视频还是其他类型的媒体内容,MediaSessionCompat都能有效地管理和传递它们的元数据。对于视频,它可以传递视频标题、导演、演员等信息;对于音频书籍,它可以传递书籍名称、朗读者等元数据。
  3. 接收媒体按键事件

    • 集成系统媒体按键MediaSessionCompat能够接收来自系统或者外部设备的媒体按键事件。这使得应用可以很好地集成系统的媒体控制功能,如耳机线控、蓝牙设备的媒体按键或者系统通知栏中的媒体播放控制按钮。例如,当用户按下耳机上的播放/暂停按钮时,系统会将这个按键事件发送到应用的MediaSessionCompat,应用可以根据这个事件来执行相应的播放或暂停操作。
    • 自定义按键处理逻辑 :除了系统标准的媒体按键事件,开发者还可以自定义按键处理逻辑。通过设置MediaSessionCompat.Callback,可以定义在接收到各种按键事件(如播放、暂停、停止、上一首、下一首等)时应用要执行的操作,从而提供个性化的媒体播放控制体验。
  4. 跨设备和跨应用协作

    • 跨设备媒体控制 :在多设备环境下,MediaSessionCompat有助于实现跨设备的媒体播放控制。例如,用户在手机上开始播放音乐,通过与其他设备(如智能音箱)的连接和同步,智能音箱可以获取手机上媒体播放的状态和元数据,从而实现无缝的跨设备播放体验。
    • 与其他应用协作 :它也方便了不同应用之间的协作。比如,一个运动追踪应用可以和一个音乐播放应用协作,当运动追踪应用检测到用户开始跑步时,它可以通过MediaSessionCompat发送指令到音乐播放应用,让音乐播放应用开始播放适合跑步的音乐列表。
  5. setPlaybackState方法

    • 用途

      • 用于设置媒体播放状态。通过传入一个PlaybackStateCompat对象,来告知系统和其他组件媒体当前的播放状态,包括播放、暂停、停止等多种状态,以及播放位置、播放速度等详细信息。
    • 参数

      • statePlaybackStateCompat类型的参数。这个对象可以通过PlaybackStateCompat.Builder构建。例如,PlaybackStateCompat.Builder().setState(PlaybackStateCompat.STATE_PLAYING, 0, 1.0f).build()就创建了一个表示正在播放状态的对象,其中第一个参数是状态码(STATE_PLAYING表示播放状态),第二个参数是播放位置(这里是0,表示刚开始播放),第三个参数是播放速度(1.0f表示正常速度)。
    • 示例代码

      • 以下是在一个音乐播放服务中,当开始播放音乐时设置播放状态为播放中的示例:
      kotlin 复制代码
      val playbackState = PlaybackStateCompat.Builder()
       .setState(PlaybackStateCompat.STATE_PLAYING, 0, 1.0f)
       .build()
      mediaSession.setPlaybackState(playbackState)
  6. setMetadata方法

    • 用途

      • 用于设置媒体元数据。这些元数据包含了媒体的各种信息,如歌曲标题、艺术家姓名、专辑封面、媒体时长等,使得系统和其他组件能够获取并展示这些信息。
    • 参数

      • metadataMediaMetadataCompat类型的参数。可以通过MediaMetadataCompat.Builder来构建这个对象。例如,MediaMetadataCompat.Builder().putString(MediaMetadataCompat.METADATA_KEY_TITLE, "歌曲标题").putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "艺术家名字").build()就创建了一个包含歌曲标题和艺术家姓名的元数据对象。
    • 示例代码

      • 以下是在音乐播放服务中,当播放一首歌曲时设置歌曲元数据的示例:
      kotlin 复制代码
      val metadata = MediaMetadataCompat.Builder()
       .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "My Song")
       .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "My Artist")
       .build()
      mediaSession.setMetadata(metadata)
  7. setCallback方法

    • 用途

      • 用于设置媒体会话的回调。通过传入一个MediaSessionCompat.Callback对象,来处理各种媒体相关事件,如媒体按键事件、播放状态变化请求、元数据变化请求等。
    • 参数

      • callbackMediaSessionCompat.Callback类型的参数。开发者需要自定义一个继承自MediaSessionCompat.Callback的类,并实现其中的方法,如onMediaButtonEvent用于处理媒体按键事件,onPlayonPauseonStop等用于处理播放状态变化的请求。
    • 示例代码

      • 以下是设置媒体会话回调的示例:
      kotlin 复制代码
      mediaSession.setCallback(object : MediaSessionCompat.Callback() {
          override fun onMediaButtonEvent(mediaButtonIntent: Intent): Boolean {
              val event = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT) as? KeyEvent
              if (event!= null) {
                  if (event.action == KeyEvent.ACTION_DOWN) {
                      val keyCode = event.keyCode
                      when (keyCode) {
                          KeyEvent.KEYCODE_MEDIA_PLAY -> startPlayback()
                          KeyEvent.KEYCODE_MEDIA_PAUSE -> pausePlayback()
                          KeyEvent.KEYCODE_MEDIA_STOP -> stopPlayback()
                      }
                  }
              }
              return super.onMediaButtonEvent(mediaButtonIntent)
          }
      })
  8. getController方法

    • 用途

      • 用于获取与该媒体会话相关联的MediaControllerCompat对象。这个对象可以用于在其他组件(如Activity)中控制媒体播放,如播放、暂停、停止等操作,以及获取媒体播放状态和元数据。
    • 参数

      • 该方法没有参数。
    • 示例代码

      • 以下是在一个服务中获取媒体控制器,然后将其传递给某个组件的示例(假设context是有效的上下文):
      kotlin 复制代码
      val mediaController = mediaSession.getController(context)
      someComponent.setMediaController(mediaController)
  9. setActive方法

    • 用途

      • 用于设置媒体会话的活动状态。当媒体会话处于活动状态时,它能够接收和处理媒体按键事件、状态更新请求、元数据更新请求等。当处于非活动状态时,这些功能会受到限制。
    • 参数

      • active :一个布尔值参数。true表示将媒体会话设置为活动状态,false表示设置为非活动状态。
    • 示例代码

      • 以下是在音乐播放服务启动时,将媒体会话设置为活动状态的示例:
      kotlin 复制代码
      mediaSession.setActive(true)

MediaBrowserCompat(客户端)

  1. 用途
    • 媒体资源浏览客户端接口MediaBrowserCompat是在客户端用于浏览媒体资源的工具。它允许客户端应用与MediaBrowserServiceCompat(运行在服务端)进行交互,以获取媒体资源的组织结构信息,如获取音乐库中的专辑列表、视频应用中的频道列表等。
    • 连接媒体服务与资源获取 :其主要目的是建立与媒体浏览服务的连接,并提供方法来加载和探索媒体资源。例如,在一个多媒体应用中,通过MediaBrowserCompat可以连接到媒体服务,获取媒体资源的根目录信息,然后进一步获取子目录或具体媒体文件的信息。
    • 与媒体控制器协作 :它与MediaControllerCompat紧密配合。MediaBrowserCompat负责获取媒体资源相关信息,而MediaControllerCompat主要用于控制媒体播放(如播放、暂停等操作)。在连接建立后,MediaBrowserCompat能够帮助获取用于创建MediaControllerCompat的令牌,从而实现完整的媒体浏览和播放控制功能。
  2. 方法
    • 构造函数

      • 用途 :用于创建MediaBrowserCompat实例,建立与媒体浏览服务的连接。

      • 参数和示例

        • MediaBrowserCompat(Context context, ComponentName serviceComponent, MediaBrowserCompat.ConnectionCallback callback, Bundle rootHints)是主要的构造函数。
        • context :应用的上下文,用于获取系统服务等操作,如this(在Activity或其他组件内部)。
        • serviceComponent :一个ComponentName对象,用于指定媒体浏览服务的组件名称。例如,ComponentName(this, MusicService::class.java),其中this是上下文,MusicService::class.java是媒体浏览服务的类。
        • callback :一个MediaBrowserCompat.ConnectionCallback对象,用于接收连接相关的回调事件,如连接成功、连接暂停、连接失败等。
        • rootHints :一个Bundle对象,包含了关于根媒体资源的提示信息,可用于辅助服务端确定根节点的内容和权限,通常可以为null
        • 示例:
        kotlin 复制代码
        val mediaBrowser = MediaBrowserCompat(
            this,
            ComponentName(this, MusicService::class.java),
            mediaBrowserConnectionCallback,
            null
        )
    • connect方法

      • 用途 :用于发起与媒体浏览服务的连接。当MediaBrowserCompat实例创建后,通过这个方法开始建立连接,使得客户端可以开始与服务端交互以获取媒体资源信息。

      • 示例

        kotlin 复制代码
        mediaBrowser.connect()
    • disconnect方法

      • 用途:用于断开与媒体浏览服务的连接。在客户端不再需要浏览媒体资源或者应用即将退出等场景下,应该调用这个方法来释放资源,避免不必要的资源占用和连接开销。

      • 示例

        kotlin 复制代码
        mediaBrowser.disconnect()
    • getRoot方法

      • 用途 :用于获取媒体浏览服务的根媒体ID和权限信息。这个方法返回一个Task对象,通过这个Task可以异步获取BrowserRoot信息。在连接成功后,可以使用这个方法来确定媒体资源浏览的起点和权限范围。

      • 示例

        kotlin 复制代码
        mediaBrowser.getRoot().addOnSuccessListener { root ->
            val mediaId = root.mediaId
            val extras = root.extras
        }
    • subscribe方法

      • 用途 :用于订阅媒体资源的更新。当客户端想要获取特定媒体ID下的子媒体资源更新(如新增、删除、修改等)时,可以使用这个方法。它接收一个媒体ID和一个MediaBrowserCompat.SubscriptionCallback对象,用于接收订阅相关的回调。

      • 示例

        kotlin 复制代码
        mediaBrowser.subscribe("music_album_1", object : MediaBrowserCompat.SubscriptionCallback() {
            override fun onChildrenLoaded(parentId: String, children: MutableList<MediaBrowserCompat.MediaItem>) {
                // 处理加载的子媒体资源
            }
        })
    • unsubscribe方法

      • 用途:用于取消对特定媒体ID下媒体资源更新的订阅。当客户端不再需要关注某个媒体资源的更新时,可以使用这个方法来取消订阅,减少不必要的资源消耗和回调处理。

      • 示例

        kotlin 复制代码
        mediaBrowser.unsubscribe("music_album_1")

MediaControllerCompat

  1. 用途
    • 媒体播放控制中心接口MediaControllerCompat在媒体应用中充当了一个控制中心的角色。它主要用于控制媒体的播放行为,如播放、暂停、停止、跳过到下一首或上一首等操作。无论是音频还是视频媒体,都可以通过这个接口来进行播放控制。
    • 状态和元数据获取与同步 :除了控制播放,它还用于获取媒体播放的状态(如当前是播放、暂停还是停止状态)以及媒体的元数据(如歌曲标题、艺术家名字、视频标题等)。这使得客户端(如用户界面组件)能够与媒体播放服务保持状态和信息的同步。例如,当媒体播放状态在服务端发生改变(从暂停变为播放),客户端可以通过MediaControllerCompat及时获取到这个变化并更新界面显示。
    • 跨组件和跨进程通信 :在复杂的媒体应用架构中,可能存在多个组件或者服务运行在不同的进程中。MediaControllerCompat能够跨越这些组件和进程边界,实现对媒体播放的统一控制和状态共享。例如,一个在后台运行的媒体播放服务和一个在前台显示的用户界面Activity可以通过MediaControllerCompat进行通信,确保媒体播放的连贯性和状态的一致性。
  2. 方法
    • 构造函数

      • 用途 :用于创建MediaControllerCompat实例,以便与媒体会话建立连接并进行后续的播放控制和状态获取操作。

      • 参数和示例

        • MediaControllerCompat(Context context, MediaSessionCompat.Token token)是主要的构造函数。
        • context :应用的上下文,用于获取系统服务等操作,通常是当前Activity或者Service的上下文,如this(在Activity或其他组件内部)。
        • token :一个MediaSessionCompat.Token对象,用于标识与哪个媒体会话进行关联。这个令牌通常是从MediaBrowserCompat连接到媒体服务后获取的,它建立了MediaControllerCompat与媒体会话之间的联系,使得控制操作能够正确地发送到对应的媒体会话。
        • 示例:
        kotlin 复制代码
        val mediaController = MediaControllerCompat(this, mediaBrowser.sessionToken)
    • registerCallback方法

      • 用途 :用于注册一个回调对象,以便接收媒体播放状态和元数据变化的通知。通过实现MediaControllerCompat.Callback接口并注册回调,客户端可以在媒体播放状态改变(如开始播放、暂停播放)或者媒体元数据更新(如歌曲切换后标题、艺术家等信息改变)时执行相应的操作,如更新界面显示。

      • 参数和示例

        • callback :一个MediaControllerCompat.Callback对象,需要开发者自定义一个继承自该类的子类,并实现其中的方法,如onPlaybackStateChanged用于处理播放状态变化,onMetadataChanged用于处理元数据变化。
        • 示例:
        kotlin 复制代码
        mediaController.registerCallback(object : MediaControllerCompat.Callback() {
            override fun onPlaybackStateChanged(state: PlaybackStateCompat?) {
                // 根据播放状态变化更新界面显示
            }
            override fun onMetadataChanged(metadata: MediaMetadataCompat?) {
                // 根据元数据变化更新界面显示
            }
        })
    • unregisterCallback方法

      • 用途 :与registerCallback相反,用于取消之前注册的媒体控制器回调。当客户端不再需要接收媒体播放状态和元数据变化的通知时,比如用户离开相关媒体播放界面或者媒体播放结束后,可以调用这个方法来释放资源并停止接收回调。

      • 示例

        kotlin 复制代码
        mediaController.unregisterCallback(mediaControllerCallback)
    • getMetadata方法

      • 用途 :用于获取当前媒体的元数据。返回一个MediaMetadataCompat对象,其中包含了媒体的各种信息,如标题、艺术家、时长、封面等。客户端可以使用这些信息来展示媒体的详细内容,例如在音乐播放器中显示歌曲标题和艺术家名字。

      • 示例

        kotlin 复制代码
        val metadata = mediaController.getMetadata()
        val title = metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE)
        val artist = metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST)
    • getPlaybackState方法

      • 用途 :用于获取当前媒体的播放状态。返回一个PlaybackStateCompat对象,通过这个对象可以获取媒体是处于播放、暂停、停止等哪种状态,还能获取播放位置、播放速度等详细信息。客户端可以根据播放状态来更新界面上的播放控制按钮显示(如播放时隐藏播放按钮,显示暂停按钮)。

      • 示例

        kotlin 复制代码
        val playbackState = mediaController.getPlaybackState()
        val state = playbackState.state
        when (state) {
            PlaybackStateCompat.STATE_PLAYING -> {
                // 处理播放状态下的界面更新
            }
            PlaybackStateCompat.STATE_PAUSED -> {
                // 处理暂停状态下的界面更新
            }
        }
    • transportControls方法

      • 用途 :用于获取媒体传输控制对象TransportControls。通过这个对象可以执行各种媒体播放控制操作,如play(播放)、pause(暂停)、stop(停止)、skipToNext(跳到下一首)、skipToPrevious(跳到上一首)等。这些操作会将指令发送到对应的媒体会话,从而控制媒体播放。

      • 示例

        kotlin 复制代码
        val transportControls = mediaController.transportControls
        transportControls.play()

更多

https://www.apiref.com/android-zh/android/support/v4/media/session/MediaSessionCompat.html

https://www.apiref.com/android-zh/android/support/v4/media/session/MediaControllerCompat.html

参考地址

豆包AI

相关推荐
LuiChun2 小时前
webview_flutter_android 4.3.0使用
android·flutter
Tanecious.2 小时前
C语言--分支循环实践:猜数字游戏
android·c语言·游戏
闲暇部落4 小时前
kotlin内联函数——takeIf和takeUnless
android·kotlin
Android西红柿13 小时前
flutter-android混合编译,原生接入
android·flutter
大叔编程奋斗记14 小时前
【Salesforce】审批流程,代理登录 tips
android
程序员江同学16 小时前
Kotlin 技术月报 | 2025 年 1 月
android·kotlin
爱踢球的程序员-116 小时前
Android:View的滑动
android·kotlin·android studio
大耳猫17 小时前
Android HandlerThread
android·thread·handler
新玉540117 小时前
PHP反序列化练习
android·开发语言·前端·php