🎯 一、AVSession 概述与核心价值

AVSession(媒体会话)是HarmonyOS分布式媒体控制的核心框架。它允许应用程序将本地播放的媒体信息和控制能力暴露给系统,使得其他设备(如手机、平板、智慧屏)可以发现、查看和控制该媒体播放,从而实现真正的跨设备无缝媒体体验

核心能力包括:

  • 元数据展示 (Metadata): 将媒体信息(标题、歌手、专辑、封面等)同步到控制中心和其他设备。
  • 播放状态同步 (Playback State): 同步播放、暂停、进度、速度等状态。
  • 控制命令分发 (Control Command): 接收并处理来自系统控制中心或其他设备的控制指令(播放、暂停、下一首等)。
  • 跨设备接力 (Cross-Device Continuity): 用户可以将媒体播放从一个设备无缝转移到另一个设备。

⚙️ 二、前期准备与配置

  1. 模块配置 (module.json5): 申请必要的权限并声明后台运行能力。

    {
      "module": {
        "requestPermissions": [
          {
            "name": "ohos.permission.MEDIA_CONTROL" // 媒体控制权限
          },
          {
            "name": "ohos.permission.DISTRIBUTED_DATASYNC" // 分布式数据同步权限
          }
        ],
        "abilities": [
          {
            "name": "EntryAbility",
            "backgroundModes": [
              "audioPlayback", // 声明音频播放后台任务类型
              "dataTransfer"
            ]
          }
        ]
      }
    }
    
  2. 导入模块: 在ArkTS文件中导入必要的模块。

    // AVSessionManager.ets
    import avSession from '@ohos.multimedia.avsession';
    import image from '@ohos.multimedia.image';
    import common from '@ohos.app.ability.common';
    import { BusinessError } from '@ohos.base';
    

🧩 三、创建与管理AVSession

AVSession的管理通常封装在一个单独的服务类中。

export class AVSessionService {
  private session: avSession.AVSession | null = null;
  private context: common.Context | null = null;

  // 初始化AVSession服务
  public async initAVSession(context: common.Context, sessionTag: string): Promise<void> {
    this.context = context;
    try {
      // 1. 创建AVSession,类型为音频(audio)或视频(video)
      this.session = await avSession.createAVSession(context, sessionTag, 'audio'); // 或 'video'
      console.info(`AVSession created successfully. Session ID: ${this.session.sessionId}`);

      // 2. 设置会话事件监听器(关键步骤)
      await this.setSessionEventListener();
    } catch (error) {
      console.error(`Failed to create AVSession. Error code: ${(error as BusinessError).code}, message: ${(error as BusinessError).message}`);
    }
  }

  // 激活AVSession,通常在元数据和监听器设置完成后调用
  public async activateSession(): Promise<void> {
    if (this.session) {
      try {
        await this.session.activate();
        console.info('AVSession activated.');
      } catch (error) {
        console.error(`Failed to activate AVSession. Error code: ${(error as BusinessError).code}, message: ${(error as BusinessError).message}`);
      }
    }
  }

  // 销毁AVSession,在退出播放或组件销毁时调用
  public async destroySession(): Promise<void> {
    if (this.session) {
      try {
        await this.session.destroy();
        this.session = null;
        console.info('AVSession destroyed.');
      } catch (error) {
        console.error(`Failed to destroy AVSession. Error code: ${(error as BusinessError).code}, message: ${(error as BusinessError).message}`);
      }
    }
  }

  public getSession(): avSession.AVSession | null {
    return this.session;
  }
}
  • 会话类型: 'audio''video'在系统控中心会呈现不同的控制模板(如视频会有进度条和快进快退)。
  • 生命周期管理: 务必在合适的时机(如应用切换到后台或退出时)销毁会话,释放资源。

📝 四、设置媒体元数据 (Metadata)

元数据描述了媒体内容本身,用于在控制中心、锁屏界面或其他设备上展示。

// 在 AVSessionService 类中添加方法
public async setMediaMetadata(metadata: avSession.AVMetadata): Promise<void> {
  if (!this.session) {
    console.error('AVSession is not initialized. Call initAVSession first.');
    return;
  }
  try {
    await this.session.setAVMetadata(metadata);
    console.info('Media metadata set successfully.');
  } catch (error) {
    console.error(`Failed to set media metadata. Error code: ${(error as BusinessError).code}, message: ${(error as BusinessError).message}`);
  }
}

// 示例:设置一首歌的元数据
const sampleMetadata: avSession.AVMetadata = {
  assetId: 'music_123456', // 媒体唯一标识,必选
  title: 'HarmonyOS Theme Song', // 标题
  artist: 'HarmonyOS Team', // 艺术家
  album: 'OpenHarmony Soundtrack', // 专辑
  writer: 'Composer', // 词曲作者
  composer: 'Composer',
  duration: 227000, // 媒体总时长,单位毫秒
  mediaImage: await image.createPixelMapFromFile('common/media/album_cover.jpg'), // 专辑封面,类型为image.PixelMap
  lyric: 'path/to/lyric.lrc', // LRC歌词文件路径
  // 其他可选字段...
};
// await avSessionService.setMediaMetadata(sampleMetadata);
  • assetId: 是媒体的唯一标识,常用于匹配媒体库中的资源。
  • mediaImage: 需要将图片资源转换为 image.PixelMap类型。
  • lyric: 如果提供LRC格式的歌词路径,系统播控中心可能会展示并同步滚动歌词。

🎮 五、处理控制命令 (Control Commands)

这是AVSession的核心交互部分。应用程序需要注册并响应来自系统或其他设备的控制命令。

// 在 AVSessionService 类中添加方法
private async setSessionEventListener(): Promise<void> {
  if (!this.session) {
    return;
  }

  // 注册播放命令监听
  this.session.on('play', () => {
    console.info('Received play command from controller.');
    // 调用本地播放器的播放方法
    // myLocalPlayer.play();
    // 随后必须更新AVSession的播放状态(见下一节)
  });

  // 注册暂停命令监听
  this.session.on('pause', () => {
    console.info('Received pause command from controller.');
    // myLocalPlayer.pause();
  });

  // 注册跳转命令监听
  this.session.on('seek', (time: number) => {
    console.info(`Received seek command to position: ${time} ms.`);
    // myLocalPlayer.seek(time);
  });

  // 注册下一首命令监听
  this.session.on('playNext', () => {
    console.info('Received playNext command.');
    // myLocalPlayer.playNext();
  });

  // 注册上一首命令监听
  this.session.on('playPrevious', () => {
    console.info('Received playPrevious command.');
    // myLocalPlayer.playPrevious();
  });

  // 可以根据需要注册更多命令,如 'stop', 'setSpeed', 'setLoopMode' 等
  this.session.on('stop', () => {
    console.info('Received stop command.');
    // myLocalPlayer.stop();
    // await this.destroySession(); // 停止后通常可以销毁会话
  });

  console.info('AVSession event listeners registered.');
}
  • 当用户在控制中心或另一台设备上点击“播放”按钮时,'play'事件会被触发,你需要在回调函数中驱动你自己的本地播放器执行相应的操作。

📊 六、同步播放状态 (Playback State)

在处理完控制命令或本地播放状态发生变化后,必须将最新的状态同步回AVSession框架,以便系统和其他设备更新显示。

// 在 AVSessionService 类中添加方法
public async updatePlaybackState(state: avSession.AVPlaybackState): Promise<void> {
  if (!this.session) {
    console.error('AVSession is not initialized. Call initAVSession first.');
    return;
  }
  try {
    await this.session.setAVPlaybackState(state);
    console.info('Playback state updated successfully.');
  } catch (error) {
    console.error(`Failed to update playback state. Error code: ${(error as BusinessError).code}, message: ${(error as BusinessError).message}`);
  }
}

// 示例:在播放开始时调用
const playbackState: avSession.AVPlaybackState = {
  state: avSession.PlaybackState.PLAYBACK_STATE_PLAYING, // 播放状态
  speed: 1.0, // 播放速度
  position: { // 播放位置
    elapsedTime: 0, // 已播放时间(毫秒)
    updateTime: new Date().getTime() // 状态更新时间戳
  },
  loopMode: avSession.LoopMode.LOOP_MODE_NONE, // 循环模式
  isFavorite: false // 是否收藏
};
// await avSessionService.updatePlaybackState(playbackState);

// 示例:在暂停时调用
const pausedState: avSession.AVPlaybackState = {
  state: avSession.PlaybackState.PLAYBACK_STATE_PAUSED,
  speed: 1.0,
  position: {
    elapsedTime: 15000, // 当前播放到的位置
    updateTime: new Date().getTime()
  },
  loopMode: avSession.LoopMode.LOOP_MODE_NONE,
  isFavorite: false
};
// await avSessionService.updatePlaybackState(pausedState);
  • 状态一致性: 务必保证通过 setAVPlaybackState同步的状态与本地播放器的真实状态一致。
  • 实时更新: 播放进度 (position) 需要定期更新(例如每秒更新一次),以便进度条能够平滑拖动。但注意频率,不必过于频繁。

🌐 七、实现跨设备控制

AVSession通过分布式能力自动将会话信息同步到同一帐号下的可信设备组网中。要在设备B上控制设备A的播放,通常需要创建AVSessionController

// 在控制端设备(Device B)的代码中
import deviceManager from '@ohos.distributedDeviceManager';

export class RemoteControlService {
  private controller: avSession.AVSessionController | null = null;

  // 发现并连接到远程设备的AVSession
  public async connectToRemoteSession(remoteDeviceId: string, remoteSessionId: string): Promise<void> {
    try {
      // 1. 创建针对远程会话的控制器
      this.controller = await avSession.createController(remoteDeviceId, remoteSessionId);
      console.info('AVSessionController created successfully.');

      // 2. (可选) 获取远程会话的元数据和状态
      const metadata = await this.controller.getAVMetadata();
      const playbackState = await this.controller.getAVPlaybackState();
      console.info(`Remote is playing: ${metadata.title} by ${metadata.artist}`);

    } catch (error) {
      console.error(`Failed to create remote controller. Error code: ${(error as BusinessError).code}, message: ${(error as BusinessError).message}`);
    }
  }

  // 向远程设备发送控制命令
  public async sendPlayCommand(): Promise<void> {
    if (this.controller) {
      try {
        await this.controller.sendControlCommand({
          command: 'play',
          args: { position: 0 } // 可以附带参数,如从特定位置播放
        });
        console.info('Play command sent to remote device.');
      } catch (error) {
        console.error(`Failed to send play command. Error code: ${(error as BusinessError).code}, message: ${(error as BusinessError).message}`);
      }
    }
  }
  // 同样可以实现 sendPauseCommand, sendSeekCommand 等方法
}

// 获取可信设备列表
const devManager = deviceManager.createDeviceManager(await getContext(this).bundleName);
const trustedDevices = await devManager.getTrustedDeviceListSync();
  • 设备发现和连接依赖于HarmonyOS的分布式软总线能力,确保设备在同一个局域网内并登录了相同的华为帐号。

🖥️ 八、集成示例:简单的音乐播放器组件

下面是一个简单的UI组件,集成了本地播放器和AVSession功能。

// SimpleMusicPlayer.ets
import { AVSessionService } from '../services/AVSessionService';
import { LocalMusicPlayer } from '../services/LocalMusicPlayer'; // 假设的本地播放器

@Entry
@Component
struct SimpleMusicPlayer {
  private avSessionService: AVSessionService = new AVSessionService();
  private localPlayer: LocalMusicPlayer = new LocalMusicPlayer();
  @State currentSong: Song | null = null; // 假设的Song数据类型
  @State isPlaying: boolean = false;
  @State currentPos: number = 0;

  async aboutToAppear() {
    // 初始化AVSession
    await this.avSessionService.initAVSession(getContext(this), 'MyMusicPlayer');
    // 设置监听器后激活会话
    await this.avSessionService.activateSession();

    // 假设加载了一首歌
    this.currentSong = await this.loadCurrentSong();
    const metadata: avSession.AVMetadata = {
      assetId: this.currentSong.id,
      title: this.currentSong.title,
      artist: this.currentSong.artist,
      album: this.currentSong.album,
      duration: this.currentSong.duration,
      // ... 设置封面等
    };
    await this.avSessionService.setMediaMetadata(metadata);
  }

  aboutToDisappear() {
    // 销毁AVSession,释放资源
    this.avSessionService.destroySession();
  }

  build() {
    Column() {
      // 本地播放器UI控件
      Text(this.currentSong?.title || 'No Song').fontSize(20)
      Button(this.isPlaying ? 'Pause' : 'Play')
        .onClick(async () => {
          if (this.isPlaying) {
            await this.localPlayer.pause();
            await this.avSessionService.updatePlaybackState({
              state: avSession.PlaybackState.PLAYBACK_STATE_PAUSED,
              speed: 1.0,
              position: { elapsedTime: this.currentPos, updateTime: Date.now() }
            });
          } else {
            await this.localPlayer.play();
            await this.avSessionService.updatePlaybackState({
              state: avSession.PlaybackState.PLAYBACK_STATE_PLAYING,
              speed: 1.0,
              position: { elapsedTime: this.currentPos, updateTime: Date.now() }
            });
          }
          this.isPlaying = !this.isPlaying;
        })
      Slider({ value: this.currentPos, min: 0, max: this.currentSong?.duration || 100 })
        .onChange(async (value) => {
          await this.localPlayer.seek(value);
          this.currentPos = value;
          await this.avSessionService.updatePlaybackState({
            state: this.isPlaying ? avSession.PlaybackState.PLAYBACK_STATE_PLAYING : avSession.PlaybackState.PLAYBACK_STATE_PAUSED,
            speed: 1.0,
            position: { elapsedTime: value, updateTime: Date.now() }
          });
        })
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

💡 九、实战技巧与注意事项

  1. 错误处理: 对所有AVSession API调用进行 try-catch,网络和设备连接异常是分布式操作的常见问题。
  2. 后台播放: 如果需要在应用退到后台后继续播放,除了AVSession,还需使用 BackgroundTaskManager申请 AUDIO_PLAYBACK 后台任务权限,并在 module.json5中声明 audioPlayback背景模式。
  3. 状态同步一致性: 确保本地播放器状态、AVSession状态和UI状态三者同步。避免因网络延迟等原因导致的状态不一致。
  4. 性能与资源: 定期更新播放进度时,注意优化频率,避免不必要的性能开销。在会话结束时务必销毁并释放资源。
  5. 兼容性检查: 在发起跨设备控制前,检查目标设备是否支持AVSession能力。

通过掌握AVSession,你可以为你的HarmonyOS媒体应用赋予强大的跨设备协同能力,为用户提供无缝衔接的沉浸式媒体体验。

需要参加鸿蒙认证的请点击 鸿蒙认证链接

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐