【车载Android】多媒体开发入门(上) - MediaSession

随着车载 Android 生态的成熟,多媒体已从独立的 App 演变为一项全局协同的服务,需实时联动桌面卡片、控制中心及语音助手,以实现无缝流转与智能交互。

在这套复杂的交互体系下,MediaSession 是 Android 媒体框架中连接播放端与控制端的核心组件。它是确保跨进程状态同步、响应全局指令的基石。本文将结合具体开发实践,探讨 MediaSession 在车载开发中的核心应用。

一、为什么必须用 MediaSession?

你可能有这样一个问题,为什么多媒体开发建议使用MediaSession?很简单,如果不使用MediaSession,你会失去:

  • 锁屏播放控件
  • 通知栏媒体通知(新版强制要求 MediaStyle + MediaSession)
  • 耳机/蓝牙媒体键控制(播放、暂停、切歌)
  • Android Auto / Car App 支持
  • Wear OS 手表控制
  • Google Assistant 语音控制(如"嘿 Google,播放下一首")
  • 系统音量键独立控制媒体音量
  • PiP(画中画)视频控件联动

一句话,如果你做音视频播放,不用 MediaSession 就等于放弃了 80% 的系统集成能力。

本文基于 Media3 框架编写,使用的类库如下:

ini 复制代码
media3 = "1.8.0"

media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" }

media3-ui = { group = "androidx.media3", name = "media3-ui", version.ref = "media3" }

media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3" }

二、核心组件一览

服务端核心组件

MediaSessionService

Media3 新增的 Service 基类,是整个音频后台运行的载体。它管理着 MediaSession 的生命周期。当系统(如蓝牙、通知栏)需要访问媒体信息时,它负责按需启动服务。

通过重写 onGetSession(controllerInfo),可以精细控制哪些客户端(如 Auto、WearOS)有权连接你的会话。

MediaSession

媒体会话服务端的核心类,它是 Player 与外界沟通的桥梁。它将 Player 的状态(播放中、进度、音量)广播给系统,并接收来自外部的 SessionCommand(如锁屏点击)。

MediaSession 是新项目首选。对于老项目,Media3 内部通过适配器自动处理了与 MediaSessionCompat 的兼容,开发者无需再手动处理繁琐的兼容逻辑。

Player

Player是实际执行音视频解码、缓冲与渲染的核心组件。

MediaSession并不限制 Player 的具体实现,只要实现了 androidx.media3.common.Player 接口即可,这使得切换不同播放引擎变得极其简单。

MediaItem

播放队列中的原子单元,不仅仅是数据的载体。通过内部的 MediaMetadata,它直接决定了锁屏界面的 UI 展示(标题、歌手、封面 URI)。

此外,它还承载了 DRM 加密配置、媒体流格式声明(MimeType)以及剪辑播放区域(Clipping)等逻辑。


客户端核心组件

SessionToken

MediaSession 的"身份识别令牌"。系统利用它在不同进程间定位你的 MediaSessionService

MediaController

播放控制的控制器。通过 SessionToken 异步连接到服务端。一旦连接成功,它会镜像服务端 Player 的所有状态。

不仅是 App 内部的播放页 UI,系统层面的通知栏、锁屏控件、蓝牙车载协议、甚至 Google Assistant 的底层实现都是一个 MediaController


三、入门级音视频播放器

服务端

实现 MediaSessionService 服务端代码通常分为四个核心步骤:搭建后台载体 -> 初始化播放引擎 -> 建立会话桥梁 -> 注册服务。

1. 创建 Service 并继承 MediaSessionService

这是服务端的基础,需要重写 onGetSession 方法,可以决定哪些客户端连接到你的会话。

kotlin 复制代码
class PlaybackService : MediaSessionService() {
    private lateinit var mediaSession: MediaSession
    
    // 当有控制器(如通知栏、锁屏或你的 Activity)尝试连接时调用
    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {
        return mediaSession
    }
}

2. 初始化 Player (如 ExoPlayer)

onCreate 生命周期中初始化真正的播放器引擎。

kotlin 复制代码
override fun onCreate() {
    super.onCreate()
    // 这里初始化视频播放器
    val player = ExoPlayer.Builder(this).build()
}

3. 构建并关联 MediaSession

Player 实例交给 MediaSession 托管,并设置 SessionActivity(点击通知栏跳转的页面)。

scss 复制代码
mediaSession = MediaSession.Builder(this, player)
            .setId(SESSION_ID)
            .setCallback(mediaSessionCallback)
            .build()

4. 初始化播放资源

构建MediaItem。仅作示例, 实际应用中, 应根据业务逻辑, 从本地数据库或网络获取音视频信息后, 动态构建 MediaItem

kotlin 复制代码
    private fun initMediaItem() {
        // 构建 MediaItem, 并设置到 ExoPlayer
        val mediaItem = MediaItem.Builder()
            .setUri("https://media.w3.org/2010/05/sintel/trailer.mp4".toUri())
            .setMediaId("123456")
            .setMediaMetadata(
                MediaMetadata.Builder()
                    .setTitle("视频标题")
                    .build(),
            )
            .build()
        player.setMediaItem(mediaItem)
        player.prepare()
        player.playWhenReady = true
    }

5. 在 AndroidManifest.xml 中注册服务

这是最关键的一步 。必须声明权限、Action 以及 foregroundServiceType(Android 10+ 要求)。

ini 复制代码
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService" />
    </intent-filter>
</service>

6. 生命周期管理

为了防止内存泄漏,务必在 onDestroy 中释放资源。

kotlin 复制代码
override fun onDestroy() {
    player.release()
    mediaSession.release()
    super.onDestroy()
}

完成上述步骤后,Media3会自动处理以下逻辑:

  • 自动生成通知 :只要 Player 开始播放,系统会自动弹出媒体通知和锁屏控件。

  • 状态同步 :锁屏上的进度条、暂停按钮会自动响应 Player 的状态变化。

7. 完整实现

服务端MediaSessionService完整代码如下:

kotlin 复制代码
const val SESSION_ID = "session_multimedia"

@OptIn(UnstableApi::class)
class PlaybackService : MediaSessionService() {
    private lateinit var mediaSession: MediaSession
    private lateinit var player: ExoPlayer
    private val mediaSessionCallback = object : MediaSession.Callback {

        override fun onPlaybackResumption(
            mediaSession: MediaSession,
            controller: MediaSession.ControllerInfo,
        ): ListenableFuture<MediaSession.MediaItemsWithStartPosition> {
            return super.onPlaybackResumption(mediaSession, controller)
        }

        override fun onConnect(
            session: MediaSession,
            controller: MediaSession.ControllerInfo,
        ): MediaSession.ConnectionResult {
            return super.onConnect(session, controller)
        }

        override fun onCustomCommand(
            session: MediaSession,
            controller: MediaSession.ControllerInfo,
            customCommand: SessionCommand,
            args: Bundle,
        ): ListenableFuture<SessionResult> {
            return super.onCustomCommand(session, controller, customCommand, args)
        }
    }

    override fun onCreate() {
        super.onCreate()
        initPlayer()
        initMediaSession()
        initMediaItem()
    }

    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {
        return mediaSession
    }


    private fun initPlayer() {
        player = ExoPlayer.Builder(this)
            .build()
    }

    private fun initMediaSession() {
        mediaSession = MediaSession.Builder(this, player)
            .setId(SESSION_ID)
            .setCallback(mediaSessionCallback)
            .build()
    }

    // 仅作示例, 实际应用中, 应根据业务逻辑, 从本地数据库或网络获取音视频信息后, 动态构建 MediaItem
    private fun initMediaItem() {
        // 构建 MediaItem, 并设置到 ExoPlayer
        val mediaItem = MediaItem.Builder()
            .setUri("https://media.w3.org/2010/05/sintel/trailer.mp4".toUri())
            .setMediaId("123456")
            .setMediaMetadata(
                MediaMetadata.Builder()
                    .setTitle("视频标题")
                    .build(),
            )
            .build()
        player.setMediaItem(mediaItem)
        player.prepare()
        player.playWhenReady = true
    }

    override fun onDestroy() {
        player.release()
        mediaSession.release()
        super.onDestroy()
    }
}

UI 端 / 客户端

实现 Media3 客户端(通常是的 UI 界面,如 ActivityFragment)主要围绕 MediaController 展开。

客户端的职责是:通过 Token 找到服务 -> 建立连接 -> 控制播放 -> 监听状态并刷新 UI

1. 定义 SessionToken

首先,需要创建一个"令牌"来定位后台服务。

ini 复制代码
val sessionToken = SessionToken(context, ComponentName(context.packageName, "com.wj.player.PlaybackService")) // 服务端的包名、MediasessionService类名

2. 建立异步连接

MediaController 的创建是异步的,需要监听它的连接成功回调。

kotlin 复制代码
private var controllerFuture: ListenableFuture<MediaController>? = null
private var mediaController: MediaController? = null

override fun onStart() {
    super.onStart()
    controllerFuture = MediaController.Builder(context, sessionToken).buildAsync()
    controllerFuture?.addListener({
        // 连接成功后,获取 controller 实例
        mediaController = controllerFuture?.get()
        // 此时可以绑定监听器或直接控制播放
        updateUI()
    }, MoreExecutors.directExecutor())
}

3. 控制播放 (如同操控本地 Player)

MediaController实现了Player接口,使用方式与 ExoPlayer 几乎完全一致,一些需要传入播放器才能使用的UI控件,可以直接传MediaController

scss 复制代码
// 播放
mediaController?.play()

// 切换下一曲
mediaController?.seekToNext()

// 调整进度
mediaController?.seekTo(10000L)

4. 监听状态更新 UI

通过向 mediaController 添加 Player.Listener,可以实时感知歌曲切换、进度改变或播放状态变化。

kotlin 复制代码
mediaController?.addListener(object : Player.Listener {
    override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
        // 当歌曲切换时,刷新界面的歌名、歌手和封面
        updateSongInfo(mediaItem)
    }

    override fun onIsPlayingChanged(isPlaying: Boolean) {
        // 当播放/暂停状态改变时,切换播放按钮图标
        playButton.setImageResource(if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play)
    }
})

5. 断开连接与释放

为了避免内存泄漏,在适当时机释放连接。

kotlin 复制代码
override fun onStop() {
    super.onStop()
    controllerFuture?.let {
        MediaController.releaseFuture(it)
    }
    mediaController = null
}

6. 完整实现

Compose UI 简要实现代码如下:

kotlin 复制代码
fun PlayerScreen(viewModel: PlayerViewModel, onBack: () -> Unit) {
    val mediaItems by viewModel.mediaItems.collectAsState()
    val playerState by viewModel.playerState.collectAsState()
    val context = LocalContext.current
    val mediaController = remember { mutableStateOf<MediaController?>(null) }
val isControllerReady = remember { mutableStateOf(false) }

LaunchedEffect(Unit) {
val sessionToken = SessionToken(context, ComponentName(context, PlaybackService::class.java))
        val controllerFuture = MediaController.Builder(context, sessionToken).buildAsync()
        controllerFuture.addListener(
            {
try {
                    val controller = controllerFuture.get()
                    mediaController.value = controller
                    // 添加监听器观察播放状态(仅在就绪后添加)
                    controller.addListener(
                        object : Player.Listener {
                            override fun onIsPlayingChanged(playing: Boolean) {
                                Log.e("PlayScreen", "onIsPlayingChanged: $playing")
                            }

                            override fun onPlaybackStateChanged(state: Int) {
                                Log.e("PlayScreen", "onPlaybackStateChanged: $state")
                            }
                        },
                    )
                    isControllerReady.value = true  // 标记就绪
                } catch (e: Exception) {
                    Log.e("PlayerScreen", "MediaController build failed: ${e.message}")
                    // 在这里处理 UI 反馈,如显示错误 Toast
                }
            } ,
            ContextCompat.getMainExecutor(context),
        )
    }

DisposableEffect(Unit) {
onDispose {
mediaController.value?.release()
            isControllerReady.value = false
        }
}

// 仅当控制器就绪时显示 PlayerControlView,否则显示加载 UI
    if (isControllerReady.value && mediaController.value != null) {
        mediaController.value?.let { controller ->
PlayerControlView(
                modifier = Modifier
                    .fillMaxSize()
                    .background(Color.Black),
                mediaPlayer = controller,
                onPlayPause = {
if (playerState == PlayerState.Playing) {
                        controller.pause()
                        viewModel.pause()
                    } else {
                        controller.play()
                        viewModel.play(mediaItems.first())
                    }
                } ,
                onSeek = { positionMs ->
controller.seekTo(positionMs)
                } ,
                currentTime = controller.currentPosition,
                duration = controller.duration,
            )
        }
} else {
        Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
            Text("Loading player...")
        }
}
}

@Composable
fun PlayerControlView(
    modifier: Modifier = Modifier,
    mediaPlayer: Player? = null,
) {
    // 中间播放区域
    Box(
        modifier = modifier
            .fillMaxSize()
            .background(Color.Black)
            .padding(16.dp),
    ) {
// 视频区域 (使用AndroidView嵌入PlayerView)
        AndroidView(
            modifier = Modifier.fillMaxSize(),
            factory = { context ->
PlayerView(context).apply {
 player = mediaPlayer
                }
} ,
        )
    }
}

四、高级功能开发

1. 锁屏控件增加自定义按钮

首先,需要明确一个概念,在 Media3 (MediaSession) 中,锁屏控件的展示由系统根据MediaSession的状态自动处理,不需要多媒体应用实现自定义的Notification。所以要在锁屏界面或通知栏增加自定义按钮,核心思路是通过 CommandButton 定义按钮,并将其添加到 MediaSession 的布局中。

第一步:自定义SessionCommand

首先,需要为每个自定义按钮定义一个唯一的 SessionCommand

kotlin 复制代码
// 定义按钮的 ID
private const val CUSTOM_COMMAND_FAVORITE = "ACTION_FAVORITE"

// 创建自定义 SessionCommand
private val favoriteCommand = SessionCommand(CUSTOM_COMMAND_FAVORITE, Bundle.EMPTY)

第二步:创建 CommandButton

使用 CommandButton.Builder 来配置按钮的图标、文字和对应的命令。

scss 复制代码
val favoriteButton = CommandButton.Builder()
    .setDisplayName("收藏")
    .setIconResId(R.drawable.ic_favorite) // 你的图标资源
    .setSessionCommand(favoriteCommand)
    .build()

第三步:设置 MediaSession 的自定义布局

在构建 MediaSession 时,或者在运行时动态更新,需要通过 setCustomLayout 方法将按钮告诉系统。

scss 复制代码
val mediaSession = MediaSession.Builder(context, player)
    .setCallback(sessionCallback())
    .setCustomLayout(ImmutableList.of(favoriteButton)) // 在这里添加自定义按钮列表
    .build()

第四步:处理按钮点击事件

重写 MediaSession.Callback 中的 onConnectonCustomCommand 方法,来设置自定义SessionCommand以及响应用户的点击操作。

kotlin 复制代码
private val mediaSessionCallback = object : MediaSession.Callback {

    override fun onConnect(
        session: MediaSession,
        controller: MediaSession.ControllerInfo,
    ): MediaSession.ConnectionResult {
        val sessionCommands = MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
            .add(favoriteCommand)
            .build()
        return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
            .setAvailableSessionCommands(sessionCommands)
            .build()
    }

    override fun onCustomCommand(
        session: MediaSession,
        controller: MediaSession.ControllerInfo,
        customCommand: SessionCommand,
        args: Bundle,
    ): ListenableFuture<SessionResult> {

        if (customCommand.customAction == CUSTOM_COMMAND_FAVORITE) {
            // 执行你的逻辑,比如收藏歌曲
            doFavorite()
        }

        return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
    }
}

2. 细粒度的连接管理 (Access Control)

多数时候我们不一定希望所有的外部设备(如所有的蓝牙手表或第三方助手)都能控制 你的播放器。通过重写 MediaSession.Callback 中的 onConnect,可以进行权限校验。

  • 实现方式 :检查 ControllerInfo 中的包名或 UID。
  • 场景:只允许特定的 App 远程控制你的媒体,或者为不同的客户端(如 Android Auto 与 手机通知栏)提供不同的自定义按钮布局。
kotlin 复制代码
override fun onConnect(
    session: MediaSession,
    controller: MediaSession.ControllerInfo,
): MediaSession.ConnectionResult {
    Log.e("PlaybackService", "onConnect ${controller.packageName}")
    val sessionCommands = MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()

    if (controller.packageName != packageName) {
        sessionCommands.add(favoriteCommand)
    }

    return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands.build())
        .build()
}

3. MediaButtonReceiver 重定向 (媒体按键处理)

MediaButtonReceiver 是 Android 中处理媒体按键(如耳机线控、蓝牙遥控器上的播放/暂停键)的关键组件。在 Jetpack Media3 中,它的使用变得非常自动化。当用户按下媒体按键时,系统会发出一个 ACTION_MEDIA_BUTTON 的广播。

  • 如果应用正在运行MediaSession 会直接接收并处理。
  • 如果应用已被杀死 :系统会根据清单文件(Manifest)中的配置,寻找能处理该广播的组件,从而重启你的后台服务,实现"一键恢复播放"。

在 Media3 中,不需要手动编写广播接收器类,库已经内置了处理逻辑。

第一步:在 AndroidManifest.xml 中注册

需要让系统知道你的 MediaSessionService 能够处理媒体按键广播。

ini 复制代码
<service
    android:name=".PlaybackService"
    android:exported="true"
    android:foregroundServiceType="mediaPlayback">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService" />
        <action android:name="android.intent.action.MEDIA_BUTTON" />
    </intent-filter>
</service>

<receiver android:name="androidx.media3.session.MediaButtonReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_BUTTON" />
    </intent-filter>
</receiver>

第二步:在 Service 中处理恢复逻辑

当用户在应用关闭时按下播放键,MediaSessionService 会被系统唤醒。此时,需要告诉系统"播放什么"。在 MediaSession.Callback 中重写 onPlaybackResumption

kotlin 复制代码
private inner class MySessionCallback : MediaSession.Callback {

    override fun onPlaybackResumption(
        session: MediaSession,
        controller: MediaSession.ControllerInfo
    ): ListenableFuture<MediaSession.ConnectionResult> {
       
        // 1. 在这里加载用户上次退出的播放列表、进度等数据
        val lastMediaItems = loadLastPlayedList() 
        val lastIndex = loadLastIndex()
        val lastPosition = loadLastPosition()

        // 2. 将数据设置给 Player
        session.player.setMediaItems(lastMediaItems, lastIndex, lastPosition)
        session.player.prepare()

        // 3. 返回接受连接的结果,告知系统已准备好恢复
        return Futures.immediateFuture(
            MediaSession.ConnectionResult.AcceptedResultBuilder(session).build()
        )
    }
}

五、附录

MediaSession 常用API与使用场景说明


1. 核心身份与回调

  • setCallback(MediaSession.Callback callback)

    • 含义:设置会话回调,用于处理外部指令(如:点击锁屏的自定义按钮、搜索请求、尝试连接等)。
    • 场景必用。如果你需要处理自定义按钮点击、鉴权、或者对特定的播放控制逻辑进行拦截,就必须实现并设置此回调。
  • setId(String id)

    • 含义 :为 MediaSession 设置一个 ID。
    • 场景 :同一个应用开启多个 MediaSession 时(例如一个播音乐,一个播播客),用于区分不同的会话。如果不设置,默认是空字符串。

2. 界面展示与交互

  • setSessionActivity(PendingIntent pendingIntent)

    • 含义 :设置一个点击通知栏或锁屏界面时跳转的 Activity
    • 场景强烈建议设置 。当用户在锁屏界面点击歌曲名称或封面时,系统会调用这个 PendingIntent 调起你的 App 播放界面。
  • setCustomLayout(List<CommandButton> buttons)

    • 含义:定义要在通知栏或锁屏上显示的自定义按钮列表。
    • 场景:当你除了"播放/暂停/上一曲/下一曲"之外,还想要"收藏"、"倍速"或"循环模式"按钮时使用。

3. 数据与状态管理

  • setExtras(Bundle extras)

    • 含义:存放自定义的元数据或配置信息。
    • 场景 :用于向 MediaController(控制器端)传递一些非标准的数据信息。
  • setBitmapLoader(BitmapLoader bitmapLoader)

    • 含义:自定义图片加载逻辑。
    • 场景 :默认情况下 Media3 使用简单的网络加载。如果你使用了 GlideCoil 加载封面图,可以通过此方法集成,以确保封面能高效地显示在锁屏上。

4. 周期管理

  • setSessionExtras(Bundle extras)

    • 含义 :与 setExtras 类似,但更侧重于在 Session 级别传递初始配置。
  • setPeriodicSessionExtrasInfoRefreshIntervalMs(long interval)

    • 含义:设置定期刷新 Session Extras 的时间间隔。

    • 场景:当你的会话状态(如特定的自定义业务状态)需要定时同步给所有连接的控制器时使用。

5. 典型使用示例

scss 复制代码
val session = MediaSession.Builder(context, player)
    // 1. 设置点击通知跳转到哪个页面
    .setSessionActivity(createPendingIntent()) 
    // 2. 设置回调处理逻辑
    .setCallback(MySessionCallback()) 
    // 3. 设置锁屏/通知栏的自定义按钮
    .setCustomLayout(listOf(favoriteButton, speedButton)) 
    // 4. 定义 ID 方便调试或多实例管理
    .setId("MusicSession") 
    .build()

MediaItem 常用API与使用场景说明

1. 基础资源定位

这是最常用的部分,用于告诉播放器"播什么"。

  • setMediaId(String mediaId)

    • 含义:为媒体项设置唯一标识符。
    • 场景 :在播放列表管理、从数据库恢复播放状态、或在 MediaSession 回调中识别歌曲时非常重要。如果不设置,默认使用 URI
  • setUri(Uri uri / String uri)

    • 含义:媒体内容的实际路径。
    • 场景:指向本地文件路径、网络 HTTP 链接或 ContentProvider 的 URI。

2. 媒体元数据

用于 UI 展示,如锁屏界面、通知栏和蓝牙车载界面。

  • setMediaMetadata(MediaMetadata metadata)

    • 含义 :传入一个 MediaMetadata 对象。
    • 常用内部属性setTitle() (标题), setArtist() (艺术家), setArtworkUri() (封面图片地址)。
    • 场景:当你需要让锁屏控件显示歌曲名和封面图时使用。

3. 自适应与高级配置

用于处理加密视频、流媒体协议或特定类型的文件。

  • setDrmConfiguration(MediaItem.DrmConfiguration drmConfig)

    • 含义:设置数字版权管理(DRM)配置。
    • 场景:播放受保护的付费内容(如 Widevine, PlayReady 加密的视频)。
  • setMimeType(String mimeType)

    • 含义 :显式指定媒体类型(如 application/x-mpegURL 对应 HLS)。
    • 场景:当 URI 后缀无法直接判断类型时(例如一个没有扩展名的流链接),设置此项能加快播放器解析速度。
  • setLiveConfiguration(MediaItem.LiveConfiguration liveConfig)

    • 含义:配置直播流参数(如最大/最小偏移偏移量、播放倍速)。
    • 场景:低延迟直播(Low-latency live streaming)。

4. 剪辑与部分播放

  • setClippingConfiguration(MediaItem.ClippingConfiguration config)

    • 含义:设置播放的起始和结束位置。
    • 场景:你只想播放长视频中的某一段(例如:从第 10 秒播到第 30 秒),而不需要手动去监听进度并停止。

5. 附加组件

  • setSubtitleConfigurations(List<SubtitleConfiguration> subtitles)

    • 含义:外挂字幕配置。
    • 场景 :视频文件本身不含字幕,需要加载外部的 .vtt.srt 文件时。

6. 自定义数据

  • setTag(Object tag)

    • 含义:挂载一个自定义的 Java/Kotlin 对象。
    • 场景 :在 onMediaItemTransition 监听器中,你想快速获取该媒体项关联的业务模型对象(比如数据库里的 User 对象),而不想通过 mediaId 重新查询。
  • setCustomCacheKey(String cacheKey)

    • 含义:自定义缓存键。
    • 场景:如果多个不同 URI 指向同一个物理文件,通过设置相同的 Key 可以避免重复下载缓存。

7. 典型使用示例

less 复制代码
val mediaItem = MediaItem.Builder()
    .setMediaId("song_101")
    .setUri("https://example.com/music.mp3")
    .setMediaMetadata(
        MediaMetadata.Builder()
            .setTitle("江南")
            .setArtist("林俊杰")
            .setArtworkUri(Uri.parse("https://example.com/cover.jpg"))
            .build()
    )
    // 假设只播前 30 秒作为试听
    .setClippingConfiguration(
        MediaItem.ClippingConfiguration.Builder()
            .setEndPositionMs(30000)
            .build()
    )
    .setTag(myCustomObject) // 绑定自定义业务数据
    .build()

player.setMediaItem(mediaItem)

六、总结

以上就是MediaSession入门级的知识总结,在车载应用开发中,大多数情况下都是开发一个客户端应用连接到MediaSessionService上,用于控制媒体中心的行为,那么本文涉及的内容已经基本够用。

下一篇,我们来讲讲多媒体开发过程最常见的音频焦点的处理。

相关推荐
QING6188 小时前
简单说下Kotlin 作用域函数中 apply 和 also 为什么不能空安全调用?
android·kotlin·android jetpack
城东米粉儿8 小时前
着色器 (Shader) 的基本概念和 GLSL 语法 笔记
android
儿歌八万首10 小时前
Jetpack Compose :封装 MVVM 框架
android·kotlin·compose
2501_9159214310 小时前
iOS App 中 SSL Pinning 场景下代理抓包失效的原因
android·网络协议·ios·小程序·uni-app·iphone·ssl
壮哥_icon10 小时前
Android 系统级 USB 存储检测的工程化实现(抗 ROM、抗广播丢失)
android·android-studio·android系统
Junerver10 小时前
积极拥抱AI,ComposeHooks让你更方便地使用AI
android·前端
城东米粉儿10 小时前
ColorMatrix色彩变换 笔记
android
方白羽10 小时前
告别onActivityResult:Android数据回传的三大痛点与终极方案
android·app·客户端
oMcLin10 小时前
如何在 RHEL 8 系统上实现高可用 MySQL 集群,保障电商平台的 24 小时稳定运行
android·mysql·adb