
鸿蒙跨设备音乐播放器:基于分布式能力的多设备同步方案 原创
鸿蒙跨设备音乐播放器:基于分布式能力的多设备同步方案
本文将详细介绍如何使用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和分布式能力构建一个多设备同步的音乐播放器。通过将播放状态存储在分布式数据库中,实现了播放控制、播放列表和播放进度的跨设备同步,为用户提供了无缝的音乐播放体验。
这种架构不仅适用于音乐播放器,也可以扩展到播客、有声书等音频播放场景。合理利用鸿蒙的分布式能力,可以大大增强多设备协同应用的实用性和用户体验。
