介紹
使用ArkTS語言實(shí)現(xiàn)視頻播放器,主要包括主界面和視頻播放界面,我們將一起完成以下功能:
- 主界面頂部使用Swiper組件實(shí)現(xiàn)視頻海報(bào)輪播。
- 主界面下方使用List組件實(shí)現(xiàn)視頻列表。
- 播放界面使用Video組件實(shí)現(xiàn)視頻播放。
- 在不使用視頻組件默認(rèn)控制器的前提下,實(shí)現(xiàn)自定義控制器。
- 播放界面底部使用圖標(biāo)控制視頻播放/暫停。
- 播放界面底部使用Slider組件控制和實(shí)現(xiàn)視頻播放進(jìn)度。
- 播放界面使用Stack容器組件的[Z序控制]實(shí)現(xiàn)在視頻播放畫面上添加開始/暫停/加載圖標(biāo)。
相關(guān)概念
- [Swiper]組件:滑動容器,提供切換子組件顯示的能力。
- [List]組件:列表包含一系列相同寬度的列表項(xiàng)。適合連續(xù)、多行呈現(xiàn)同類數(shù)據(jù),例如圖片和文本。
- [Video]組件:用于播放視頻文件并控制其播放狀態(tài)的組件。
- [Navigator]組件:路由容器組件,提供路由跳轉(zhuǎn)能力。
- [ForEach]組件:基于數(shù)組類型數(shù)據(jù)執(zhí)行循環(huán)渲染。
- 鴻蒙開發(fā)指導(dǎo)文檔:[
gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md
]
環(huán)境搭建
軟件要求
- [DevEco Studio]版本:DevEco Studio 3.1 Release。
- OpenHarmony SDK版本:API version 9。
硬件要求
- 開發(fā)板類型:[潤和RK3568開發(fā)板]。
- OpenHarmony系統(tǒng):3.2 Release。
環(huán)境搭建
完成本篇Codelab我們首先要完成開發(fā)環(huán)境的搭建,本示例以RK3568開發(fā)板為例,參照以下步驟進(jìn)行:
- [獲取OpenHarmony系統(tǒng)版本]:標(biāo)準(zhǔn)系統(tǒng)解決方案(二進(jìn)制)。以3.2 Release版本為例:
- 搭建燒錄環(huán)境。
- [完成DevEco Device Tool的安裝]
- [完成RK3568開發(fā)板的燒錄]
- 搭建開發(fā)環(huán)境。
- 開始前請參考[工具準(zhǔn)備],完成DevEco Studio的安裝和開發(fā)環(huán)境配置。
- 開發(fā)環(huán)境配置完成后,請參考[使用工程向?qū)創(chuàng)建工程(模板選擇“Empty Ability”)。
- 工程創(chuàng)建完成后,選擇使用[真機(jī)進(jìn)行調(diào)測]。
HarmonyOS與OpenHarmony鴻蒙文檔籽料:mau123789是v直接拿
代碼結(jié)構(gòu)解讀
本篇Codelab只對核心代碼進(jìn)行講解,對于完整代碼,我們會在gitee中提供。
├──entry/src/main/ets // 代碼區(qū)
│ ├──common
│ │ └──constants
│ │ └──CommonConstants.ets // 樣式常量類
│ ├──entryability
│ │ └──EntryAbility.ts // 程序入口類
│ ├──model
│ │ └──VideoControll.ets // 視頻播放控制相關(guān)方法類
│ ├──pages
│ │ ├──SimpleVideoIndex.ets // 主界面
│ │ └──SimpleVideoPlay.ets // 視頻播放界面
│ ├──view
│ │ ├──IndexModule.ets // 自定義首頁List模塊組件文件
│ │ ├──IndexSwiper.ets // 自定義首頁Swiper組件文件
│ │ ├──VideoPlayer.ets // 自定義播放界面視頻組件文件
│ │ └──VideoPlaySlider.ets // 自定義播放界面視頻進(jìn)度條組件文件
│ └──viewmodel
│ ├──HorizontalVideoItem.ets // 水平視頻類
│ ├──ParamItem.ets // 參數(shù)類
│ ├──SwiperVideoItem.ets // 自定義播放界面視頻組件文件
│ └──VideoData.ets // 首頁相關(guān)數(shù)據(jù)
└──entry/src/main/resource // 應(yīng)用靜態(tài)資源目錄
構(gòu)建主界面
主界面由視頻輪播模塊和多個(gè)視頻列表模塊組成,效果如圖所示:
VideoData.ets中定義的視頻輪播圖數(shù)組SWIPER_VIDEOS和視頻列表圖片數(shù)組HORIZONTAL_VIDEOS。
// VideoData.ets
import { HorizontalVideoItem } from './HorizontalVideoItem';
import { SwiperVideoItem } from './SwiperVideoItem';
export const SWIPER_VIDEOS: SwiperVideoItem[] = [
new SwiperVideoItem($r('app.media.ic_banner1')),
new SwiperVideoItem($r('app.media.ic_banner2')),
new SwiperVideoItem($r('app.media.ic_banner3'))
];
export const HORIZONTAL_VIDEOS: HorizontalVideoItem[] = [
new HorizontalVideoItem(1, $r('app.media.ic_video_list0'), '視頻1'),
new HorizontalVideoItem(2, $r('app.media.ic_video_list1'), '視頻2'),
new HorizontalVideoItem(3, $r('app.media.ic_video_list2'), '視頻3')
];
IndexSwiper.ets文件中定義的輪播圖子組件SwiperVideo,點(diǎn)擊輪播圖片,頁面跳轉(zhuǎn)到視頻播放頁面,并攜帶本地視頻flag,效果如圖所示:
// IndexSwiper.ets
@Component
export struct SwiperVideo {
build() {
Column() {
Swiper() {
ForEach(SWIPER_VIDEOS, (item: SwiperVideoItem) = > {
SwiperItem({ imageSrc: item.image, source: $rawfile('videoTest.mp4') })
}, (item: SwiperVideoItem) = > JSON.stringify(item))
}
.autoPlay(true)
}
// 樣式設(shè)置
...
}
}
@Component
struct SwiperItem {
private imageSrc: Resource = $r('app.string.empty');
private source: Resource = $r('app.string.empty');
private paramItem: ParamItem = new ParamItem();
...
build() {
// 跳轉(zhuǎn)一:使用Navigator組件跳轉(zhuǎn)到視頻播放界面
Navigator({ target: SECOND_PAGE, type: NavigationType.Push }) {
Image(this.imageSrc)
.borderRadius(MARGIN_FONT_SIZE.FIRST_MARGIN)
}
.params(this.paramItem)
}
}
IndexModule.ets文件中定義的視頻列表圖片子組件VideoModule,點(diǎn)擊子組件中的圖片,頁面跳轉(zhuǎn)到視頻播放頁面,并攜帶網(wǎng)絡(luò)視頻flag,效果如圖所示:
// IndexModule.ets
@Component
export struct VideoModule {
private moduleName: string = '';
build() {
Column() {
// 視頻列表上方的文本信息
...
// 視頻列表組件
List({ space: MARGIN_FONT_SIZE.FIRST_MARGIN }) {
ForEach(HORIZONTAL_VIDEOS, (item: HorizontalVideoItem) = > {
ListItem() {
HorizontalItem({
imageSrc: item.image,
source: NET,
videoName: item.name
})
}
}, (item: HorizontalVideoItem) = > JSON.stringify(item))
}
// 設(shè)置列表橫向排列
.listDirection(Axis.Horizontal)
}
// 樣式設(shè)置
...
}
}
@Component
struct HorizontalItem {
private imageSrc: Resource = $r('app.string.empty');
private source: string = '';
private videoName: string = '';
build() {
// 跳轉(zhuǎn)二:使用route跳轉(zhuǎn)到視頻播放界面
Column() {
Image(this.imageSrc)
.width(MARGIN_FONT_SIZE.SEVENTH_MARGIN)
.height(MARGIN_FONT_SIZE.SIXTH_MARGIN)
.onClick(() = > {
router.pushUrl({
url: SECOND_PAGE,
params: { source: this.source }
});
})
...
}
.justifyContent(FlexAlign.Center)
}
}
在SimpleVideoIndex.ets主界面中引用SwiperVideo和VideoModule子組件。
// SimpleVideoIndex.ets
@Entry
@Component
struct SimpleVideoIndex {
build() {
Column({ space: MARGIN_FONT_SIZE.FOURTH_MARGIN }) {
// 視頻輪播組件
SwiperVideo()
List() {
ForEach(LIST, (item: string) = > {
ListItem() {
VideoModule({ moduleName: item })
.margin({ top: MARGIN_FONT_SIZE.FIRST_MARGIN })
}
}, (item: string) = > JSON.stringify(item))
}
.listDirection(Axis.Vertical)
.margin({ top: MARGIN_FONT_SIZE.THIRD_MARGIN })
}
...
}
}
構(gòu)建視頻播放界面
VideoPlayer.ets其中定義了視頻播放子組件VideoPlayer ,onPrepared回調(diào)方法中可以獲取視頻總時(shí)長,onUpdate回調(diào)方法中可實(shí)時(shí)獲取到視頻播放的當(dāng)前時(shí)間戳,onFinish是視頻播放結(jié)束后的回調(diào)方法,onError是視頻播放出錯(cuò)的回調(diào)方法。
// VideoPlayer.ets
@Component
export struct VideoPlayer {
private source: string | Resource = '';
private controller: VideoController = new VideoController();
private previewUris: Resource = $r('app.media.preview');
@Provide currentTime: number = 0;
@Provide durationTime: number = 0;
@Provide durationStringTime: string = START_TIME;
@Provide currentStringTime: string = START_TIME;
@Consume isPlay: boolean;
@Consume isOpacity: boolean;
@Consume flag: boolean;
@Consume isLoading: boolean;
@Consume progressVal: number;
build() {
Column() {
Video({
src: this.source,
previewUri: this.previewUris,
controller: this.controller
})
.width(ALL_PERCENT)
.height(STRING_PERCENT.NINETY_PERCENT)
.controls(false)
.autoPlay(false)
.objectFit(ImageFit.Contain)
.loop(false)
.onUpdate((event) = > {
if (event) {
this.currentTime = event.time;
this.currentStringTime = changeSliderTime(this.currentTime);
}
})
.onPrepared((event) = > {
this.prepared(event?.duration);
})
.onFinish(() = > {
this.finish();
})
.onError(() = > {
prompt.showToast({
duration: COMMON_NUM_DURATION,
message: MESSAGE
});
})
VideoSlider({ controller: this.controller })
}
}
...
}
在自定義組件VideoPlayer底部使用了自定義子組件VideoSlider,VideoSlider自定義組件中顯示和控制視頻播放進(jìn)度,效果如圖所示:
// VideoPlaySlider.ets
@Component
export struct VideoSlider {
@Consume isOpacity: boolean;
private controller: VideoController = new VideoController();
@Consume currentStringTime: string;
@Consume currentTime: number;
@Consume durationTime: number;
@Consume durationStringTime: string;
@Consume isPlay: boolean;
@Consume flag: boolean;
@Consume isLoading: boolean;
@Consume progressVal: number;
build() {
Row({ space: MARGIN_FONT_SIZE.FIRST_MARGIN }) {
...
Slider({
value: this.currentTime,
min: 0,
max: this.durationTime,
step: 1,
style: SliderStyle.OutSet
})
.blockColor($r('app.color.white'))
.width(STRING_PERCENT.SLIDER_WITH)
.trackColor(Color.Gray)
.selectedColor($r("app.color.white"))
.showSteps(true)
.showTips(true)
.trackThickness(this.isOpacity ? SMALL_TRACK_THICK_NESS : BIG_TRACK_THICK_NESS)
.onChange((value: number, mode: SliderChangeMode) = > {
this.sliderOnchange(value, mode);
})
...
}
.opacity(this.isOpacity ? DEFAULT_OPACITY : 1)
...
}
...
}
在VideoController.ets中的視頻控制和回調(diào)的相關(guān)方法。
// VideoControll.ets
export function changeSliderTime(value: number): string {
let second: number = value % COMMON_NUM_MINUTE;
let min: number = Number.parseInt((value / COMMON_NUM_MINUTE).toString());
let head = min < COMMON_NUM_DOUBLE ? `${ZERO_STR}${min}` : min;
let end = second < COMMON_NUM_DOUBLE ? `${ZERO_STR}${second}` : second;
let nowTime = `${head}${SPLIT}${end}`;
return nowTime;
}
在SimpleVideoPlay.ets播放界面,引用VideoPlayer子組件,并在視頻播放頁面使用堆疊容器,在視頻播放畫面中心堆疊控制、視頻加載圖標(biāo),效果如圖所示:
// SimpleVideoPlay.ets
@Entry
@Component
struct Play {
// 取到Index頁面跳轉(zhuǎn)來時(shí)攜帶的source對應(yīng)的數(shù)據(jù)。
private source: string = (router.getParams() as Record< string, Object >).source as string;
private startIconResource: Resource = $r('app.media.ic_public_play');
private backIconResource: Resource = $r('app.media.ic_back');
@Provide isPlay: boolean = false;
@Provide isOpacity: boolean = false;
controller: VideoController = new VideoController();
@Provide isLoading: boolean = false;
@Provide progressVal: number = 0;
@Provide flag: boolean = false;
...
onPageHide() {
this.controller.pause();
}
build() {
Column() {
// 頂部返回以及標(biāo)題
...
Stack() {
// 不同的播放狀態(tài)渲染不同得控制圖片
if (!this.isPlay && !this.isLoading) {
Image(this.startIconResource)
.width(MARGIN_FONT_SIZE.FIFTH_MARGIN)
.height(MARGIN_FONT_SIZE.FIFTH_MARGIN)
// 同一容器中兄弟組件顯示層級關(guān)系,z值越大,顯示層級越高 用于控制圖片在視頻上。
.zIndex(STACK_STYLE.IMAGE_Z_INDEX)
}
if (this.isLoading) {
Progress({
value: STACK_STYLE.PROGRESS_VALUE,
total: STACK_STYLE.PROGRESS_TOTAL,
type: ProgressType.ScaleRing
})
.color(Color.Grey)
.value(this.progressVal)
.width(STACK_STYLE.PROGRESS_WIDTH)
.style({
strokeWidth: STACK_STYLE.PROGRESS_STROKE_WIDTH,
scaleCount: STACK_STYLE.PROGRESS_SCALE_COUNT,
scaleWidth: STACK_STYLE.PROGRESS_SCALE_WIDTH
})
.zIndex(STACK_STYLE.PROGRESS_Z_INDEX)
}
VideoPlayer({
source: this.source,
controller: this.controller
})
.zIndex(0)
}
}
.height(ALL_PERCENT)
.backgroundColor(Color.Black)
}
}
sf
-
視頻播放器
+關(guān)注
關(guān)注
0文章
33瀏覽量
11856 -
鴻蒙
+關(guān)注
關(guān)注
57文章
2339瀏覽量
42805 -
HarmonyOS
+關(guān)注
關(guān)注
79文章
1973瀏覽量
30143 -
OpenHarmony
+關(guān)注
關(guān)注
25文章
3713瀏覽量
16254
發(fā)布評論請先 登錄
相關(guān)推薦
評論