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

如何通过MediaSessionService实现Glance Widget音频播放

解决方案

一、实现从Widget触发音频播放

Widget属于跨进程组件,无法直接调用应用内播放器逻辑,需通过启动MediaSessionService并传递操作指令来实现播放控制:

  1. 完善Widget点击回调逻辑
    在AudiotWidgetView的onSoundPlayStopClicked中,发送Intent到MediaSessionService,携带音频ID/URL及操作类型(播放/停止):
onSoundPlayStopClicked = { soundUrl ->
    val intent = Intent(context, YourMediaSessionService::class.java).apply {
        action = if (playingSoundUrl == soundUrl) {
            ACTION_STOP_PLAYBACK
        } else {
            ACTION_START_PLAYBACK
        }
        putExtra(EXTRA_SOUND_URL, soundUrl)
    }
    // Android 12+需使用startForegroundService启动前台服务
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        context.startForegroundService(intent)
    } else {
        context.startService(intent)
    }
}
  1. 在MediaSessionService中处理Intent
    重写onStartCommand方法,解析Intent并调用SoundServiceHandler的播放/停止方法:
@HiltService
class YourMediaSessionService : MediaSessionService() {
    @Inject
    lateinit var soundServiceHandler: SoundServiceHandler
    private lateinit var mediaSession: MediaSession

    override fun onCreate() {
        super.onCreate()
        // 初始化前台服务通知,确保服务存活
        createNotificationChannel(this)
        val notification = buildPlaybackNotification()
        startForeground(PLAYBACK_NOTIFICATION_ID, notification)
        mediaSession = soundServiceHandler.mediaSession
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        intent?.let {
            when (it.action) {
                ACTION_START_PLAYBACK -> {
                    val soundUrl = it.getStringExtra(EXTRA_SOUND_URL)
                    soundUrl?.let { url ->
                        soundServiceHandler.playSound(url) // 调用已实现的播放方法
                    }
                }
                ACTION_STOP_PLAYBACK -> {
                    soundServiceHandler.stopPlayback() // 调用已实现的停止方法
                }
            }
        }
        return START_STICKY // 服务被杀死后尝试重启
    }

    private fun buildPlaybackNotification(): Notification {
        return NotificationCompat.Builder(this, PLAYBACK_CHANNEL_ID)
            .setContentTitle("音频播放中")
            .setSmallIcon(R.drawable.ic_audio_play)
            .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
            .build()
    }

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

    companion object {
        const val ACTION_START_PLAYBACK = "com.your.package.ACTION_START_PLAYBACK"
        const val ACTION_STOP_PLAYBACK = "com.your.package.ACTION_STOP_PLAYBACK"
        const val EXTRA_SOUND_URL = "EXTRA_SOUND_URL"
        const val PLAYBACK_NOTIFICATION_ID = 1001
        const val PLAYBACK_CHANNEL_ID = "AUDIO_PLAYBACK_CHANNEL"
    }
}
  1. 同步应用UI状态
    在Compose页面中,通过collectAsStateWithLifecycle监听SoundServiceHandler的状态流,实时更新播放UI:
// 应用内Compose页面
val serviceState by soundServiceHandler.serviceState.collectAsStateWithLifecycle()

when (serviceState) {
    is PlayerState.Playing -> {
        // 渲染播放中UI(如暂停按钮)
    }
    PlayerState.Stopped, PlayerState.Initial -> {
        // 渲染停止/初始UI(如播放按钮)
    }
}

二、解决应用被后台清除后Widget无法更新的问题

核心原因是MediaSessionService未以前台服务运行,导致应用被杀死后服务被系统回收,无法触发Widget更新。需做以下调整:

  1. 确保Service为前台服务
    在MediaSessionService的onCreate中调用startForeground,并提前创建通知渠道(Android O+要求):
// 全局创建通知渠道(可在Application onCreate中调用)
fun createNotificationChannel(context: Context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channel = NotificationChannel(
            PLAYBACK_CHANNEL_ID,
            "音频播放",
            NotificationManager.IMPORTANCE_LOW
        ).apply {
            description = "音频播放前台服务通知"
        }
        val notificationManager = context.getSystemService(NotificationManager::class.java)
        notificationManager.createNotificationChannel(channel)
    }
}
  1. 正确配置Hilt与Manifest
    确保MediaSessionService使用@HiltService注解,并在Manifest中注册:
<service
    android:name=".service.YourMediaSessionService"
    android:exported="true"
    android:foregroundServiceType="mediaPlayback">
    <intent-filter>
        <action android:name="android.media.browse.MediaBrowserService" />
    </intent-filter>
</service>
  1. 优化Widget更新任务
    使用WorkManager的唯一任务策略,避免重复更新任务,同时确保任务在后台可执行:
// 替换原WidgetWorker.enqueue方法
fun enqueue(context: Context, soundUrl: String?) {
    val inputData = workDataOf(PLAYING_SOUND_URL_KEY to soundUrl)
    val updateRequest = OneTimeWorkRequestBuilder<WidgetWorker>()
        .setInputData(inputData)
        .setConstraints(Constraints.Builder()
            .setRequiredNetworkType(NetworkType.NOT_REQUIRED)
            .build())
        .build()

    WorkManager.getInstance(context)
        .enqueueUniqueWork(
            "UpdateAudioWidget",
            ExistingWorkPolicy.REPLACE,
            updateRequest
        )
}

关键注意事项

  • Widget通过DataStore监听播放状态变化,需确保playingSoundUrlStream能正确触发Glance重组。
  • 前台服务通知需符合媒体播放规范,否则系统可能限制服务存活时间。
  • 从Widget触发播放后,需同时更新DataStore与SoundServiceHandler的状态流,保证应用UI与Widget状态同步。

内容的提问来源于stack exchange,提问作者Пенка Пшеничная

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 00:50:22