HarmonyOS Next之仿网易云APP实战开发教程(上) 原创
一、背景
最近逛论坛和各种技术站发现大家对音乐播放类应用搜索的频率非常高,刚好最近因为对next的网易云感兴趣我想着自己实现一下,借着这个机会,刚好把相关内容不够完善的空缺填不上,那么今天就为大家带来商城应用开发中比较重要的几个功能模块,首先让我们看看完成后的效果图
从效果图中我们可以看到,音乐有了点网易云音乐的样子,并且已经实现了音乐列表的展示,还有一个底部的状态栏。同时我们也完成了一个简单的音乐播放的组件,去实现音乐的播放控制
二、播放原理
在讲具体的实现之前,我们先来了解一下音乐播放的原理,我们选择使用的api是avplayer。
AVPlayer主要工作是将Audio/Video媒体资源(比如mp4/mp3/mkv/mpeg-ts等)转码为可供渲染的图像和可听见的音频模拟信号,并通过输出设备进行播放。同时呢在鸿蒙的AVPlayer中他还提供功能完善一体化播放能力,应用只需要提供流媒体来源,不负责数据解析和解码就可达成播放效果。
我们先看一下音乐播放器外部的交互图,更方便我们去理解
看完图之后我们就可以得到一些信息,音乐类应用通过调用JS接口层提供的AVPlayer接口实现相应功能时,框架层会通过播放服务(Player Framework)将资源解析成音频数据流(PCM),音频数据流经过软件解码后输出至音频服务(Audio Framework),由音频服务输出至音频驱动渲染,实现音频播放功能。完整的音频播放需要应用、Player Framework、Audio Framework、音频HDI共同实现。
三、鸿蒙网络访问
要进行网络访问首先我们需要在开发工具中找到module.Json5,然后写入requestPermissions,最后加上网络请求权限ohos.permission.INTERNET
具体实现如下
在项目中使用就可以进行一些http相关的操作了,这里通过加载一个在线logo来做演示
要加载一个网易云的logo就可以这样实现
Row(){
Image("https://tse2-mm.cn.bing.net/th/id/OIP-C.cXF1EPCQRP1xKB5Gs-jT_gHaHa?w=158&h=180&c=7&r=0&o=5&dpr=2&pid=1.7")
.borderRadius($r('app.float.album_cover_border_radius'))
.height(100)
.width(100)
Column() {
Text('仿网易云音乐')
.fontColor($r('app.color.album_name_introduction'))
.margin({ bottom: $r('app.float.album_name_margin') })
Text("网易云音乐内容测试")
.fontColor($r('app.color.album_name_introduction'))
}
.margin({left:20})
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding(10)
四、歌曲列表
歌曲列表的展示就比较简单了,我们先定义一个实体类
export class SongItem {
id: number = 0;
title: string = '';
singer: string = '';
label: Resource = $r('app.string.page_show');
src: string = '';
index: number = 0;
lyric: string = '';
}
然后添加需要展示的数据
import { SongItem } from '@ohos/mediaCommon';
const songList: SongItem[] = [
{ id: 1, title: 'wake', singer: '未知',
label: $r('app.media.wake'), src: 'mp1.mp3', index:0,
lyric: '' },
{ id: 2, title: '紫荆花盛开', singer: '李荣浩',
label: $r('app.media.zijinghuua'), src: 'zijinghua.mp3', index:1, lyric: '' }
]
export { songList }
最后我们在组件中通过list展示数据列表
List() {
LazyForEach(new SongDataSource(this.songList), (item: SongItem, index: number) => {
ListItem() {
Column() {
this.SongItem(item, index)
}
.padding({
left: $r('app.float.list_item_padding'),
right: $r('app.float.list_item_padding')
})
}
}, (item: SongItem, index?: number) => JSON.stringify(item) + index)
}
.width(StyleConstants.FULL_WIDTH)
.backgroundColor(Color.White)
.margin({ top: $r('app.float.list_area_margin_top') })
.layoutWeight(1)
.divider({
color: $r('app.color.list_divider'),
strokeWidth: $r('app.float.stroke_width'),
startMargin: $r('app.float.list_item_padding'),
endMargin: $r('app.float.list_item_padding')
})
这样我们的列表就展示出来了,还是比较简单的
可以看到我们添加的数据就已经展示到页面上了
五、自定义播放组件开发
播放组件我们先在当前页面实现一个简单的,大多数音乐软件也都会有这样的效果。在这个自定义组件里,我们要展示的内容不多,有当前歌曲的图片,歌曲名称还有当前歌曲的状态。
我们先定义一个自定义组件,把获取歌曲数据和控制歌曲播放的功能实现一下
@Component
export struct Player {
@StorageProp('selectIndex') selectIndex: number = 0;
@StorageLink('isPlay') isPlay: boolean = false;
@StorageLink('songList') songList: SongItem[] = [];
@StorageLink('topArea') topArea: number = 0;
@StorageLink('bottomArea') bottomArea: number = 0;
@StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointConstants.BREAKPOINT_SM;
@State imageRotate: number = 0;
@StorageLink('isShowPlay') isShowPlay: boolean = false;
@State componentHeight: number = 0;
@StorageLink('deviceHeight') deviceHeight: number = 0;
private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Vertical });
build() {
Row() {
Row() {
Image(this.songList[this.selectIndex]?.label)
.height($r('app.float.cover_height'))
.width($r('app.float.cover_width'))
.borderRadius($r('app.float.label_border_radius'))
.margin({ right: $r('app.float.cover_margin') })
.rotate({ angle: this.imageRotate })
Column() {
Text(this.songList[this.selectIndex].title)
.fontColor($r('app.color.song_name'))
}
.alignItems(HorizontalAlign.Start)
}
.layoutWeight(PlayerConstants.LAYOUT_WEIGHT_PLAYER_CONTROL)
.onClick(() => {
this.isShowPlay = true;
})
Blank()
.onClick(() => {
this.isShowPlay = true;
})
Row() {
Image(this.isPlay ? $r('app.media.ic_play') : $r('app.media.ic_pause'))
.height($r('app.float.control_icon_height'))
.width($r('app.float.control_icon_width'))
.displayPriority(PlayerConstants.DISPLAY_PRIORITY_THREE)
.onClick(() => {
if (MediaService.getInstance().getFirst()) {
MediaService.getInstance().loadAssent(0);
} else {
this.isPlay ? MediaService.getInstance().pause() : MediaService.getInstance().play();
}
})
}
.justifyContent(FlexAlign.End)
}
.width(StyleConstants.FULL_WIDTH)
.height(48)
.backgroundColor("#F6F9FC")
.bindContentCover($$this.isShowPlay, this.musicPlayBuilder(), ModalTransition.DEFAULT)
.padding({
left: 16,
right: 16
})
.position({
x: 0,
y: StyleConstants.FULL_HEIGHT
})
.translate({
x: 0,
y: StyleConstants.TRANSLATE_PLAYER_Y
})
}
@Builder
musicPlayBuilder() {
Column() {
Column() {
MusicControlComponent({ isShowPlay: this.isShowPlay })
}
.height((100 - this.componentHeight) + '%')
}
.height(StyleConstants.FULL_WIDTH)
.width(StyleConstants.FULL_HEIGHT)
.justifyContent(FlexAlign.End)
.transition(TransitionEffect.translate({ y: 1000 }).animation({ curve: curves.springMotion(0.6, 0.8) }))
}
}
在这里我们通过拿出数据和播放的状态来对歌曲进行操控,这样就实现了一个简单的音乐播放功能