当前位置: 首页 > article >正文

鸿蒙应用开发实战:手把手教你封装一个可复用的音乐播放器管理类(ArkTS版)

鸿蒙应用开发实战构建高可复用的音乐播放器管理类ArkTS版在鸿蒙应用开发中音频播放功能是许多应用的核心需求。本文将深入探讨如何设计一个健壮、可复用的音乐播放器管理类采用ArkTS语言实现帮助开发者提升代码组织能力和模块化思维。1. 音乐播放器架构设计一个优秀的音乐播放器管理类需要考虑以下几个核心要素播放控制播放、暂停、停止等基本功能状态管理跟踪当前播放状态播放中、暂停、停止等资源管理音频资源的加载和释放事件通知播放状态变化、播放进度等事件的监听跨页面共享在多页面间共享播放状态和控制我们采用单例模式设计MusicPlayerManager类确保全局唯一实例便于跨页面访问和控制。import media from ohos.multimedia.media export interface SongItem { id: string title: string artist: string album: string coverUrl: string audioUrl: string duration: number } export class MusicPlayerManager { private static instance: MusicPlayerManager private avPlayer: media.AVPlayer private currentSong: SongItem | null null private isPlaying: boolean false private playList: SongItem[] [] private constructor() { this.avPlayer media.createAVPlayer() } public static getInstance(): MusicPlayerManager { if (!MusicPlayerManager.instance) { MusicPlayerManager.instance new MusicPlayerManager() } return MusicPlayerManager.instance } }2. 核心功能实现2.1 播放控制播放控制是音乐播放器的核心功能我们需要实现播放、暂停、停止等基本操作并确保状态同步。public async play(song: SongItem): Promisevoid { if (this.currentSong?.id song.id this.isPlaying) { return } try { await this.avPlayer.reset() this.avPlayer.url song.audioUrl await this.avPlayer.prepare() await this.avPlayer.play() this.currentSong song this.isPlaying true this.notifyStateChange() } catch (error) { console.error(播放失败:, error) throw error } } public async pause(): Promisevoid { if (!this.isPlaying) return try { await this.avPlayer.pause() this.isPlaying false this.notifyStateChange() } catch (error) { console.error(暂停失败:, error) throw error } } public async stop(): Promisevoid { try { await this.avPlayer.stop() this.isPlaying false this.notifyStateChange() } catch (error) { console.error(停止失败:, error) throw error } }2.2 播放列表管理良好的播放列表管理能提升用户体验我们实现以下功能添加歌曲到播放列表从播放列表移除歌曲清空播放列表获取下一首/上一首歌曲public addToPlaylist(song: SongItem): void { if (!this.playList.some(item item.id song.id)) { this.playList.push(song) this.notifyPlaylistChange() } } public removeFromPlaylist(songId: string): void { this.playList this.playList.filter(item item.id ! songId) this.notifyPlaylistChange() } public clearPlaylist(): void { this.playList [] this.notifyPlaylistChange() } public getNextSong(): SongItem | null { if (!this.currentSong || this.playList.length 0) return null const currentIndex this.playList.findIndex(item item.id this.currentSong?.id) if (currentIndex -1 || currentIndex this.playList.length - 1) { return null } return this.playList[currentIndex 1] } public getPreviousSong(): SongItem | null { if (!this.currentSong || this.playList.length 0) return null const currentIndex this.playList.findIndex(item item.id this.currentSong?.id) if (currentIndex 0) { return null } return this.playList[currentIndex - 1] }3. 状态管理与事件监听3.1 播放状态管理我们需要跟踪播放器的各种状态变化包括播放/暂停状态当前播放进度缓冲状态播放错误状态private notifyStateChange(): void { const state { currentSong: this.currentSong, isPlaying: this.isPlaying, position: this.avPlayer.currentTime, duration: this.currentSong?.duration || 0 } // 使用Emitter通知状态变化 emitter.emit({eventId: player_state_change}, {data: JSON.stringify(state)}) } private setupPlayerListeners(): void { this.avPlayer.on(stateChange, (state) { console.log(播放器状态变化:, state) switch (state) { case prepared: this.isPlaying true break case paused: this.isPlaying false break case completed: this.isPlaying false this.playNext() break case error: console.error(播放器错误:, this.avPlayer.error) break } this.notifyStateChange() }) this.avPlayer.on(timeUpdate, () { this.notifyStateChange() }) this.avPlayer.on(bufferingUpdate, (info) { console.log(缓冲进度:, info) }) }3.2 跨页面通信在多页面应用中我们需要确保播放状态在所有页面间同步。使用鸿蒙的Emitter模块实现跨页面通信。import emitter from ohos.events.emitter // 在播放器管理类中 private notifyPlaylistChange(): void { emitter.emit({eventId: playlist_change}, { data: JSON.stringify({ playlist: this.playList, currentIndex: this.currentSong ? this.playList.findIndex(item item.id this.currentSong.id) : -1 }) }) } // 在页面组件中 Component struct PlayerControl { State currentSong: SongItem | null null State isPlaying: boolean false aboutToAppear() { emitter.on(player_state_change, (data) { const state JSON.parse(data.data) this.currentSong state.currentSong this.isPlaying state.isPlaying }) } // 组件实现... }4. 高级功能实现4.1 播放进度控制实现精确的播放进度控制包括跳转到指定位置和进度更新通知。public async seekTo(position: number): Promisevoid { if (position 0 || position (this.currentSong?.duration || 0)) { throw new Error(无效的播放位置) } try { await this.avPlayer.seek(position) this.notifyStateChange() } catch (error) { console.error(跳转失败:, error) throw error } } // 在播放器初始化时设置定时器定期通知进度 private setupProgressTimer(): void { setInterval(() { if (this.isPlaying) { this.notifyStateChange() } }, 1000) // 每秒更新一次 }4.2 播放模式支持支持多种播放模式提升用户体验顺序播放单曲循环随机播放列表循环export enum PlayMode { SEQUENCE sequence, LOOP loop, RANDOM random, SINGLE single } export class MusicPlayerManager { private playMode: PlayMode PlayMode.SEQUENCE public setPlayMode(mode: PlayMode): void { this.playMode mode this.notifyStateChange() } public getNextSong(): SongItem | null { if (!this.currentSong || this.playList.length 0) return null const currentIndex this.playList.findIndex(item item.id this.currentSong?.id) switch (this.playMode) { case PlayMode.SEQUENCE: if (currentIndex this.playList.length - 1) return null return this.playList[currentIndex 1] case PlayMode.LOOP: if (currentIndex this.playList.length - 1) return this.playList[0] return this.playList[currentIndex 1] case PlayMode.RANDOM: const availableIndices this.playList .map((_, index) index) .filter(index index ! currentIndex) if (availableIndices.length 0) return null const randomIndex Math.floor(Math.random() * availableIndices.length) return this.playList[availableIndices[randomIndex]] case PlayMode.SINGLE: return this.currentSong } } }4.3 音频焦点管理正确处理音频焦点避免与其他应用产生冲突。import audio from ohos.multimedia.audio export class MusicPlayerManager { private audioManager: audio.AudioManager private constructor() { this.audioManager audio.getAudioManager() this.setupAudioFocusListener() } private setupAudioFocusListener(): void { this.audioManager.on(audioFocusChange, (focusChange) { switch (focusChange) { case audio.AudioFocusState.LOSS: case audio.AudioFocusState.LOSS_TRANSIENT: this.pause() break case audio.AudioFocusState.GAIN: if (this.currentSong) { this.play(this.currentSong) } break } }) } private async requestAudioFocus(): Promiseboolean { try { const result await this.audioManager.requestAudioFocus({ streamType: audio.AudioStreamType.MUSIC, focusType: audio.AudioFocusType.GAIN }) return result audio.AudioFocusRequestResult.REQUEST_GRANTED } catch (error) { console.error(获取音频焦点失败:, error) return false } } public async play(song: SongItem): Promisevoid { const focusGranted await this.requestAudioFocus() if (!focusGranted) { throw new Error(无法获取音频焦点) } // 继续播放逻辑... } }5. 性能优化与错误处理5.1 资源管理与内存优化export class MusicPlayerManager { private isInitialized: boolean false public async initialize(): Promisevoid { if (this.isInitialized) return try { this.avPlayer await media.createAVPlayer() this.setupPlayerListeners() this.setupProgressTimer() this.isInitialized true } catch (error) { console.error(播放器初始化失败:, error) throw error } } public async release(): Promisevoid { try { if (this.avPlayer) { await this.avPlayer.release() this.avPlayer null this.isInitialized false } } catch (error) { console.error(释放播放器资源失败:, error) } } // 在页面aboutToDisappear时调用release }5.2 错误处理与重试机制export class MusicPlayerManager { private retryCount: number 0 private readonly MAX_RETRY_COUNT: number 3 private async playWithRetry(song: SongItem): Promisevoid { try { await this.play(song) this.retryCount 0 } catch (error) { console.error(播放失败(尝试次数: ${this.retryCount 1}):, error) if (this.retryCount this.MAX_RETRY_COUNT) { this.retryCount await new Promise(resolve setTimeout(resolve, 1000 * this.retryCount)) return this.playWithRetry(song) } else { this.retryCount 0 throw error } } } }5.3 网络音频流处理对于网络音频流需要特别处理缓冲和网络状态变化。export class MusicPlayerManager { private setupNetworkListener(): void { this.avPlayer.on(bufferingUpdate, (info) { const bufferedPercentage info.bufferedSize / info.totalSize * 100 console.log(缓冲进度: ${bufferedPercentage.toFixed(1)}%) if (bufferedPercentage 10 this.isPlaying) { this.notifyBufferingState(true) } else { this.notifyBufferingState(false) } }) // 监听网络状态变化 // 实际项目中需要接入鸿蒙的网络状态API } private notifyBufferingState(isBuffering: boolean): void { emitter.emit({eventId: buffering_state}, { data: JSON.stringify({isBuffering}) }) } }6. 实际应用示例6.1 在页面中使用播放器Entry Component struct MusicPage { State songs: SongItem[] [] State currentSong: SongItem | null null State isPlaying: boolean false State isBuffering: boolean false private player MusicPlayerManager.getInstance() aboutToAppear() { this.loadSongs() emitter.on(player_state_change, (data) { const state JSON.parse(data.data) this.currentSong state.currentSong this.isPlaying state.isPlaying }) emitter.on(buffering_state, (data) { this.isBuffering JSON.parse(data.data).isBuffering }) } private loadSongs(): void { // 从API或本地加载歌曲列表 this.songs [...] } build() { Column() { // 歌曲列表 List() { ForEach(this.songs, (song) { ListItem() { SongItemView({song: song}) .onClick(() { this.player.play(song) }) } }) } // 播放控制组件 PlayerControl({ currentSong: this.currentSong, isPlaying: this.isPlaying, isBuffering: this.isBuffering, onPlay: () this.currentSong this.player.play(this.currentSong), onPause: () this.player.pause(), onNext: () this.player.playNext(), onPrevious: () this.player.playPrevious() }) } } }6.2 自定义播放控制组件Component struct PlayerControl { Prop currentSong: SongItem | null Prop isPlaying: boolean Prop isBuffering: boolean Link onPlay: () void Link onPause: () void Link onNext: () void Link onPrevious: () void State progress: number 0 aboutToAppear() { emitter.on(player_state_change, (data) { const state JSON.parse(data.data) this.progress state.position / state.duration * 100 }) } build() { Column() { if (this.currentSong) { Row() { Image(this.currentSong.coverUrl) .width(50) .height(50) .borderRadius(5) Column() { Text(this.currentSong.title) .fontColor(Color.White) Text(this.currentSong.artist) .fontColor(#cccccc) .fontSize(12) } } Slider({ value: this.progress, min: 0, max: 100 }).onChange((value: number) { if (this.currentSong) { const position value / 100 * this.currentSong.duration MusicPlayerManager.getInstance().seekTo(position) } }) Row() { Button(this.isPlaying ? 暂停 : 播放) .onClick(() { if (this.isPlaying) { this.onPause() } else { this.onPlay() } }) Button(上一首) .onClick(() this.onPrevious()) Button(下一首) .onClick(() this.onNext()) } if (this.isBuffering) { Text(缓冲中...) .fontColor(#cccccc) } } else { Text(未选择歌曲) .fontColor(#cccccc) } } } }7. 测试与调试7.1 单元测试示例describe(MusicPlayerManager Tests, () { let player: MusicPlayerManager const testSong: SongItem { id: 1, title: Test Song, artist: Test Artist, album: Test Album, coverUrl: http://example.com/cover.jpg, audioUrl: http://example.com/audio.mp3, duration: 180 } beforeAll(() { player MusicPlayerManager.getInstance() }) it(should play a song correctly, async () { await player.play(testSong) expect(player.getCurrentSong()).toEqual(testSong) expect(player.isPlaying()).toBeTruthy() }) it(should pause the current song, async () { await player.pause() expect(player.isPlaying()).toBeFalsy() }) it(should handle play next song, async () { const nextSong {...testSong, id: 2} player.addToPlaylist(nextSong) await player.playNext() expect(player.getCurrentSong()?.id).toBe(2) }) })7.2 常见问题排查播放无声音检查设备音量是否开启确认音频焦点是否获取成功验证音频文件URL是否有效播放卡顿检查网络连接状态监控缓冲进度适当增加缓冲大小降低音频质量如有必要跨页面状态不同步确保所有页面都正确监听Emitter事件检查事件数据类型是否一致验证JSON序列化/反序列化过程内存泄漏在页面销毁时取消事件监听定期检查播放器实例状态使用开发者工具监控内存使用情况

相关文章:

鸿蒙应用开发实战:手把手教你封装一个可复用的音乐播放器管理类(ArkTS版)

鸿蒙应用开发实战:构建高可复用的音乐播放器管理类(ArkTS版) 在鸿蒙应用开发中,音频播放功能是许多应用的核心需求。本文将深入探讨如何设计一个健壮、可复用的音乐播放器管理类,采用ArkTS语言实现,帮助开发…...

讲透RenderTarget · 第一章:RenderTarget 是什么

**欢迎新朋友点赞、关注、收藏三连。第一章:RenderTarget 是什么一句话概括: RenderTarget 就是 GPU 的"画布"——不一定画在屏幕上,可以画在任何一块显存里。⏱ 30 秒概览RenderTarget(RT) GPU 可以写入像素…...

Windows自定义部署神器:从零开始的安装介质制作指南

Windows自定义部署神器:从零开始的安装介质制作指南 【免费下载链接】MediaCreationTool.bat Universal MCT wrapper script for all Windows 10/11 versions from 1507 to 21H2! 项目地址: https://gitcode.com/gh_mirrors/me/MediaCreationTool.bat 你是否…...

从idea ai插件到在线原型:用快马平台快速构建你的智能代码生成器

最近在开发中频繁使用IDEA的AI插件辅助编码,发现这类工具能大幅减少重复劳动。但插件功能往往局限于当前IDE环境,于是萌生了一个想法:能否把这种智能生成能力搬到线上,做成一个轻量级的Web工具?经过在InsCode(快马)平台…...

比特币钱包密码与助记词恢复实战指南:6大核心模块掌握btcrecover全功能

比特币钱包密码与助记词恢复实战指南:6大核心模块掌握btcrecover全功能 【免费下载链接】btcrecover An open source Bitcoin wallet password and seed recovery tool designed for the case where you already know most of your password/seed, but need assista…...

如何高效使用Super IO插件:Blender批量导入导出终极指南

如何高效使用Super IO插件:Blender批量导入导出终极指南 【免费下载链接】super_io blender addon for copy paste import / export 项目地址: https://gitcode.com/gh_mirrors/su/super_io 想要在Blender中实现一键导入导出模型和图像吗?Super I…...

3大焕新方案:老旧iOS设备性能重生全指南

3大焕新方案:老旧iOS设备性能重生全指南 【免费下载链接】Legacy-iOS-Kit An all-in-one tool to downgrade/restore, save SHSH blobs, and jailbreak legacy iOS devices 项目地址: https://gitcode.com/gh_mirrors/le/Legacy-iOS-Kit 老旧iOS设备随着系统…...

Janus-Pro-7B开发者案例:基于7860 Web UI构建内部AI知识助手

Janus-Pro-7B开发者案例:基于7860 Web UI构建内部AI知识助手 1. 项目背景与价值 企业内部知识管理一直是个头疼的问题。各种文档、图片、报告散落在不同系统中,员工想要快速找到需要的信息往往需要花费大量时间。传统的搜索工具只能基于文字匹配&#…...

Cadence Allegro 17.4进阶技巧:PCB Editor中高效调整丝印的三大步骤

1. 丝印调整的核心价值与准备工作 在PCB设计流程中,丝印调整往往被新手工程师视为"收尾环节",但实际它直接影响着后续生产的可制造性和产品维护的便利性。Cadence Allegro 17.4的PCB Editor模块提供了完整的丝印处理工具链,我经手…...

3分钟解锁暗黑破坏神2完整体验:PlugY插件终极指南 [特殊字符]

3分钟解锁暗黑破坏神2完整体验:PlugY插件终极指南 🎮 【免费下载链接】PlugY PlugY, The Survival Kit - Plug-in for Diablo II Lord of Destruction 项目地址: https://gitcode.com/gh_mirrors/pl/PlugY 还在为暗黑破坏神2单机模式的种种限制而…...

华为交换机MAC地址漂移检测与风暴抑制联动配置指南

1. 华为交换机MAC地址漂移检测原理与实战 刚接触网络运维时,第一次遇到MAC地址漂移报警简直一头雾水。后来才发现,这其实是交换机在提醒我们:"兄弟,你的网络里可能有环路!" MAC地址漂移的本质是同一个MAC地址…...

某循环流化床锅炉设计【论文+ CAD图纸+翻译】

循环流化床锅炉作为高效清洁燃烧技术的代表,其设计需兼顾热效率、污染物控制与运行稳定性。论文部分通过系统分析流体力学、传热学及燃烧学原理,构建了锅炉本体结构、受热面布置与气固两相流场优化的理论模型。针对不同煤种特性,重点探讨了循…...

3步解锁Windows运行安卓应用:APK-Installer轻量解决方案

3步解锁Windows运行安卓应用:APK-Installer轻量解决方案 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 在数字化办公与娱乐融合的今天,安卓应用…...

2024通信工程师初级备考指南:综合能力与专业实务核心考点解析

1. 2024通信工程师初级考试概况 2024年通信工程师初级资格考试定于9月28日举行,采用机考形式,考试时间为上午8:30至12:30,总时长4小时。这个考试分为两个科目:《通信专业综合能力》和《通信专业实务》,两科连续考试&am…...

从LC谐振到信号振铃:用Multisim仿真带你理解PCB上的阻尼振荡

从LC谐振到信号振铃:用Multisim仿真揭示PCB阻尼振荡的本质 1. 振铃现象:硬件工程师的"噩梦" 第一次在示波器上看到信号边沿那些诡异的振荡波形时,我差点以为自己的电路板被某种神秘力量干扰了。这种被称为"振铃"的现象…...

图片去水印 API 接口实战:网站如何实现自动去水印(Python / PHP / C#)

在做网站或后台系统时,一个很常见但容易被忽视的问题是: 👉 用户上传的图片自带水印 👉 平台展示希望统一成干净版本 👉 还要支持批量、自动化处理 👉 最好能无缝接入现有系统 如果你正在找: …...

革新性英雄联盟智能辅助解决方案:一站式游戏体验提升工具

革新性英雄联盟智能辅助解决方案:一站式游戏体验提升工具 【免费下载链接】League-Toolkit 兴趣使然的、简单易用的英雄联盟工具集。支持战绩查询、自动秒选等功能。基于 LCU API。 项目地址: https://gitcode.com/gh_mirrors/le/League-Toolkit 在快节奏的英…...

Swin2SR效果实测:处理含文字区域图像时的可读性保持能力专项测试

Swin2SR效果实测:处理含文字区域图像时的可读性保持能力专项测试 1. 测试背景与目的 在日常工作和生活中,我们经常会遇到一些低分辨率、模糊不清的图片,特别是那些包含文字的图像。无论是扫描的文档、网页截图,还是老照片中的文…...

如何用QtScrcpy实现跨平台Android设备高效投屏与控制

如何用QtScrcpy实现跨平台Android设备高效投屏与控制 【免费下载链接】QtScrcpy Android实时投屏软件,此应用程序提供USB(或通过TCP/IP)连接的Android设备的显示和控制。它不需要任何root访问权限 项目地址: https://gitcode.com/barry-ran/QtScrcpy 在数字化…...

BACnet4j实战:从模拟设备到点位数据采集的完整流程解析

1. BACnet4j与工业物联网数据采集入门 第一次接触BACnet协议时,我被各种专业术语搞得晕头转向。直到用BACnet4j成功读取到第一个温度传感器的数据,才真正理解这个协议的价值。BACnet/IP就像工业设备间的普通话,而BACnet4j就是让Java程序能说这…...

IndexTTS 2.0优化指南:如何选择参考音频,获得最佳克隆效果

IndexTTS 2.0优化指南:如何选择参考音频,获得最佳克隆效果 1. 引言:为什么参考音频如此重要? 在语音合成领域,参考音频就像是一把钥匙,决定了最终生成声音的质量和相似度。IndexTTS 2.0作为一款零样本音色…...

ICLR 2025论文解读│PointOBB-v2:单点监督下的高效有向目标检测新突破

1. PointOBB-v2:单点监督的革命性突破 有向目标检测一直是计算机视觉领域的重要研究方向,特别是在遥感图像分析、自动驾驶和工业检测等实际应用中。传统的有向边界框(OBB)标注需要人工精确标注目标的旋转角度和四个顶点坐标&…...

PMOS 在电源管理中的高效应用

1. PMOS在高侧开关中的天然优势 我第一次用PMOS做高侧开关是在一个车载设备项目里。当时需要控制12V电源的通断,尝试了几种方案后,发现PMOS简直是这个场景的"天选之子"。相比NMOS,PMOS最大的优势就是控制逻辑简单直接——栅极拉低导…...

从“Hello World”到区域赛银牌:我的ACM算法打怪升级全记录(附各阶段工具包)

从“Hello World”到区域赛银牌:我的ACM算法打怪升级全记录 记得大一刚接触编程时,连最简单的冒泡排序都要调试半天。三年后站在领奖台上,回想这段旅程,最珍贵的不是奖牌,而是那些深夜debug的坚持和突破自我的瞬间。这…...

释放创意:Mi-Create让智能表盘设计触手可及

释放创意:Mi-Create让智能表盘设计触手可及 【免费下载链接】Mi-Create Unofficial watchface creator for Xiaomi wearables ~2021 and above 项目地址: https://gitcode.com/gh_mirrors/mi/Mi-Create 问题发现:智能表盘设计的三重困境 在智能穿…...

告别重复劳动:用快马ai生成高效openclaw脚本提升安卓测试效率

告别重复劳动:用快马AI生成高效OpenClaw脚本提升安卓测试效率 在安卓自动化测试中,编写重复性的设备操作脚本往往是最耗时耗力的环节。每次测试新版本,我们都需要重复编写类似的点击、滑动、输入等操作代码,不仅效率低下&#xf…...

Z-Image-Turbo_Sugar脸部Lora赋能网络安全:生成模拟人脸进行隐私保护测试

Z-Image-Turbo_Sugar脸部Lora赋能网络安全:生成模拟人脸进行隐私保护测试 1. 引言:当网络安全遇上AI造脸 你有没有想过,那些用来保护我们手机、门禁的人脸识别系统,到底安不安全?安全研究员们每天都在琢磨这个问题。…...

戴森球计划FactoryBluePrints:解锁游戏工厂建造的终极免费蓝图库

戴森球计划FactoryBluePrints:解锁游戏工厂建造的终极免费蓝图库 【免费下载链接】FactoryBluePrints 游戏戴森球计划的**工厂**蓝图仓库 项目地址: https://gitcode.com/GitHub_Trending/fa/FactoryBluePrints 还在为《戴森球计划》中复杂的工厂布局头疼吗&…...

告别云端:在百元ESP32-S3上实现离线婴儿哭声识别,隐私与实时性我全都要

边缘智能革命:用ESP32-S3打造零隐私风险的婴儿监护终端 当科技与育儿需求碰撞,我们面临一个核心矛盾:如何在不牺牲隐私的前提下实现智能化监护?传统方案依赖云端处理,却让敏感数据暴露在传输与存储环节。本文将揭示一种…...

5分钟精通Meld文件对比工具:效率倍增的3大场景实战指南

5分钟精通Meld文件对比工具:效率倍增的3大场景实战指南 【免费下载链接】meld Read-only mirror of https://gitlab.gnome.org/GNOME/meld 项目地址: https://gitcode.com/gh_mirrors/me/meld Meld是一款开源的可视化文件对比工具,能够帮助开发者…...