README.md
1# 短视频切换案例
2
3### 介绍
4
5本示例介绍了[@ohos.multimedia.media](https://gitee.com/openharmony/docs/blob/OpenHarmony-5.0.0-Release/zh-cn/application-dev/reference/apis-media-kit/js-apis-media.md)组件和[@ohos.window](https://gitee.com/openharmony/docs/blob/OpenHarmony-5.0.0-Release/zh-cn/application-dev/reference/apis-arkui/js-apis-window.md)接口以及使用[触摸热区](https://gitee.com/openharmony/docs/blob/OpenHarmony-5.0.0-Release/zh-cn/application-dev/reference/apis-arkui/arkui-ts/ts-universal-attributes-touch-target.md)实现视频横竖屏切换及进度条热区拖动的功能。
6该场景多用于横竖屏视频等媒体播放。
7
8### 效果图预览
9
10<img src="./entry/src/main/resources/base/media/video_screen_direction_switching.gif" width="300">
11
12**使用说明**:
13
14* 点击全屏观看按钮,切换横屏窗口。
15* 点击左上角返回按钮,恢复竖屏窗口。
16* 在进度条上方位置横向拖动可带动进度条移动。
17
18## 实现步骤
19
201. 初始化[@ohos.multimedia.media](https://gitee.com/openharmony/docs/blob/OpenHarmony-5.0.0-Release/zh-cn/application-dev/reference/apis-media-kit/js-apis-media.md)的AVPlayer。
21 ```ts
22 async Init(): Promise<void> {
23 await this.release();
24 const context = getContext(this);
25 // 获取fdSrc用于注册AVPlayer
26 context.resourceManager.getRawFd(this.fileName).then(async (value: resourceManager.RawFileDescriptor) => {
27 this.avPlayer = await media.createAVPlayer();
28 this.isCreate = true;
29 this.setSourceInfo(); // 视频信息上报函数
30 this.setStateChangeCallback(); // 状态机上报回调函数
31 this.avPlayer.fdSrc = {
32 fd: value.fd,
33 offset: value.offset,
34 length: value.length
35 };
36 });
37 }
38 ```
392. 当AVPlayer初始化完毕进入initialized状态时,将XComponent和AVPlayer通过surfaceId绑定,这样可以在XComponent组件内实现视频播放功能。比起Video组件,AVPlayer可以更方便自定义全屏动画效果。
40 ```ts
41 // TODO 知识点:XComponent和AVPlayer通过surfaceId绑定
42 setSurfaceID(): void {
43 logger.info('play video: surfaceID is:' + this.surfaceID);
44 this.avPlayer.surfaceId = this.surfaceID;
45 }
46 ```
473. 使用[AVPlayer](https://gitee.com/openharmony/docs/blob/OpenHarmony-5.0.0-Release/zh-cn/application-dev/reference/apis-media-kit/js-apis-media.md#avplayer9)中的width和height属性判断是横屏视频还是竖屏视频,方便判断是否需要展示**全屏观看**按钮。
48```ts
49 case 'prepared':
50 logger.info('state prepared called');
51 this.isPlaying = true; // 准备完成阶段 开始播放
52 this.totalDuration = this.avPlayer.duration; // 获取视频时长
53 this.aspect_ratio = this.avPlayer.width / this.avPlayer.height; // 获取视频宽高比
54 if(this.avPlayer.width >= this.avPlayer.height) { // 判断是横屏视频还是竖屏视频
55 this.verticalVideo = false;
56 } else {
57 this.verticalVideo = true;
58 }
59 this.getPlay();
60 break;
61```
624. 调用[@ohos.window](https://gitee.com/openharmony/docs/blob/OpenHarmony-5.0.0-Release/zh-cn/application-dev/reference/apis-arkui/js-apis-window.md)的 getLastWindow 方法获取当前应用内最上层的子窗口,若无应用子窗口,则返回应用主窗口。
635. 利用获取到的窗口对象,调用 setWindowSystemBarEnable 方法设置窗口是否显示导航栏和状态栏。
646. 调用窗口对象的 setPreferredOrientation 方法设置窗口旋转方向以及是否应用重力感应。
657. 调用窗口对象的setWindowLayoutFullScreen方法实现沉浸式布局。
66 ```ts
67 changeOrientation() {
68 // 获取UIAbility实例的上下文信息
69 let context = getContext(this);
70 // 调用该接口手动改变设备横竖屏状态(设置全屏模式,先强制横屏,再加上传感器模式)
71 window.getLastWindow(context).then((lastWindow) => {
72 if (this.isLandscape) {
73 // 设置窗口的布局是否为沉浸式布局
74 lastWindow.setWindowLayoutFullScreen(true, () => {
75 // 设置窗口全屏模式时导航栏、状态栏的可见模式
76 lastWindow.setWindowSystemBarEnable([]);
77 // 设置窗口的显示方向属性,AUTO_ROTATION_LANDSCAPE表示传感器自动横向旋转模式
78 lastWindow.setPreferredOrientation(window.Orientation.AUTO_ROTATION_LANDSCAPE);
79 });
80 } else {
81 // 设置窗口的显示方向属性,UNSPECIFIED表示未定义方向模式,由系统判定
82 lastWindow.setPreferredOrientation(window.Orientation.UNSPECIFIED, () => {
83 // 设置窗口全屏模式时导航栏、状态栏的可见模式
84 lastWindow.setWindowSystemBarEnable(WINDOW_SYSTEM_BAR, () => {
85 // 设置窗口的布局是否为沉浸式布局
86 lastWindow.setWindowLayoutFullScreen(false, () => {
87 setTimeout(() => {
88 // 设置退出全屏动画
89 animateTo({
90 duration: ANIMATE_DURATION,
91 onFinish: () => {
92 this.fileName = '';
93 }
94 }, () => {
95 this.isFullScreen = !this.isFullScreen;
96 });
97 }, TIMEOUT_DURATION);
98 });
99 });
100 });
101 }
102 });
103 }
104 ```
1058. 对进度条整个组件设置[触摸热区](https://gitee.com/openharmony/docs/blob/OpenHarmony-5.0.0-Release/zh-cn/application-dev/reference/apis-arkui/arkui-ts/ts-universal-attributes-touch-target.md)。
106 ```ts
107 Row() {
108 // ...
109 Slider({
110 value: Math.round(this.currentTime / this.totalDuration * 100)
111 })
112 .onChange((value: number, mode: SliderChangeMode) => {
113 if (this.isCreate) {
114 this.currentTime = this.totalDuration * value / 100;
115 this.avPlayer.seek(this.currentTime);
116 if (mode === SliderChangeMode.Moving) {
117 if (this.avPlayer.state === 'playing') {
118 this.getPause();
119 }
120 } else if (mode === SliderChangeMode.End) {
121 if (this.avPlayer.state === 'paused' && this.isPlaying) {
122 this.getPlay();
123 }
124 }
125 }
126 })
127 // ...
128 }
129 .hitTestBehavior(HitTestMode.Transparent) // 将组件的触摸测试类型设置为自身和子节点都响应触摸测试,不会阻塞兄弟节点的触摸测试,不会影响祖先节点的触摸测试。
130 .responseRegion( // 设置多个触摸热区
131 [
132 {
133 x: 0,
134 y: 0,
135 width: $r('app.string.video_screen_direction_switching_layout_100'),
136 height: $r('app.string.video_screen_direction_switching_layout_100')
137 },
138 {
139 x: 0,
140 y: $r('app.string.video_screen_direction_switching_layout_negative_200'),
141 width: $r('app.string.video_screen_direction_switching_layout_100'),
142 height: $r('app.string.video_screen_direction_switching_layout_200')
143 }
144 ]
145 )
146 .gesture( // 设置拖动手势,将距离变量与进度条变量进行绑定计算
147 PanGesture(new PanGestureOptions({
148 direction: PanDirection.Left | PanDirection.Right
149 }))
150 .onActionStart(() => {
151 this.flagValue = this.currentTime;
152 })
153 .onActionUpdate((event?: GestureEvent) => {
154 if (event) {
155 if (this.isCreate) {
156 if (this.avPlayer.state === 'playing') {
157 this.getPause();
158 }
159 this.currentTime = (this.flagValue + this.totalDuration * (event.offsetX / 3) / 100) > this.totalDuration ? this.totalDuration : (this.flagValue + this.totalDuration * (event.offsetX / 3) / 100);
160 }
161 }
162 })
163 .onActionEnd(() => {
164 if (this.isCreate) {
165 this.avPlayer.seek(this.currentTime);
166 if (this.avPlayer.state === 'paused' && this.isPlaying) {
167 this.getPlay();
168 }
169 }
170 })
171 )
172 ```
173
174### 高性能知识点
175
1761. 本示例使用了[LazyForEach](https://gitee.com/openharmony/docs/blob/OpenHarmony-5.0.0-Release/zh-cn/application-dev/quick-start/arkts-rendering-control-lazyforeach.md) 进行数据懒加载优化,以降低内存占用和渲染开销。
177
178### 工程结构&模块类型
179
180 ```
181 VideoSwitching // hap
182 |---component
183 | |---VideoComponent.ets // AVPlayer组件页面
184 |---model
185 | |---BasicDataSource.ets // 数据类型文件
186 |---pages
187 | |---Index.ets // 入口文件
188 |---util
189 | |---TimeTools.ets // 时间轴组件页面
190 |---view
191 | |---VideoScreenDirectionSwitching.ets // 视频横竖屏切换容器页面
192 ```
193
194### 参考资料
195
196[LazyForEach](https://gitee.com/openharmony/docs/blob/OpenHarmony-5.0.0-Release/zh-cn/application-dev/quick-start/arkts-rendering-control-lazyforeach.md)
197
198[@ohos.multimedia.media](https://gitee.com/openharmony/docs/blob/OpenHarmony-5.0.0-Release/zh-cn/application-dev/reference/apis-media-kit/js-apis-media.md)
199
200[@ohos.window](https://gitee.com/openharmony/docs/blob/OpenHarmony-5.0.0-Release/zh-cn/application-dev/reference/apis-arkui/js-apis-window.md)
201
202### 约束与限制
203
2041.本示例仅支持在标准系统上运行。
205
2062.本示例仅支持API12版本SDK,版本号:5.0.0.71。
207
2082.本示例需要使用DevEco Studio 5.0.0 Release 才可编译运行。
209
210### 下载
211
212如需单独下载本工程,执行如下命令:
213```typescript
214git init
215git config core.sparsecheckout true
216echo /code/BasicFeature/Media/VideoSwitching/ > .git/info/sparse-checkout
217git remote add origin https://gitee.com/openharmony/applications_app_samples.git
218git pull origin master
219```