# 基于SwipePlayer三方库的短视频流畅点播切换场景解决方案
## SwipePlayer简介
SwipePlayer三方库主要聚焦短视频流畅点播切换场景,提供短视频上下滑动切换和快速起播能力,同时通过自定义能力的开放满足应用不同短视频滑动场景的业务诉求。
SwipePlayer底层封装了系统AVPlayer及XComponent、Swiper、LazyForEach组件,基于AVPlayer的预创建,预加载以及提前起播实现了快速起播的能力。
SwipePlayer三方库通过对外提供一个SwipePlayer组件以及SwipePlayerController控制器对外提供能力,包括上下滑动、快速起播、动态添加数据源、
自定义跟随视频滑动的组件、自定义滑动组件切换、全屏和弹框等,目的是让开发者在开发过程中无需关注繁琐的AVPlayer创建、管理和播放逻辑,可以基于SliderPlayer
库能力快速实现短视频流畅滑动的场景开发体验,使开发者可以更加聚焦实际场景业务的开发。
## 特性
+ 支持网络视频(http/https)上下滑动及快速起播
+ 支持动态添加/刷新播放源数据
+ 支持自定义跟随视频滑动的组件
+ 支持自定义滑动组件切换
+ 支持自定义弹框(可自定义动画和弹框方向,提供默认动画实现)
+ 支持全屏/退出全屏播放,支持定义全屏播放界面组件
+ 支持进度条(通过组件形式提供)
## 依赖版本
HarmonyOS NEXT Beta1及以上
+ 手机版本:NEXT.0.0.26
## 下载安装
使用ohpm安装依赖
```shell
ohpm install @hadss/swipeplayer
```
或者按需在模块中配置运行时依赖,修改oh-package.json5
```json5
{
"dependencies": {
"@hadss/swipeplayer": "^1.0.0-rc.5"
}
}
```
## 快速开始
### 支持网络视频(http/https)上下滑动及快速起播
定义控制器SwipePlayerController,初始化数据源,初始化SwipePlayer入口组件即可实现短视频滑动和快速起播能力。
```typescript
@Entry
@Component
struct Index {
sourceUrls: Array<string > = [
'https://v-cdn.zjol.com.cn/276984.mp4',
'https://v-cdn.zjol.com.cn/276985.mp4',
'https://v-cdn.zjol.com.cn/276986.mp4'
]
// 1.定义控制器
swipePlayerController: SwipePlayerController = new SwipePlayerController();
dataSources: SwipePlayerIDataSource = new SwipePlayerIDataSource();
getRandomNumber(): number {
return Math.floor(Math.random() * 1000)
}
// 2.创建播放源
aboutToAppear(): void {
this.sourceUrls.forEach((item, i) => {
this.dataSources.pushData({
// 插入类型为视频
type: 'video',
// 视频源
sourcePath: item,
verticalScreenBuilder: {
// 传入竖屏页面的样式builder
customBuilder: () => wrapBuilder(viewScreen),
// 页面其他信息
dataObj: new VerticalScreenData(this.getRandomNumber(), this.getRandomNumber(), this.getRandomNumber(), false,
CommonConstant.PUBLISHER_IMAGE[i], CommonConstant.PUBLISHER_NAME[i],
CommonConstant.VIDEO_INFOMATION[i])
}
});
});
}
build() {
Column() {
// 3.创建入口组件
SwipePlayer({
datasource: this.dataSources,
swipePlayerController: this.swipePlayerController,
})
}
.width('100%')
.height('100%')
}
}
```
### 支持动态添加/刷新播放源数据
```typescript
@Entry
@Component
struct Index {
sourceUrls: Array<string > = [
'https://v-cdn.zjol.com.cn/276984.mp4',
'https://v-cdn.zjol.com.cn/276985.mp4',
'https://v-cdn.zjol.com.cn/276986.mp4'
]
// 1.定义控制器
swipePlayerController: SwipePlayerController = new SwipePlayerController();
dataSources: SwipePlayerIDataSource = new SwipePlayerIDataSource();
// 2.创建播放源
aboutToAppear(): void {
...
// 同上在此插入播放源
}
build() {
Column() {
// 3.创建入口组件
SwipePlayer({
datasource: this.dataSources,
swipePlayerController: this.swipePlayerController,
options: {
swiperCallback: {
onAnimationStart: (targetIndex: number) => {
this.dataSources.pushData({
type: 'video',
sourcePath: 'https://v-cdn.zjol.com.cn/276993.mp4',
verticalScreenBuilder: {
customBuilder: () => wrapBuilder(viewScreen),
dataObj: new VerticalScreenData(this.getRandomNumber(), this.getRandomNumber(),
this.getRandomNumber(), false,
CommonConstant.PUBLISHER_IMAGE[0], CommonConstant.PUBLISHER_NAME[0],
CommonConstant.VIDEO_INFOMATION[0])
}
})
}
}
}
})
}
.width('100%')
.height('100%')
// 下拉刷新视频
.gesture(
PanGesture()
.onActionStart((event: GestureEvent) => {
if (event.offsetY > 5 && this.swipePlayerController.curentIndex == 0) {
this.dataSources.reloadData([{ type: 'video', sourcePath: 'https://v-cdn.zjol.com.cn/276986.mp4' }])
}
})
)
}
}
```
### 在滑动视频组件上添加自定义组件
短视频滑动场景往往包含很多随视频一起滑动的组件,如点赞评论转发,视频内容描述等,由于不同的应用这些自定义组件的内容各不相同,SwipePlayer提供自定义能力,
可以通过传递customBuilder组件的形式定义这些随视频滑动的自定义组件。
```typescript
@Entry
@Component
struct Index {
sourceUrls: Array<string > = [
'https://v-cdn.zjol.com.cn/276984.mp4',
'https://v-cdn.zjol.com.cn/276985.mp4',
'https://v-cdn.zjol.com.cn/276986.mp4'
]
// 1.定义控制器
swipePlayerController: SwipePlayerController = new SwipePlayerController();
dataSources: SwipePlayerIDataSource = new SwipePlayerIDataSource();
getRandomNumber(): number {
return Math.floor(Math.random() * 1000)
}
// 2.创建播放源
aboutToAppear(): void {
this.sourceUrls.forEach((item, i) => {
this.dataSources.pushData({
type: 'video',
sourcePath: item,
verticalScreenBuilder: {
// 将VerticalScreenComponent导出成builder传入
customBuilder: () => wrapBuilder(viewScreen),
// 页面的点赞、收藏、分享等数据
dataObj: new VerticalScreenData(this.getRandomNumber(), this.getRandomNumber(), this.getRandomNumber(), false,
CommonConstant.PUBLISHER_IMAGE[i], CommonConstant.PUBLISHER_NAME[i],
CommonConstant.VIDEO_INFOMATION[i])
}
});
});
}
build() {
Column() {
// 3.创建入口组件
SwipePlayer({
datasource: this.dataSources,
swipePlayerController: this.swipePlayerController,
})
}
.width('100%')
.height('100%')
}
}
```
```typescript
// 竖屏页面样式
@Component
export struct VerticalScreenComponent {
build() {
RelativeContainer() {
...
}
.width('100%')
.height('100%')
}
}
```
### 自定义滑动切换组件(广告,图片轮播等)
短视频切换场景下除了视频播放场景外还可能存在广告页,图片轮播等场景和其他一些不可预期的页面,SwipePlayer提供自定义滑动切换组件的能力,
通过传递customBuilder组件的形式实现。
```typescript
@Entry
@Component
struct Index {
sourceUrls: Array<string > = [
'https://v-cdn.zjol.com.cn/276984.mp4',
'https://v-cdn.zjol.com.cn/276985.mp4',
'https://v-cdn.zjol.com.cn/276986.mp4'
]
// 1.定义控制器
swipePlayerController: SwipePlayerController = new SwipePlayerController();
dataSources: SwipePlayerIDataSource = new SwipePlayerIDataSource();
// 2.创建播放源
aboutToAppear(): void {
...
// 同上在此插入播放源
}
build() {
Column() {
// 3.创建入口组件
SwipePlayer({
datasource: this.dataSources,
swipePlayerController: this.swipePlayerController,
options: {
swiperCallback: {
onAnimationStart: (targetIndex: number) => {
let totalNum = this.dataSources.totalCount()
if (totalNum - targetIndex <= 5) {
if (targetIndex % 2 === 0) {
// 新增视频页
this.dataSources.pushData({
type: 'video',
sourcePath: 'https://v-cdn.zjol.com.cn/276993.mp4',
verticalScreenBuilder: {
customBuilder: () => wrapBuilder(viewScreen),
dataObj: new VerticalScreenData(this.getRandomNumber(), this.getRandomNumber(),
this.getRandomNumber(), false,
CommonConstant.PUBLISHER_IMAGE[0], CommonConstant.PUBLISHER_NAME[0],
CommonConstant.VIDEO_INFOMATION[0])
}
})
} else {
// 添加广告页
this.dataSources.pushData({
type: 'custom',
// AdvertBuilder写入广告页的样式即可
customBuilder: { customBuilder: () => wrapBuilder(AdvertBuilder) }
})
}
}
}
}
}
})
}
.width('100%')
.height('100%')
}
}
```
### 长按视频3s弹出倍速播放配置,选择倍速后弹出toast弹框提示并倍速播放视频
短视频滑动还涉及一些手势操作的业务场景,比如长按3s弹出倍速页面,可以通过组件提供的Gesture手势事件注册能力实现,
使用SwipePlayerController提供的弹框能力可以弹出自定义配置页面,应用还可以通过AVPlayer创建回调注册AVPlayer回调监听speedDone事件并在回调中弹出toast提示。
```typescript
// 竖屏页面样式
@Component
export struct VerticalScreenComponent {
build() {
RelativeContainer() {
...
}
.width('100%')
.height('100%')
// 页面长按弹出倍速弹窗
.gesture(
LongPressGesture()
.onAction(() => {
this.swipePlayerController.openDialog({
// FastSpeedBuilder倍速弹窗的样式
dialogBuilder: () => wrapBuilder(FastSpeedBuilder),
dataObj: this.swipeData
}, PushDirection.DOWN_TO_UP, 350, false);
})
);
}
}
```
```typescript
// 倍速弹窗
@Component
export struct FastSpeed {
private swipeData: SwipeData = null!;
build() {
RelativeContainer() {
...
Flex() {
ForEach(CommonConstant.VIDEO_FAST_SPEED, (item: number, commonIndex: number) => {
// 倍速的文字,例如1x,2x
Text(item + 'x')
.onClick(() => {
// 设置视频倍速
this.swipeData.swipePlayerController.setSpeed(item);
promptAction.openCustomDialog({
// toast提示窗样式
builder: () => this.clickSpeedBuilder(),
...
});
});
});
}
.width('100%');
...
}
}
}
}
```
### 点击评论按钮弹出评论页面,包含视频(缩放)上移动画,跟手动画
SwipePlayerController会封装点击页面弹框及视频动画并对外提供调用方法供应用使用,使用该方法时会弹出空白弹框,用户可以自定义弹框内容。
该弹框弹出式时会有默认视频动画(视频上移或缩放上移),收起弹框时默认有视频还原动画。
```typescript
@Entry
@Component
struct Index {
build{
SwipePlayer({
...
options: {
swiperCallback:{
onAnimationStart:() => {
this.dataSources.pushData({
...
verticalScreenBuilder: {
// 将VerticalScreenComponent导出成builder传入
customBuilder:() => wrapBuilder(viewScreen)
}
})
}
}
}
})
}
}
```
```typescript
// 竖屏页面样式
@Component
export struct VerticalScreenComponent {
@State swipePlayerController: SwipePlayerController = null!;
build() {
RelativeContainer() {
...
// 评论图标、评论数,点击拉起评论弹窗
Column() {
Image(CommonConstant.CUSTOM_IMAGE[1])
.width(CommonConstant.CUSTOM_IMAGE_WIDTH_OR_HEIGHT)
.height(CommonConstant.CUSTOM_IMAGE_WIDTH_OR_HEIGHT);
Text(this.verticalScreenData.commentsNum.toString())
}
.onClick(() => {
let withAnimation = false;
// 获取屏幕宽度,判断直板机和折叠屏的状态
if (this.displayWidth < CommonConstant.PHONE_WIDTH) {
withAnimation = true;
}
// 直板机和折叠屏折叠态,弹窗会压缩视频,否则不会压缩视频
this.swipePlayerController.openDialog({
// CommentsBuilder为评论区样式
dialogBuilder: () => wrapBuilder(CommentsBuilder),
dataObj: this.dialogComponentData
}, PushDirection.DOWN_TO_UP, 350, withAnimation)
});
...
}
}
.width('100%')
.height('100%')
}
}
```
### 横向视频全屏播放
播放横向适配时,会额外显示全屏播放按钮,点击后全屏播放,并显示返回键,标题,进度条等内容,使用SwipePlayerController提供的全屏和退出全屏能力结合自定义组件videoBuilder实现。
```typescript
// 页面样式
@Component
export struct VerticalScreenComponent {
build() {
RelativeContainer() {
...
// 全屏按钮
Text() {
Span(CommonConstant.REQUEST_FULL_SCREEN)
.fontWeight(CommonConstant.CUSTOM_FONT_WEIGHT)
.fontColor(Color.White);
Span(' ')
.letterSpacing(5);
ImageSpan(CommonConstant.CUSTOM_IMAGE[5])
.width(CommonConstant.FULL_SCREEN_TEXT_WIDTH);
}
.onClick(() => {
// 切换为横屏
this.dataSources.pushData({
type: 'video',
sourcePath: 'https://v-cdn.zjol.com.cn/276994.mp4',
fullScreenBuilder: {
// fullScreen横屏样式
customBuilder: () => wrapBuilder(fullScreen)
}
})
});
...
}
}
.width('100%')
.height('100%')
}
}
```
### 实现自定义进度条
某些情况下应用不想使用默认进度条组件,需要实现自定义的进度条,可以通过options配置禁用显示默认进度条,然后通过videoBuilder结合AVPlayer监听事件注册实现自定义进度条。
```typescript
@Entry
@Component
struct Index {
build{
SwipePlayer({
...
options: {
swiperCallback:{
onAnimationStart:() => {
this.dataSources.pushData({
...
verticalScreenBuilder: {
// 将VerticalScreenComponent变成builder传入
customBuilder:() => wrapBuilder(viewScreen)
}
})
}
}
}
})
}
}
```
```typescript
// 页面样式
@Component
export struct VerticalScreenComponent {
@State duration: number = 0;
@State currentTime: number = 0;
@State durationTime: number = 0;
@State isSliderMoving: boolean = false;
@State swipePlayerController: SwipePlayerController = null!;
build() {
RelativeContainer() {
...
// 自定义进度条
ProgressBar({
duration: this.duration,
currentTime: this.currentTime,
durationTime: this.durationTime,
isSliderMoving: this.isSliderMoving,
swipePlayerController: this.swipePlayerController,
trackColor: Color.Gray,
selectedColor: CommonConstant.SPEED_BUTTON_FONT_COLOR
})
...
}
}
.width('100%')
.height('100%')
}
}
```
## SwipePlayer接口和属性列表
### SwipePlayer: 短视频流畅点播切换入口组件
| 参数 | 说明 |
|-------------------------------------|---------------------------------------------------|
| dataSource: SwipePlayerDataSource | 用于LazyForEach遍历的SwipePlayer的播放源数据对象 |
| controller: SwipePlayerController | SwipePlayer的控制器,提供全屏/退出全屏,弹框/关闭弹框及AVPlayer相关控制能力 |
| options: SwipePlayerOption | SwipePlayer的参数配置,提供缓存数 |
### SwipePlayerIDataSource: LazyForEach遍历的数据源对象
| 参数 | 说明 |
|---------------------------------------------|-----------------------|
| dataSources: Array\<SwipePlayerDataSource\> | SwipePlayer的播放源数据列表 |
| 接口原型 | 参数 | 返回值 | 接口描述 |
|------------|-------------------------------------------------------------|-------------------------|--------------------|
| totalCount | | number | SwipePlayer播放源总数 |
| getData | index: number | SwipePlayerDataSource | 获取数据源对象 |
| pushData | data: SwipePlayerDataSource; Array\<SwipePlayerDataSource\> | void | 动态添加数据源 |
| reloadData | data: Array\<SwipePlayerDataSource\> | void | 重新刷新数据源 |
### VideoSwipePlayerDataSource: 视频播放类型数据源对象(实现SwipePlayerDataSource)
| 参数 | 说明 |
|-------------------------------------|---------------------------------------------------------------------|
| type: 'video | 短视频播放源类型 |
| sourcePath: string | 短视频播放源路径,支持http/https |
| videoBuilder?: CustomBuilder | 视频播放组件上的自定义组件(会跟随视频一起滑动) |
| fullScreenBuilder?: CustomerBuilder | 全屏播放时,视频组件上的自定义组件 |
| cover?: Resource | 视频封面,支持gif(在AVPlayer的initialize, prepared, playing期间显示,不设置默认显示黑屏画面) |
| pauseIcon?: Resource | 用户点击视频暂停时显示的图标(默认为透明白色三角) |
### CustomSwipePlayerDataSource: 自定义类型数据源对象(实现SwipePlayerDataSource)
| 参数 | 说明 |
|-------------------------------|----------------------------|
| type: 'custom' | 自定义播放类型源 |
| customBuilder?: CustomBuilder | 用户自定义的非视频类型的滑动组件(如广告,图片轮播) |
### CustomBuilder: 用户自定义组件对象
| 参数 | 说明 |
|------------------------------------------|---------------|
| customBuilder?: WrappedBuilder<[Object]> | 用户自定义组件 |
| dataObj?: object | 用户自定义组件匹配的数据源 |
### 配置参数: SwipePlayerOptions
| 参数 | 说明 |
|---------------------------------|----------------------------------|
| cachedCount | LazyForEach缓存数量,范围0-3,默认1 |
| swiperCallBack? SwiperCallback | SwipePlayer包装的Swiper组件的一些滑动回调注册 |
### SwiperCallback: Swiper回调注册
| 参数 | 说明 |
|--------------------------------------------------------------------------------------------------|-------------------------------|
| onAnimationStart?: (index: number, targetIndex: number, extraInfo: SwiperAnimationEvent) => void | 对应Swiper组件的onAnimationStart回调 |
| onAnimationEnd?: (index: number, extraInfo: SwiperAnimationEvent) => void | 对应Swiper组件的onAnimationEnd回调 |
| onChange?: (index: number) => void | 对应Swiper组件的onChange回调 |
### SwipePlayerController: SwipePlayer组件的控制器
| 接口原型 | 参数 | 返回值 | 接口描述 |
|-------------------|----------------------------------------------------------------------------------------------|----------------------|-----------------------------|
| getAVPlayer | | media.AVPlayer | 获取当前播放的AVPlayer对象,存在获取不到的情况 |
| async play | | void | 播放当前视频,有状态校验 |
| async pause | | void | 暂停播放当前视频,有状态校验 |
| seek | | void | 跳转到指定位置播放,有状态校验 |
| release | | void | 释放当前的AVPlayer对象,有状态校验 |
| setSpeed | speed: media.PlaybackSpeed | void | 设置视频的播放速度,有状态校验 |
| requestFullScreen | | void | 请求全屏播放,会隐藏videoBuilder组件 |
| exitFullScreen | | void | 退出全屏播放,会显示videoBuilder组件 |
| openDialog | builder: DialogBuilder, directionOrAnimationCallback: PushDirection \| OpenAnimationCallback | void | 打开自定义弹框页面,支持设置弹框方向和自定义动画 |
| closeDialog | builder: animation: CloseAnimationCallback | void | 关闭自定义页面弹框(可支持自定义动画) |
### PlayerSession: 储存AVPlayer对象及其对应的事件
| 接口原型 | 参数 | 返回值 | 接口描述 |
|---------|----------------------------------------------------|------|------|
| onEvent | key: string, callback: (...args: ESObject) => void | void | 注册回调 |
## 开源协议
本项目基于 Apache License 2.0,请自由地享受和参与开源。