回复
HarmonyOS Next之仿网易云APP实战开发第二期(1) 原创
wuyanghcoa
发布于 2024-11-30 13:13
浏览
0收藏
前言
在数字化时代,音乐已经成为我们生活中不可或缺的一部分。本文将带领大家探讨如何基于HarmonyOS Next开发一个仿网易云音乐APP,从音频播放的核心技术到自定义播放组件的开发,我们将重点讨论音频播放的基本界面和功能、播放原理,以及如何进行鸿蒙网络访问和歌曲列表的构建。
基础
1. 媒体播放
在HarmonyOS中,媒体播放主要通过AVPlayer
模块实现。AVPlayer
提供了一系列的API来控制媒体的播放,包括创建播放器、设置播放源、准备播放、播放控制等。以下是AVPlayer
的一些关键操作:
- 创建播放器:使用
OH_AVPlayer * OH_AVPlayer_Create(void)
创建一个播放器实例。 - 设置播放源:通过
OH_AVPlayer_SetURLSource
或OH_AVPlayer_SetFDSource
设置播放源,可以是网络URL或者文件描述符。 - 准备播放:调用
OH_AVPlayer_Prepare
准备播放环境,异步缓存媒体数据。 - 播放控制:使用
OH_AVPlayer_Play
、OH_AVPlayer_Pause
、OH_AVPlayer_Stop
等API进行播放控制。 - 资源释放:播放结束后,使用
OH_AVPlayer_Release
或OH_AVPlayer_ReleaseSync
释放播放器资源。
2. 鸿蒙网络访问
在网络开发中,HTTP请求是必不可少的一部分。在鸿蒙(HarmonyOS)开发中,同样需要处理网络请求,无论是与后端服务器交互还是获取外部API的数据。下面是对鸿蒙开发中涉及到的HTTP模块——http模块,以及一个常用的第三方库——axios模块的总结。
基本概念:
- 请求(Request):客户端向服务器发送的消息。
- 响应(Response):服务器接收到请求后返回给客户端的消息。
- 消息格式:HTTP消息由报文头(Header)、状态行(Status Line)和可选的实体主体(Entity Body)组成。
基本用法:
- 导入
import { http } from '@kit.NetworkKit'
- 创建请求对象
const req = http.createHttp()
- 发送请求并获取响应
req.request('请求地址url')
.then((res: http.HttpResponse) => {
AlertDialog.show({ message: JSON.stringify(res) })
})
注意事项:
-
预览器:无需配置网络权限即可成功发送请求。
-
模拟器:需要配置网络权限才能成功发送请求。
-
真机:需要配置网络权限才能成功发送请求。
-
在HarmonyOS中,你需要在module.json5文件中配置网络权限。
3. 歌曲列表
歌曲列表的实现通常涉及到UI组件和数据管理。在HarmonyOS中,可以使用List
组件来展示歌曲列表,并通过数据绑定动态更新列表内容。
4. 自定义播放组件开发
自定义播放组件的开发涉及到播放器的封装和UI的定制。以下是开发步骤和示例代码:
class AudioPlayer {
constructor() {
this.player = OH_AVPlayer_Create();
}
play(url) {
OH_AVPlayer_SetURLSource(this.player, url);
OH_AVPlayer_Prepare(this.player);
OH_AVPlayer_Play(this.player);
}
pause() {
OH_AVPlayer_Pause(this.player);
}
// 其他播放器控制方法...
}
具体实现
这里主要展示歌曲播放组件页面的开发
AVPlayer.ets
import media from '@ohos.multimedia.media'
import { BusinessError } from '@kit.BasicServicesKit'
import { audio } from '@kit.AudioKit'
import {Size_Data as sd} from '../Common/Constant/Size_data'
import {Configuration} from '../Common/Constant/Configuration'
/**
* 音频播放器操作类
* 提供音频播放器的初始化、播放控制和错误处理等功能
*/
export class player_op{
is_paused:boolean = false // 播放器暂停状态标志
player:media.AVPlayer|undefined // 音频播放器实例
/**
* 初始化播放器回调函数
* 设置播放器的事件监听,包括错误处理和状态变化
*/
Init_player_callingback(){
if(this.player==undefined) return
// 错误事件处理
this.player.on('error',(err:BusinessError)=>{
this.player?.reset()
AlertDialog.show({
message:'当前曲目无法播放',
alignment:DialogAlignment.Center,
width:sd.play_failure_width,
height:sd.play_failure_height,
shadow:{radius:50,color:Configuration.dialog_background_color,offsetX:30,offsetY:-30}
})
})
// 状态变化事件处理
this.player.on('stateChange',async (state:string,reason:media.StateChangeReason)=>{
switch (state) {
case 'idle':
console.info('AvPlayer_State_Change: Player reset, now state: Idle')
this.player?.release()
break
case 'initialized':
console.info('AvPlayer_State_Change: Player initialized, now state: Initialized')
if(this.player!=undefined){
//设置播放器渲染参数,必须在Prepare调用前设置
this.player.audioRendererInfo = {
usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
rendererFlags:0
}
}
this.player?.prepare()
break
case 'prepared':
console.info('AvPlayer_State_Change: Player prepared, now state: prepared')
this.player?.play()
break
case 'playing':
//播放到末尾后自动跳转到Completed状态
console.info('AvPlayer_State_Change: Player play, now state: playing')
break
case 'paused':
console.info('AvPlayer_State_Change: Player play, now state: paused')
break
case 'completed':
console.info('AvPlayer_State_Change: now state: Complete')
this.player?.stop()
break
case 'stopped':
this.player?.reset()
break
case 'released':
this.player = undefined
break
default :
break
}
})
}
/**
* 开始播放音频
* @param url 音频文件的URL
*/
Start_play(url:string){
media.createAVPlayer().then((_player:media.AVPlayer)=>{
this.player = _player
this.Init_player_callingback()
this.player.url = url
})
}
/**
* 暂停播放
*/
player_pause(){
if(this.is_paused) return
this.is_paused = true
this.player?.pause()
}
/**
* 继续播放
*/
player_continue(){
if(!this.is_paused) return
this.is_paused = false
this.player?.play()
}
/**
* 重置播放器并播放新的音频
* @param _url 新音频文件的URL
*/
player_reset(_url:string){
this.is_paused = false
if(this.player==undefined){
this.Start_play(_url)
}else{
this.player.release().then(()=>{
this.player = undefined
this.Start_play(_url)
})
}
}
}
// 创建并导出音频播放器操作实例
const Player = new player_op()
export default Player
SongPage.ets
import {Song,Page_transmission as pt,Static_Config} from '../Class_def/song_def'
import http from "@ohos.net.http"
import {Size_Data as sd} from '../Common/Constant/Size_data'
import { CircleShape } from '@kit.ArkUI'
import {Order} from '../Common/Constant/Order'
import player from '../Api/AVPlayer'
import {web_dialog} from '../Build/Dialog'
import {Configuration} from '../Common/Constant/Configuration'
import text from '@ohos.graphics.text'
@Builder
export function song_builder(name:string,param:object){
Song_page({current_song:Static_Config.current_song,page_info:(param as pt).page_info})
}
@Entry
@Component
struct test{
build() {
}
}
@Component
struct Song_page {
@State current_song:Song = new Song()
@State lyrics:Array<string> = []
page_info:NavPathStack|undefined
aboutToAppear(): void {
//Get lyrics
if(this.current_song.id<0) return
this.get_http()
}
async get_http() : Promise<void>{
let http_request = http.createHttp()
let response = http_request.request(
Order.url + Order.get_lyrics + this.current_song.id.toString()
);
await response.then((data)=>{
if(data.responseCode==200){
//Get new search data
this.lyrics.splice(0,this.lyrics.length) //Clear content
let res = data.result as string
let json_res = JSON.parse(res) as object
let lrc = (json_res?.["lrc"]) as object
let lrc_content = (lrc as object)?.["lyric"] as string
let base:number = 0
for(let i=0;i!=lrc_content.length;i++){
if(lrc_content[i]==']') base = i+1
if(lrc_content[i]=='\n') this.lyrics.push(lrc_content.substring(base,i))
}
}else{
console.info("Search request fail")
}
})
}
build() {
NavDestination(){
Column(){
Stack(){
//当前歌曲名称
Text(this.current_song.song_name)
.width('100%')
.fontSize(30)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.padding(5)
.position({left:0,top:10})
//返回按钮
Button(){
Text("<")
.fontSize(20)
.fontColor(Color.White)
}
.borderWidth(1.5)
.borderColor(Color.White)
.backgroundColor(Color.Transparent)
.width('10%')
.height('90%')
.margin(10)
.position({left:5,top:2})
.type(ButtonType.Circle)
.onClick((event: ClickEvent) => {
this.page_info?.pop()
})
}
.width('100%')
.height('7%')
.margin({top:10})
//歌手名称,点击可打开显示歌手主页的网页
Text(this.current_song.singer_name)
.width('100%')
.fontSize(20)
.fontColor(Configuration.singer_name_color)
.textAlign(TextAlign.Center)
.onClick(()=>{
let dialog_controller:CustomDialogController|null = new CustomDialogController({
builder: web_dialog({
singer_id:this.current_song.singer_id.toString()
}),
alignment:DialogAlignment.Bottom,
offset:{dx:0,dy:-100},
shadow:{radius:50,color:Color.Red,offsetX:30,offsetY:-30}
})
dialog_controller.open()
dialog_controller = null
})
Blank().height(30)
//Main Content:用于显示歌词
List(){
ForEach(this.lyrics,(line:string)=>{
ListItem(){
Text(line)
.fontSize(20)
.width('100%')
.textAlign(TextAlign.Center)
.fontColor(Configuration.lyric_color)
}
.width('100%')
.height(sd.lyrics_line)
.margin(10)
},(index:number)=>index.toString())
}
.height('65%')
.width('100%')
.scrollBar(BarState.Off)
/*按键区,包含三个按键,分别为
* 1. 切换上一首,待后续添加逻辑
* 2. 暂停或继续播放
* 3. 切换下一首,待后续添加逻辑
*/
Row(){
Image($r('app.media.last'))
.margin({left:'20%'})
.size({width:sd.next_button,height:sd.next_button})
.clipShape(new CircleShape({width:sd.next_button,height:sd.next_button}))
Image($r('app.media.play'))
.size({width:sd.play_button,height:sd.play_button})
.clipShape(new CircleShape({width:sd.play_button,height:sd.play_button}))
.margin({left:'7%'})
.onClick(()=>{
if(player.player!=undefined){
if(player.is_paused) player.player_continue()
else player.player_pause()
}
})
Image($r('app.media.next'))
.size({width:sd.next_button,height:sd.next_button})
.clipShape(new CircleShape({width:sd.next_button,height:sd.next_button}))
.margin({left:'7%'})
}
.width('100%')
.height('20%')
//content({current_song:this.current_song}).width('100%').height('100%')
}
.width('100%')
.height('100%')
.linearGradient({
direction:GradientDirection.Bottom,colors:
[['#FF4D4D',0.0],
['#FF7A7A',0.5],
['#FFA5A5',1.0]]})
}.hideTitleBar(true)
}
}
实现效果
©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
已于2024-11-30 13:18:13修改
赞
收藏
回复
相关推荐