You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Media3 MediaSessionService未生成媒体通知问题求助

问题描述

根据Android官方文档说明:

MediaSessionService会以MediaNotification的形式自动为您创建MediaStyle通知。

我实现了一个支持“播放”歌曲和获取元数据的最小化MediaSessionService,可通过Activity中的MediaController进行控制,但在应用外无法看到用于控制的通知。

补充说明:应用已获取android.permission.POST_NOTIFICATIONS权限,且未触发onForegroundServiceStartNotAllowedException,但仍无通知显示。


相关代码

PlaybackService代码

@OptIn(UnstableApi::class)
class PlaybackService : MediaSessionService() {
    private val _logger = Logger.getLogger("PlaybackService")
    private val _job = SupervisorJob()
    private var _mediaSession: MediaSession? = null

    override fun onCreate() {
        super.onCreate()

        val scope = CoroutineScope(Dispatchers.IO + _job)
        _mediaSession = MediaSession.Builder(this, Player(mainLooper, scope)).build()
        setListener(object : Listener {
            override fun onForegroundServiceStartNotAllowedException() {
                _logger.warning("Foreground service start not allowed.")
            }
        })
    }

    override fun onDestroy() {
        _mediaSession?.player?.release();
        _mediaSession?.release();

        _job.cancel()

        super.onDestroy()
    }

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

    private class Player(looper: Looper, val scope: CoroutineScope) : SimpleBasePlayer(looper) {
        private val _logger = Logger.getLogger("PlaybackService")

        private val _availableCommands: Commands = Commands.Builder().addAll(
            COMMAND_PLAY_PAUSE,
            COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM,
            COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
            COMMAND_GET_CURRENT_MEDIA_ITEM,
            COMMAND_GET_METADATA
        ).build();

        private val _metadata = MediaMetadata.Builder()
            .setArtist("Artist")
            .setTitle("Title")
            .build()

        private val _audioAttributes = AudioAttributes.Builder()
            .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
            .setUsage(C.USAGE_MEDIA)
            .build()

        override fun getState(): State {
            return State.Builder()
                .setAvailableCommands(_availableCommands)
                .setPlaylist(listOf(MediaItemData.Builder("")
                    .setMediaMetadata(_metadata)
                    .build()))
                .setAudioAttributes(_audioAttributes)
                .build()
        }

        override fun handleSetPlayWhenReady(playWhenReady: Boolean) = scope.future {
            onPlayPause(playWhenReady)
        }

        private suspend fun onPlayPause(play: Boolean) {
            delay(500)
            if (play)
                _logger.info("Play command invoked.")
            else
                _logger.info("Pause command invoked.")
        }
    }
}

MediaController使用代码

override fun onStart() {
    super.onStart()

    val context = requireContext()

    if (ContextCompat.checkSelfPermission(context, "android.permission.POST_NOTIFICATIONS") != PERMISSION_GRANTED) {
        _logger.info("Requesting permission for 'android.permission.POST_NOTIFICATIONS'...")
        ActivityCompat.requestPermissions(requireActivity(), arrayOf("android.permission.POST_NOTIFICATIONS"), 0)
    } else
        _logger.info("Already has permission for 'android.permission.POST_NOTIFICATIONS'.")

    val sessionToken = SessionToken(context, ComponentName(context, PlaybackService::class.java))
    val future = MediaController.Builder(context, sessionToken).buildAsync();
    future.addListener({
        val mediaController = future.get()
        mediaController.play()
        val metadata = mediaController.mediaMetadata
    }, MoreExecutors.directExecutor())
}

Android清单文件

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.LayoutPreview"
        tools:targetApi="31">

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

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

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

问题修复方案

你遗漏了两个核心逻辑,导致自动通知未触发:

1. 播放器状态未完善关键字段

MediaSessionService需要明确的播放状态、进度信息才会触发通知逻辑。修改Player类:

private class Player(looper: Looper, val scope: CoroutineScope) : SimpleBasePlayer(looper) {
    private val _logger = Logger.getLogger("PlaybackService")
    private var _isPlaying = false // 新增播放状态标记

    // ... 原有代码不变 ...

    override fun getState(): State {
        return State.Builder()
            .setAvailableCommands(_availableCommands)
            .setPlaylist(listOf(MediaItemData.Builder("")
                .setMediaMetadata(_metadata)
                .build()))
            .setAudioAttributes(_audioAttributes)
            // 补充播放状态标记
            .setPlayWhenReady(_isPlaying)
            // 补充播放状态类型
            .setPlaybackState(if (_isPlaying) PLAYBACK_STATE_PLAYING else PLAYBACK_STATE_PAUSED)
            // 补充播放进度(必须字段,否则通知逻辑不会触发)
            .setPositionMs(0)
            .setDurationMs(300000) // 示例:设置歌曲时长为5分钟
            .build()
    }

    private suspend fun onPlayPause(play: Boolean) {
        delay(500)
        _isPlaying = play
        notifyStateChanged() // 通知状态更新
        if (play)
            _logger.info("Play command invoked.")
        else
            _logger.info("Pause command invoked.")
    }
}

2. 绑定会话活动并监听状态变化

在PlaybackService的onCreate中,给MediaSession绑定会话活动,并监听播放器状态变化:

override fun onCreate() {
    super.onCreate()

    val scope = CoroutineScope(Dispatchers.IO + _job)
    val player = Player(mainLooper, scope)
    _mediaSession = MediaSession.Builder(this, player).build()

    // 绑定会话活动,通知需要关联的Activity
    _mediaSession?.setSessionActivity(PendingIntent.getActivity(
        this@PlaybackService,
        0,
        Intent(this@PlaybackService, MainActivity::class.java),
        PendingIntent.FLAG_IMMUTABLE
    ))

    // 监听播放器状态变化,确保服务能感知播放状态
    player.addListener(object : Player.Listener {
        override fun onStateChanged(state: State) {
            // 状态变化时自动触发前台通知逻辑
        }
    })

    setListener(object : Listener {
        override fun onForegroundServiceStartNotAllowedException() {
            _logger.warning("Foreground service start not allowed.")
        }
    })
}

额外检查项

  • 确保使用最新版本的Media3依赖,旧版本可能存在自动通知逻辑的bug
  • 测试时确认播放器确实进入PLAYING状态,只有播放中才会触发前台通知

内容的提问来源于stack exchange,提问作者Dark Daskin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 20:45:09