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

自定义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

相关产品推荐
方舟 Agent Plan

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

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