鸿蒙跨设备音乐播放器:基于分布式能力的多设备同步方案 原创

进修的泡芙
发布于 2025-6-18 22:06
浏览
0收藏

鸿蒙跨设备音乐播放器:基于分布式能力的多设备同步方案

本文将详细介绍如何使用HarmonyOS的AVPlayer和分布式能力,构建一个支持多设备同步的简易音乐播放器。该播放器能够在不同设备间同步播放状态、播放列表和播放进度。

技术架构
媒体播放层:使用AVPlayer实现本地音频播放

播放控制层:管理播放状态和用户交互

分布式同步层:通过分布式数据管理实现多设备同步

UI展示层:音乐播放界面和控制面板

完整代码实现
音乐数据模型定义

// model/MusicItem.ts
export class MusicItem {
id: string = ‘’; // 音乐唯一ID
title: string = ‘’; // 音乐标题
artist: string = ‘’; // 艺术家
duration: number = 0; // 时长(毫秒)
cover: Resource = $r(‘app.media.default_cover’); // 封面图
filePath: string = ‘’; // 文件路径

constructor(data?: Partial<MusicItem>) {
if (data) {
Object.assign(this, data);
if (!this.id) {
this.id = this.generateId();
}

private generateId(): string {

return 'music-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);

// 格式化时长显示

get formattedDuration(): string {
const minutes = Math.floor(this.duration / 60000);
const seconds = Math.floor((this.duration % 60000) / 1000);
return {minutes}:{seconds.toString().padStart(2, ‘0’)};
}

播放状态模型定义

// model/PlaybackState.ts
export class PlaybackState {
currentMusicId: string = ‘’; // 当前播放音乐ID
playlist: MusicItem[] = []; // 播放列表
isPlaying: boolean = false; // 是否正在播放
currentPosition: number = 0; // 当前播放位置(毫秒)
deviceId: string = ‘’; // 最后控制的设备ID
lastUpdate: number = 0; // 最后更新时间戳

constructor(data?: Partial<PlaybackState>) {
if (data) {
Object.assign(this, data);
if (!this.lastUpdate) {
this.lastUpdate = Date.now();
}

}

音乐播放服务实现

// service/MusicPlayerService.ts
import media from ‘@ohos.multimedia.media’;
import { MusicItem, PlaybackState } from ‘…/model’;

export class MusicPlayerService {
private avPlayer: media.AVPlayer;
private currentState: PlaybackState = new PlaybackState();
private updateCallback: (state: PlaybackState) => void = () => {};
private positionUpdateInterval: number = 0;

constructor() {
this.avPlayer = new media.AVPlayer();
this.setupPlayerListeners();
// 初始化播放器监听

private setupPlayerListeners() {
this.avPlayer.on(‘stateChange’, (state: string) => {
if (state === ‘playing’) {
this.currentState.isPlaying = true;
this.startPositionUpdates();
else if (state === ‘paused’) {

    this.currentState.isPlaying = false;
    this.stopPositionUpdates();

else if (state === ‘completed’) {

    this.next();

this.notifyStateChange();

});

// 开始更新播放位置

private startPositionUpdates() {
this.stopPositionUpdates();
this.positionUpdateInterval = setInterval(() => {
this.avPlayer.getCurrentTime().then((time: number) => {
this.currentState.currentPosition = time;
this.notifyStateChange();
});
}, 1000);
// 停止更新播放位置

private stopPositionUpdates() {
if (this.positionUpdateInterval) {
clearInterval(this.positionUpdateInterval);
this.positionUpdateInterval = 0;
}

// 通知状态变化
private notifyStateChange() {
this.currentState.lastUpdate = Date.now();
this.updateCallback(this.currentState);
// 设置状态更新回调

setUpdateCallback(callback: (state: PlaybackState) => void) {
this.updateCallback = callback;
// 设置播放列表

setPlaylist(playlist: MusicItem[], currentId?: string) {
this.currentState.playlist = playlist;
this.currentState.currentMusicId = currentId |playlist[0]?.id
| ‘’;
this.notifyStateChange();
// 播放指定音乐

async play(musicId: string) {
const music = this.currentState.playlist.find(item => item.id === musicId);
if (!music) return;

this.currentState.currentMusicId = musicId;
this.currentState.currentPosition = 0;

await this.avPlayer.reset();
await this.avPlayer.setSource(music.filePath);
await this.avPlayer.prepare();
await this.avPlayer.play();

// 播放/暂停

async togglePlay() {
if (this.currentState.isPlaying) {
await this.avPlayer.pause();
else {

  if (this.currentState.currentMusicId) {
    await this.avPlayer.play();

else if (this.currentState.playlist.length > 0) {

    await this.play(this.currentState.playlist[0].id);

}

// 下一首

async next() {
const currentIndex = this.currentState.playlist.findIndex(
item => item.id === this.currentState.currentMusicId
);

if (currentIndex >= 0 && currentIndex < this.currentState.playlist.length - 1) {
  await this.play(this.currentState.playlist[currentIndex + 1].id);

}

// 上一首
async prev() {
const currentIndex = this.currentState.playlist.findIndex(
item => item.id === this.currentState.currentMusicId
);

if (currentIndex > 0) {
  await this.play(this.currentState.playlist[currentIndex - 1].id);

}

// 跳转到指定位置
async seekTo(position: number) {
await this.avPlayer.seek(position);
this.currentState.currentPosition = position;
this.notifyStateChange();
// 获取当前状态

getCurrentState(): PlaybackState {
return this.currentState;
// 释放资源

cleanup() {
this.stopPositionUpdates();
this.avPlayer.release();
}

分布式音乐同步服务

// service/MusicSyncService.ts
import distributedData from ‘@ohos.data.distributedData’;
import deviceInfo from ‘@ohos.deviceInfo’;
import { PlaybackState } from ‘…/model/PlaybackState’;

const STORE_ID = ‘music_sync_store’;
const PLAYBACK_KEY = ‘current_playback’;

export class MusicSyncService {
private kvManager: distributedData.KVManager;
private kvStore: distributedData.SingleKVStore;
private localDeviceId: string = deviceInfo.deviceId;

// 初始化分布式数据存储
async initialize() {
const config = {
bundleName: ‘com.example.musicplayer’,
userInfo: {
userId: ‘music_user’,
userType: distributedData.UserType.SAME_USER_ID
};

this.kvManager = distributedData.createKVManager(config);
const options = {
  createIfMissing: true,
  encrypt: false,
  backup: false,
  autoSync: true,
  kvStoreType: distributedData.KVStoreType.SINGLE_VERSION
};

this.kvStore = await this.kvManager.getKVStore(STORE_ID, options);

// 订阅数据变更
this.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
  this.handleDataChange(data);
});

// 处理数据变更

private handleDataChange(data: distributedData.ChangeNotification) {
if (data.insertEntries.length > 0 && data.insertEntries[0].key === PLAYBACK_KEY) {
const newState = JSON.parse(data.insertEntries[0].value.value);
AppStorage.setOrCreate(‘playbackState’, new PlaybackState(newState));
}

// 同步播放状态
async syncPlaybackState(state: PlaybackState) {
state.deviceId = this.localDeviceId;
await this.kvStore.put(PLAYBACK_KEY, JSON.stringify(state));
// 获取当前设备ID

getLocalDeviceId(): string {
return this.localDeviceId;
}

音乐播放器页面实现

// pages/MusicPlayerPage.ets
import { MusicPlayerService } from ‘…/service/MusicPlayerService’;
import { MusicSyncService } from ‘…/service/MusicSyncService’;
import { MusicItem } from ‘…/model/MusicItem’;

@Entry
@Component
struct MusicPlayerPage {
private playerService: MusicPlayerService = new MusicPlayerService();
private syncService: MusicSyncService = new MusicSyncService();
@StorageLink(‘playbackState’) playbackState: PlaybackState = new PlaybackState();
@State localMusicList: MusicItem[] = [];

async aboutToAppear() {
await this.syncService.initialize();

// 设置状态更新回调
this.playerService.setUpdateCallback((state) => {
  this.playbackState = state;
  this.syncService.syncPlaybackState(state);
});

// 加载本地音乐
await this.loadLocalMusic();

onPageHide() {

this.playerService.cleanup();

build() {

Column() {
  // 音乐封面
  Image(this.getCurrentMusic()?.cover || $r('app.media.default_cover'))
    .width(300)
    .height(300)
    .margin({ top: 40, bottom: 20 })
    .borderRadius(150)
  
  // 音乐信息
  Column() {
    Text(this.getCurrentMusic()?.title || '无歌曲')
      .fontSize(22)
      .fontWeight(FontWeight.Bold)
      .margin({ bottom: 8 })
    
    Text(this.getCurrentMusic()?.artist || '未知艺术家')
      .fontSize(16)
      .fontColor('#666666')

.margin({ bottom: 30 })

  // 播放进度条
  Slider({
    value: this.playbackState.currentPosition,
    min: 0,
    max: this.getCurrentMusic()?.duration || 100,
    step: 1000,
    style: SliderStyle.OutSet
  })
  .width('80%')
  .onChange((value: number) => {
    this.playerService.seekTo(value);
  })
  
  // 时间显示
  Row() {
    Text(this.formatTime(this.playbackState.currentPosition))
      .fontSize(14)
      .fontColor('#666666')
      .layoutWeight(1)
    
    Text(this.getCurrentMusic()?.formattedDuration || '0:00')
      .fontSize(14)
      .fontColor('#666666')

.width(‘80%’)

  .margin({ bottom: 30 })
  
  // 控制按钮
  Row() {
    Button('')
      .icon($r('app.media.ic_skip_previous'))
      .onClick(() => {
        this.playerService.prev();
      })
    
    Button('')
      .icon(this.playbackState.isPlaying ? r('app.media.ic_pause') : r('app.media.ic_play'))
      .margin({ left: 20, right: 20 })
      .onClick(() => {
        this.playerService.togglePlay();
      })
    
    Button('')
      .icon($r('app.media.ic_skip_next'))
      .onClick(() => {
        this.playerService.next();
      })

.margin({ bottom: 30 })

  // 同步状态
  Row() {
    Circle()
      .width(10)
      .height(10)
      .fill(this.playbackState.deviceId === this.syncService.getLocalDeviceId() ? '#4CAF50' : '#F44336')
      .margin({ right: 5 })
    
    Text(this.playbackState.deviceId === this.syncService.getLocalDeviceId() ? '本设备控制' : '其他设备控制')
      .fontSize(14)
      .fontColor('#666666')

}

.width('100%')
.height('100%')
.alignItems(HorizontalAlign.Center)

// 获取当前播放的音乐

private getCurrentMusic(): MusicItem | undefined {
return this.playbackState.playlist.find(
item => item.id === this.playbackState.currentMusicId
);
// 格式化时间显示

private formatTime(ms: number): string {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return {minutes}:{remainingSeconds.toString().padStart(2, ‘0’)};
// 加载本地音乐

private async loadLocalMusic() {
try {
// 模拟加载本地音乐文件
const mockMusic: MusicItem[] = [
title: ‘示例音乐1’,

      artist: '示例艺术家',
      duration: 180000,
      filePath: 'file://media/music1.mp3',
      cover: $r('app.media.music_cover1')
    },

title: ‘示例音乐2’,

      artist: '示例艺术家',
      duration: 210000,
      filePath: 'file://media/music2.mp3',
      cover: $r('app.media.music_cover2')

];

  this.localMusicList = mockMusic;
  this.playerService.setPlaylist(mockMusic);

catch (err) {

  console.error('加载音乐失败:', err);

}

实现原理详解
播放同步机制:

主设备控制播放状态并同步到分布式数据库

从设备接收状态更新并同步本地播放器

显示控制来源设备信息
播放控制功能:

播放/暂停、上一首/下一首

进度条跳转

本地音乐文件播放
状态恢复策略:

页面切换时保存播放状态

重新进入时恢复播放位置

播放完成自动下一首

扩展功能建议
播放列表管理:

  // 添加播放列表管理功能

async addToPlaylist(music: MusicItem) {
const newList = […this.playbackState.playlist, music];
this.playerService.setPlaylist(newList);

网络音乐支持:

  // 添加网络音乐播放支持

async playOnlineMusic(url: string) {
await this.avPlayer.reset();
await this.avPlayer.setSource(url);
await this.avPlayer.prepare();
await this.avPlayer.play();

歌词显示功能:

  // 解析和显示歌词

async loadLyrics(musicId: string) {
const lyrics = await lyricsApi.getLyrics(musicId);
this.displayLyrics(lyrics, this.playbackState.currentPosition);

总结

本文详细介绍了如何利用HarmonyOS的AVPlayer和分布式能力构建一个多设备同步的音乐播放器。通过将播放状态存储在分布式数据库中,实现了播放控制、播放列表和播放进度的跨设备同步,为用户提供了无缝的音乐播放体验。

这种架构不仅适用于音乐播放器,也可以扩展到播客、有声书等音频播放场景。合理利用鸿蒙的分布式能力,可以大大增强多设备协同应用的实用性和用户体验。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报
回复
    相关推荐