自定义CastPlayer投屏通知后物理音量按键失效及双通知问题
解决Chromecast自定义通知与物理音量键控制冲突问题
核心思路
既不显示重复通知,又保留物理按键控制投屏音量的关键是:让Cast框架复用你的前台服务的媒体会话和通知通道,而非直接禁用Cast的通知配置。
具体实现步骤
1. 调整CastOptionsProvider配置
不要将NotificationOptions设为null,而是指定使用你前台服务的通知通道,并设置不单独创建Cast通知:
public class CastOptionsProvider implements OptionsProvider { @Override public CastOptions getCastOptions(Context context) { // 替换为你的前台服务通知通道ID String foregroundChannelId = "CAST_PLAYER_CHANNEL"; NotificationOptions castNotificationOpts = new NotificationOptions.Builder() .setNotificationChannelId(foregroundChannelId) // 设置为null,让Cast复用你的前台通知的媒体会话 .setMediaSessionToken(null) .build(); CastMediaOptions mediaOpts = new CastMediaOptions.Builder() .setNotificationOptions(castNotificationOpts) .build(); return new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setCastMediaOptions(mediaOpts) .build(); } @Override public List<SessionProvider> getAdditionalSessionProviders(Context context) { return null; } }
2. 前台服务中配置媒体会话
在你的Player Service中创建MediaSessionCompat,关联到前台通知,并处理音量调整逻辑:
public class PlayerService extends Service { private MediaSessionCompat mediaSession; private CastSession currentCastSession; @Override public void onCreate() { super.onCreate(); initCastSessionListener(); initMediaSession(); startForegroundServiceWithCustomNotification(); } private void initCastSessionListener() { // 监听Cast会话状态,获取当前活跃的投屏会话 CastContext.getSharedInstance(this).getSessionManager().addSessionManagerListener( new SessionManagerListener<CastSession>() { @Override public void onSessionStarted(CastSession session, String sessionId) { currentCastSession = session; } @Override public void onSessionEnded(CastSession session, int error) { currentCastSession = null; } // 实现其他必要的回调方法 @Override public void onSessionStarting(CastSession session) {} @Override public void onSessionEnding(CastSession session) {} @Override public void onSessionResumed(CastSession session, boolean wasSuspended) {} @Override public void onSessionResuming(CastSession session, String sessionId) {} @Override public void onSessionSuspended(CastSession session, int reason) {} }, CastSession.class); } private void initMediaSession() { mediaSession = new MediaSessionCompat(this, "CastPlayerSession"); mediaSession.setCallback(new MediaSessionCompat.Callback() { @Override public void onAdjustVolume(int direction, boolean fromKey) { if (currentCastSession != null && currentCastSession.isConnected()) { // 有活跃投屏会话时,控制Chromecast音量 float currentVol = currentCastSession.getVolume(); float newVol = direction == AudioManager.ADJUST_RAISE ? Math.min(currentVol + 0.1f, 1.0f) : Math.max(currentVol - 0.1f, 0.0f); currentCastSession.setVolume(newVol); } else { // 无投屏时控制本地音量 AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, direction, 0); } } // 按需实现其他播放控制回调(play/pause/stop等) @Override public void onPlay() { if (currentCastSession != null) { currentCastSession.getRemoteMediaClient().play(); } } @Override public void onPause() { if (currentCastSession != null) { currentCastSession.getRemoteMediaClient().pause(); } } }); mediaSession.setActive(true); } private void startForegroundServiceWithCustomNotification() { // 确保已创建通知通道(Android 8.0+) createNotificationChannel(); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, "CAST_PLAYER_CHANNEL") .setSmallIcon(R.drawable.ic_cast_notification) .setContentTitle("投屏中") .setContentText("当前播放内容") .setPriority(NotificationCompat.PRIORITY_LOW) // 将媒体会话关联到通知,让Cast框架能通过它接收按键事件 .setStyle(new androidx.media.app.NotificationCompat.MediaStyle() .setMediaSession(mediaSession.getSessionToken()) .setShowActionsInCompactView(0, 1)) // 添加播放控制按钮 .addAction(R.drawable.ic_pause, "暂停", MediaButtonReceiver.buildMediaButtonPendingIntent(this, PlaybackStateCompat.ACTION_PAUSE)) .addAction(R.drawable.ic_play, "播放", MediaButtonReceiver.buildMediaButtonPendingIntent(this, PlaybackStateCompat.ACTION_PLAY)); startForeground(1001, notificationBuilder.build()); } private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( "CAST_PLAYER_CHANNEL", "投屏播放", NotificationManager.IMPORTANCE_LOW ); NotificationManager notificationManager = getSystemService(NotificationManager.class); notificationManager.createNotificationChannel(channel); } } @Override public void onDestroy() { super.onDestroy(); mediaSession.release(); CastContext.getSharedInstance(this).getSessionManager().removeSessionManagerListener( (SessionManagerListener<?>) CastContext.getSharedInstance(this).getSessionManager().getSessionManagerListeners().get(0), CastSession.class); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }
3. 配置权限与清单声明
在AndroidManifest.xml中添加必要权限和服务声明:
<!-- Android 12+ 前台服务权限 --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> <application ...> <service android:name=".PlayerService" android:foregroundServiceType="mediaPlayback" /> <!-- CastOptionsProvider 声明 --> <meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME" android:value=".CastOptionsProvider" /> </application>
关键说明
- 禁用Cast默认通知的正确方式是给
NotificationOptions设置setMediaSessionToken(null),而非直接传null给setNotificationOptions,后者会完全切断Cast框架与媒体控制的关联,导致音量键失效。 - 通过监听Cast会话状态,在媒体会话的
onAdjustVolume回调中针对性处理投屏或本地播放的音量调整,确保物理按键的控制逻辑正确。 - 前台通知必须关联媒体会话,这样Cast框架才能通过该会话接收系统按键事件,实现音量控制。
内容的提问来源于stack exchange,提问作者Mateusz Kaflowski
相关产品推荐
相关产品推荐

