在鸿蒙(HarmonyOS)应用开发中,AVSession Kit(音视频播控服务)是实现专业级音视频体验的核心框架。它不仅能让应用在锁屏状态下继续播放,还能将播放控制权无缝移交给系统播控中心、蓝牙耳机、智能手表等设备。

一、 核心机制与强制约束

  1. 后台播放的“通行证”
    鸿蒙系统对后台音视频播放有严格的约束。如果应用需要后台播放(如音乐、听书、长视频),必须同时接入 AVSession 和 BackgroundTasks Kit(申请 AUDIO_PLAYBACK 长时任务)。
  2. 系统级管控
    若应用有后台播放需求但未接入 AVSession,当应用退到后台或锁屏时,系统会强制暂停音频播放或静音通话声音,以防止恶意后台发声。
  3. 统一管控与解耦
    AVSession 在应用与控制器(如播控中心、语音助手)之间建立了一个标准化的“会话(Session)”。应用只需向系统注册播放状态和控制命令,无需单独适配各种外设,即可实现一次开发、处处可控。
// BackgroundTaskManager.ets
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { wantAgent } from '@kit.AbilityKit';

export class BackgroundTaskHelper {
    // 1. 在播放开始前申请 AUDIO_PLAYBACK 长时任务
    public static async startAudioPlayback(context: Context) {
        try {
            let wantAgentObj = await wantAgent.getWantAgent(context, {
                wants: [{ bundleName: context.abilityInfo.bundleName, abilityName: context.abilityInfo.name }],
                operationType: wantAgent.OperationType.START_ABILITIES,
                requestCode: 0,
                wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
            });
            
            await backgroundTaskManager.startBackgroundRunning(
                context, 
                'AudioPlaybackTask', 
                wantAgentObj,
                backgroundTaskManager.BackgroundMode.AUDIO_PLAYBACK
            );
        } catch (err) {
            console.error('Failed to start background running:', err);
        }
    }

    // 2. 播放结束或暂停时释放长时任务
    public static async stopAudioPlayback(context: Context) {
        try {
            await backgroundTaskManager.stopBackgroundRunning(context, 'AudioPlaybackTask');
        } catch (err) {
            console.error('Failed to stop background running:', err);
        }
    }
}

二、 接入流程与状态同步

  1. 创建并激活会话
    根据业务场景选择正确的会话类型(如 'audio' 对应音乐,'video' 对应视频,'voice_call' 对应通话),这决定了播控中心展示的按钮样式。创建后,必须调用 activate() 激活会话。
  2. 设置元数据(Metadata)
    通过 setAVMetadata 上报当前媒体的标题、歌手、专辑封面、时长等信息,这些信息会实时展示在锁屏界面和播控中心。
  3. 注册控制命令与状态同步
    应用需注册播放、暂停、上一首、下一首等回调,以响应外部控制。同时,当应用内部状态改变时,必须通过 setAVPlaybackState 及时同步给系统,确保播控中心的进度条和播放状态与 App 完全一致。
// AVSessionManager.ets
import { avSession } from '@kit.AVSessionKit';

export class AVSessionManager {
    private session: avSession.AVSession | null = null;

    // 严格遵循初始化顺序:创建 -> 设置元数据 -> 注册命令 -> 激活
    public async initSession(context: Context) {
        // 1. 创建并激活会话
        this.session = await avSession.createAVSession(context, 'MyMusicSession', 'audio');
        
        // 2. 设置元数据(Metadata)
        const metadata: avSession.AVMetadata = {
            title: '鸿蒙星河交响曲',
            artist: 'Harmony Band',
            mediaImage: 'https://example.com/cover.jpg',
            duration: 240000 // 毫秒
        };
        await this.session.setAVMetadata(metadata);

        // 3. 注册控制命令回调
        this.session.on('play', () => {
            console.info('Received play command');
            // 调用内部 AVPlayer 的 play() 方法
        });
        this.session.on('pause', () => {
            console.info('Received pause command');
            // 调用内部 AVPlayer 的 pause() 方法
        });
        this.session.on('playNext', () => {
            console.info('Received next track command');
        });

        // 4. 激活会话
        this.session.activate();
    }

    // 状态同步:当内部播放器状态改变时调用
    public async syncPlaybackState(isPlaying: boolean, elapsedTime: number) {
        if (!this.session) return;
        
        const playbackState: avSession.AVPlaybackState = {
            state: isPlaying 
                ? avSession.PlaybackState.PLAYBACK_STATE_PLAY 
                : avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
            position: {
                elapsedTime: elapsedTime,
                updateTime: new Date().getTime()
            }
        };
        await this.session.setAVPlaybackState(playbackState);
    }
}

三、 性能优化

  1. 会话生命周期管理
    AVSession 对象在后台播放期间必须保持存活。严禁将其定义在页面组件内部,否则页面销毁会导致 Session 释放,后台播放随即被系统掐断。应将其挂载到全局变量或单例中。
  2. 严格的初始化顺序
    接入流程必须遵循:设置元数据 -> 注册控制命令 -> 激活 Session。如果先激活再注册,可能会导致播控中心界面显示不全或按钮置灰。
  3. 配置防错
    在 module.json5 中声明后台任务模式时,backgroundModes 必须准确填写为 "audio",写成 "music" 或 "media" 会导致后台播放静默失效,且不会报错。
// GlobalMediaService.ets
import { Context } from '@kit.AbilityKit';
import { AVSessionManager } from './AVSessionManager';
import { BackgroundTaskHelper } from './BackgroundTaskManager';

// 核心避坑:严禁将 AVSession 定义在页面组件内部,必须挂载到全局单例
export class GlobalMediaService {
    private static instance: GlobalMediaService;
    public avSessionManager: AVSessionManager;
    private context: Context;

    private constructor(context: Context) {
        this.context = context;
        this.avSessionManager = new AVSessionManager();
    }

    public static getInstance(context: Context): GlobalMediaService {
        if (!this.instance) {
            this.instance = new GlobalMediaService(context);
        }
        return this.instance;
    }

    // 会话销毁与资源释放规范
    public async destroySession() {
        // 1. 取消长时任务
        await BackgroundTaskHelper.stopAudioPlayback(this.context);
        // 2. 销毁 AVSession
        if (this.avSessionManager) {
            // 内部需调用 session.off() 取消所有监听,然后 session.destroy()
            await this.avSessionManager.destroy(); 
        }
    }
}

四、 多模设备交互与外设按键适配

AVSession 的核心价值之一在于屏蔽底层硬件差异,让应用能够无缝响应各种外设的控制指令。

  1. 蓝牙耳机与有线耳机适配
    应用无需单独监听蓝牙或有线耳机的媒体按键事件。只要正确接入了 AVSession 并注册了相应的控制指令,系统会自动将耳机的按键事件(如播放/暂停、上下一首、佩戴检测等)转换为 AVSession 的控制指令发送给应用。
  2. 进度条拖拽控制
    对于支持进度调整的场景,应用需注册 seek 控制命令。当用户在锁屏界面或播控中心拖动进度条时,应用会收到目标时间戳的回调。此时建议先将播放状态上报为 BUFFERING(缓冲中),待应用内播放器完成 Seek 操作后,再同步更新真实的播放状态与进度。
  3. 循环模式与收藏状态同步
    播控中心支持展示循环模式和收藏状态。应用需在收到 setLoopMode 等指令后,更新内部播放器状态,并立即通过 setAVPlaybackState 将最新的 loopMode 或 isFavorite 状态上报给系统,确保 UI 表现一致。
// AVSessionControlHandler.ets
import { avSession } from '@kit.AVSessionKit';

export class AVSessionControlHandler {
    private session: avSession.AVSession | null = null;

    constructor(session: avSession.AVSession) {
        this.session = session;
        this.registerCommands();
    }

    private registerCommands() {
        if (!this.session) return;

        // 1. 进度条拖拽控制 (Seek)
        this.session.on('seek', async (position: number) => {
            console.info(`Received seek command to position: ${position}ms`);
            // 建议先上报缓冲状态,防止播控中心进度条跳动
            await this.session?.setAVPlaybackState({
                state: avSession.PlaybackState.PLAYBACK_STATE_BUFFERING
            });
            
            // TODO: 调用内部播放器执行 seek 操作
            // await internalPlayer.seek(position);
            
            // 操作完成后,同步真实播放状态
            await this.updatePlaybackState(true, position);
        });

        // 2. 循环模式切换
        this.session.on('setLoopMode', async (mode: avSession.LoopMode) => {
            console.info(`Received setLoopMode: ${mode}`);
            // TODO: 更新内部播放器的循环模式
            // internalPlayer.setLoopMode(mode);
            
            // 立即同步状态给系统播控中心
            await this.session?.setAVPlaybackState({
                loopMode: mode
            });
        });

        // 3. 收藏状态同步
        this.session.on('toggleFavorite', async (isFavorite: boolean) => {
            console.info(`Received toggleFavorite: ${isFavorite}`);
            // TODO: 执行数据库收藏/取消收藏操作
            
            // 同步状态
            await this.session?.setAVPlaybackState({
                isFavorite: isFavorite
            });
        });
    }

    public async updatePlaybackState(isPlaying: boolean, elapsedTime: number) {
        if (!this.session) return;
        await this.session.setAVPlaybackState({
            state: isPlaying 
                ? avSession.PlaybackState.PLAYBACK_STATE_PLAY 
                : avSession.PlaybackState.PLAYBACK_STATE_PAUSE,
            position: {
                elapsedTime: elapsedTime,
                updateTime: new Date().getTime()
            }
        });
    }
}

五、进阶场景:投播与虚拟扩展屏

在鸿蒙生态中,音视频播放不仅限于本地设备,还涉及跨设备的协同体验。

  1. 投播长时任务检测
    当应用进行音视频投播(将本端音视频投至远端设备)时,即使应用退至后台,只要音视频播放或投屏业务其一正常运行,系统就不会终止 AUDIO_PLAYBACK 长时任务。
  2. 扩展屏设备监听
    通过 AVSession 提供的 getAllCastDisplays() 接口,应用可以获取当前系统中所有支持扩展屏投播的显示设备。结合 castDisplayChange 事件监听,应用能在检测到远端设备连接或断开时,动态启动或停止扩展屏上的 UIAbility 绘制,实现双屏联动。
// CastDisplayManager.ets
import { avSession } from '@kit.AVSessionKit';
import { common } from '@kit.AbilityKit';

export class CastDisplayManager {
    private session: avSession.AVSession | null = null;

    public async initCastDisplay(context: common.UIAbilityContext) {
        // 1. 创建视频类型的 AVSession
        this.session = await avSession.createAVSession(context, 'CastDisplay', 'video');
        
        // 2. 监听扩展屏设备连接/断开状态变化
        this.session.on('castDisplayChange', (info: avSession.CastDisplayInfo) => {
            if (info.state === avSession.CastDisplayState.STATE_ON) {
                console.info('扩展屏已连接,启动大屏 UIAbility');
                // 启动扩展屏的 UIAbility,传入 displayId
                context.startAbility({
                    bundleName: context.abilityInfo.bundleName,
                    abilityName: 'CastScreenAbility'
                }, {
                    displayId: info.id
                });
            } else if (info.state === avSession.CastDisplayState.STATE_OFF) {
                console.info('扩展屏已断开,停止大屏业务');
                // TODO: 停止扩展屏业务或回退到本地播放
            }
        });

        // 3. 主动获取当前可用的扩展屏设备
        const displays = await this.session.getAllCastDisplays();
        if (displays.length > 0) {
            console.info(`发现 ${displays.length} 个可用扩展屏`);
        }
    }
}

六、 资源释放与异常处理规范

健壮的生命周期管理是防止内存泄漏和系统异常的关键。

  1. 会话销毁规范
    当应用完全退出或不再有任何音视频播放业务时,必须严格按照顺序释放资源:先调用 deactivate() 停用会话,再调用 destroy() 彻底销毁 Session 对象,同时取消已申请的后台长时任务。
  2. 暂停事件与任务解绑
    应用应设置监听音频暂停事件 on('pause')。如果收到系统下发的暂停指令且后续不再需要继续播放,推荐主动取消已申请的 AUDIO_PLAYBACK 长时任务,以节省系统资源。
  3. 防冻结机制
    即使申请了长时任务,如果设备长时间没有有效的音频流输出,应用仍可能被系统冻结。因此,确保 AVPlayer 的播放状态与 AVSession 的上报状态保持绝对同步,是维持后台保活的核心前提。
// AVSessionCleanup.ets
import { avSession } from '@kit.AVSessionKit';
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';

export class AVSessionCleanup {
    // 严格的资源释放规范
    public static async safeDestroy(
        session: avSession.AVSession, 
        context: Context, 
        taskId: string
    ) {
        try {
            // 1. 取消所有已注册的控制命令监听器(防止内存泄漏)
            session.off('play');
            session.off('pause');
            session.off('seek');
            session.off('setLoopMode');
            session.off('toggleFavorite');
            session.off('castDisplayChange');

            // 2. 停用并销毁会话
            await session.deactivate();
            await session.destroy();

            // 3. 释放后台长时任务
            await backgroundTaskManager.stopBackgroundRunning(context, taskId);
            
            console.info('AVSession 资源安全释放完毕');
        } catch (err) {
            console.error('AVSession 资源释放失败:', err);
        }
    }
}

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐