You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何为基于ExoPlayer的音乐播放器服务添加自定义媒体通知操作?

如何为基于ExoPlayer的音乐播放器服务添加自定义媒体通知操作?

我看你已经在基于Java的Material 3音乐播放器里用了Media3(ExoPlayer)框架,接下来结合你现有的代码,一步步给你梳理怎么把自定义操作按钮加到媒体通知里:


1. 先在MediaSession回调中注册自定义命令

要让系统识别你的自定义操作,首先得在MediaSession.Callback里声明自定义命令,同时把这些命令和对应的按钮布局传给媒体控制器。这样通知里的自定义按钮才能和逻辑绑定:

public class CustomCallback implements MediaSession.Callback {
    // 定义你的自定义命令,action字符串要唯一,后续触发要对应
    private final SessionCommand SHUFFLE_COMMAND = new SessionCommand("com.xmusic.action.SHUFFLE", Bundle.EMPTY);
    // 定义自定义按钮:图标、显示名称、绑定的命令、位置槽位
    private final CommandButton SHUFFLE_BUTTON = new CommandButton.Builder(R.drawable.ic_shuffle)
            .setDisplayName("随机播放")
            .setSessionCommand(SHUFFLE_COMMAND)
            .setSlots(CommandButton.SLOT_FORWARD_SECONDARY) // 按钮在通知的次要位置
            .build();
    private final List<CommandButton> CUSTOM_LAYOUT = ImmutableList.of(SHUFFLE_BUTTON);

    // 声明可用的命令集合:系统默认命令 + 自定义命令
    private final SessionCommands AVAILABLE_COMMANDS = SessionCommands.EMPTY.buildUpon()
            .addAll(Player.COMMANDS) // 包含播放/暂停/上一曲/下一曲等默认命令
            .add(SHUFFLE_COMMAND)
            .build();

    @Override
    public ConnectionResult onConnect(MediaSession mediaSession, ControllerInfo controllerInfo) {
        // 连接时告诉控制器我们支持的自定义命令和布局
        return ConnectionResult.accept(AVAILABLE_COMMANDS, Player.COMMANDS)
                .setCustomLayout(CUSTOM_LAYOUT);
    }

    @Override
    public void onPostConnect(MediaSession mediaSession, ControllerInfo controllerInfo) {
        // 连接完成后再次同步自定义布局和命令,确保兼容性
        mediaSession.setAvailableCommands(controllerInfo, AVAILABLE_COMMANDS, Player.COMMANDS);
        mediaSession.setCustomLayout(controllerInfo, CUSTOM_LAYOUT);
    }

    @Override
    public ListenableFuture<SessionResult> onCustomCommand(MediaSession mediaSession, ControllerInfo controllerInfo, SessionCommand sessionCommand, Bundle bundle) {
        // 处理自定义按钮的点击逻辑
        if ("com.xmusic.action.SHUFFLE".equals(sessionCommand.customAction)) {
            // 这里写你的随机播放切换逻辑,比如:
            boolean isShuffling = mediaSession.getPlayer().getShuffleMode() == Player.SHUFFLE_MODE_ALL;
            mediaSession.getPlayer().setShuffleMode(isShuffling ? Player.SHUFFLE_MODE_NONE : Player.SHUFFLE_MODE_ALL);
            return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
        }
        // 未知命令返回失败
        return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_ERROR_UNKNOWN));
    }
}

重点:自定义命令的action字符串(比如com.xmusic.action.SHUFFLE)要全局唯一,点击通知按钮时会通过这个字符串匹配到对应的处理逻辑


2. 自定义NotificationProvider添加按钮到通知栏

你已经继承了DefaultMediaNotificationProvider,现在重写addNotificationActions方法,把自定义按钮加到通知Builder里:

public class CustomNotificationProvider extends DefaultMediaNotificationProvider {
    private final Context mContext;

    public CustomNotificationProvider(Context context) {
        super(context);
        mContext = context;
    }

    @Override
    public int[] addNotificationActions(MediaSession mediaSession, ImmutableList<CommandButton> mediaButtons, NotificationCompat.Builder builder, MediaNotification.ActionFactory actionFactory) {
        // 先添加系统默认的播放控制按钮(比如播放/暂停/上一曲/下一曲)
        int[] defaultActionIndices = super.addNotificationActions(mediaSession, mediaButtons, builder, actionFactory);

        // 添加我们的自定义随机播放按钮
        CommandButton shuffleButton = new CommandButton.Builder(R.drawable.ic_shuffle)
                .setDisplayName("随机播放")
                .setSessionCommand(new SessionCommand("com.xmusic.action.SHUFFLE", Bundle.EMPTY))
                .setSlots(CommandButton.SLOT_BACK_SECONDARY)
                .build();

        // 用ActionFactory创建可触发自定义命令的Notification Action
        NotificationCompat.Action shuffleAction = actionFactory.createCustomAction(
                mediaSession,
                IconCompat.createWithResource(mContext, R.drawable.ic_shuffle),
                "随机播放",
                "com.xmusic.action.SHUFFLE",
                Bundle.EMPTY
        );
        builder.addAction(shuffleAction);

        // 返回操作按钮的索引数组(如果需要调整顺序可以修改)
        return defaultActionIndices;
    }
}

注意:如果不想保留系统默认按钮,可以跳过super.addNotificationActions这一步,直接添加自定义按钮


3. 在Service初始化时完成关联配置

最后在你的音乐播放器Service的onCreate方法里,把这些组件串联起来,确保MediaSession、NotificationProvider、前台服务都正确初始化:

@Override
public void onCreate() {
    super.onCreate();
    fallbackUri = Uri.parse("android.resource://" + this.getPackageName() + "/" + resId);

    // 1. 初始化ExoPlayer到后台线程
    HandlerThread handlerThread = new HandlerThread("ExoPlayerThread");
    handlerThread.start();
    Looper backgroundLooper = handlerThread.getLooper();
    Handler exoPlayerHandler = new Handler(backgroundLooper);

    DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this)
            .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON);
    Player player = new ExoPlayer.Builder(this, renderersFactory)
            .setLooper(backgroundLooper)
            .build();

    // 配置音频属性
    androidx.media3.common.AudioAttributes audioAttrs = new androidx.media3.common.AudioAttributes.Builder()
            .setUsage(C.USAGE_MEDIA)
            .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
            .build();
    exoPlayerHandler.post(() -> player.setAudioAttributes(audioAttrs, true));

    // 2. 初始化MediaSession并绑定自定义Callback
    boolean isBuilt = false;
    if (!isBuilt) {
        setupMediaSession(player);
        isBuilt = true;
    }

    // 3. 配置通知相关
    boolean isNotifDead = false;
    createNotificationChannel();
    // 设置自定义通知提供者
    setMediaNotificationProvider(new CustomNotificationProvider(this));

    // 4. 启动前台服务(适配Android 13+的前台服务类型)
    Notification initialNotification = buildNotification("XMusic", "No song is playing", "");
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        startForeground(NOTIFICATION_ID, initialNotification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
    } else {
        startForeground(NOTIFICATION_ID, initialNotification);
    }

    // 其他初始化:音频焦点、AudioManager等(你已有的代码保留即可)
    AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    setupAudioFocusRequest();
}

// 初始化MediaSession的方法
private void setupMediaSession(Player player) {
    MediaSession mediaSession = null;
    if (mediaSession != null) return;
    mediaSession = new androidx.media3.session.MediaSession.Builder(this, player)
            .setId("XMusicMediaSessionPrivate")
            .setCallback(new CustomCallback()) // 绑定我们的自定义回调
            .build();
}

// 基础通知构建方法(你已有的代码可以保留,注意适配Media3的API)
private Notification buildNotification(String title, String artist, String cover) {
    MediaSession mediaSession = null;
    if (mediaSession == null) setupMediaSession(player);

    MediaStyleNotificationHelper.MediaStyle mediaStyle = new MediaStyleNotificationHelper.MediaStyle(mediaSession)
            .setShowCancelButton(true);

    Intent resumeIntent = getPackageManager().getLaunchIntentForPackage(getPackageName());
    PendingIntent contentIntent = PendingIntent.getActivity(this, 0, resumeIntent, PendingIntent.FLAG_IMMUTABLE);

    // 专辑封面Bitmap,确保已正确加载
    Bitmap current = null;

    return new NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.mipmap.ic_launcher_foreground)
            .setContentTitle(title)
            .setContentText(artist)
            .setLargeIcon(current)
            .setStyle(mediaStyle)
            .setOngoing(true)
            .setContentIntent(contentIntent)
            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            .build();
}

// 创建通知通道(Android 8.0+必须)
private void createNotificationChannel() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationChannel channel = new NotificationChannel(
                "XMusic_CHANNEL",
                "XMusic Playback",
                NotificationManager.IMPORTANCE_LOW
        );
        channel.setDescription("Music playback controls");
        NotificationManager notificationManager = getSystemService(NotificationManager.class);
        notificationManager.createNotificationChannel(channel);
    }
}

几个关键注意点

  1. SDK版本适配:Android 13(API 33)及以上需要指定FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK类型,否则会崩溃。
  2. PendingIntent的Flag:Android 12(API 31)及以上必须用FLAG_IMMUTABLEFLAG_MUTABLE,这里用FLAG_IMMUTABLE即可,因为通知操作不需要修改Intent。
  3. 音频焦点:确保你已经正确申请和处理音频焦点(你代码里的setupAudioFocusRequest),这是媒体播放器的标准流程。
  4. 资源命名:自定义按钮的图标资源(比如ic_shuffle)要确保在drawable目录下存在,避免资源找不到的崩溃。

这样调整后,你的媒体通知里就会出现自定义的随机播放按钮,点击后会触发CustomCallback里的逻辑,完成对应的功能啦!

火山引擎 最新活动