diff --git a/AppScope/app.json5 b/AppScope/app.json5 index 83474faa86f70f58c8d2474835d9f19e51c85101..d1d2c5a73821a1cd135177cf6065c922f9d4df0c 100644 --- a/AppScope/app.json5 +++ b/AppScope/app.json5 @@ -15,12 +15,11 @@ { "app": { - "bundleName": "net.openvally.videoplay", + "bundleName": "com.example.avplayer_basic_control", "vendor": "example", "versionCode": 1000000, "versionName": "1.0.0", - "icon": "$media:app_icon", - "label": "$string:app_name", - "distributedNotificationEnabled": true + "icon": "$media:layered_image", + "label": "$string:app_name" } } diff --git a/AppScope/resources/base/element/string.json b/AppScope/resources/base/element/string.json index 890b5c1cef25ffee69bf80276ba3a0e3a560cbc3..e1ab609a842ddb871d7bc6d573ce77479bd7c9e9 100644 --- a/AppScope/resources/base/element/string.json +++ b/AppScope/resources/base/element/string.json @@ -2,7 +2,7 @@ "string": [ { "name": "app_name", - "value": "VideoPlay" + "value": "avplayer_basic_control" } ] } diff --git a/AppScope/resources/base/media/app_icon.png b/AppScope/resources/base/media/app_icon.png index ce307a8827bd75456441ceb57d530e4c8d45d36c..a39445dc87828b76fed6d2ec470dd455c45319e3 100644 Binary files a/AppScope/resources/base/media/app_icon.png and b/AppScope/resources/base/media/app_icon.png differ diff --git a/README.md b/README.md index 0b7de8605b514a2d0716f560e68977dd1d5a9e27..a105d314b8209b7abaa22d1d4a5d7e9b6a60e0d0 100644 --- a/README.md +++ b/README.md @@ -2,26 +2,17 @@ ## 项目简介 -本示例主要展示了如何基于AVPlayer系统播放器实现播放本地视频相关功能,指导开发者实现以下开发场景: - -- 视频加载、播放、暂停、退出 -- 跳转播放 -- 静音播放 -- 循环播放 -- 窗口缩放模式设置 -- 倍速设置 -- 音量设置 -- 字幕挂载 +本示例主要展示了如何基于AVPlayer系统播放器实现播放本地视频相关功能,指导开发者实现视频加载、播放、暂停、退出;跳转播放;静音播放;循环播放;窗口缩放模式设置;倍速设置;音量设置;字幕挂载等开发场景。 ## 效果预览 -| 播放 | 暂停 | 倍速弹窗 | -|--------------------------------------------|------------------------------------------|----------------------------------------------------| -| ![播放.png](screenshots/devices/playing.png) | ![暂停.png](screenshots/devices/pause.png) | ![img_2.png](screenshots/devices/speed_dialog.png) | +| 播放 | 暂停 | 倍速弹窗 | +|-----------------------------------------------------------------|------------------------------------------|----------------------------------------------------| +| | | | | 静音设置 | 音量设置 | 窗口缩放模式设置 | |------------------------------------------------------|-------------------------------------------------|----------------------------------------------------| -| ![静音设置.png](screenshots/devices/set_media_muted.png) | ![音量设置.png](screenshots/devices/set_volume.png) | ![窗口缩放模式设置.png](screenshots/devices/scale_fit.png) | +| | | | ## 使用说明 @@ -35,7 +26,7 @@ 8. 点击窗口缩放模式按钮,可以选择拉伸至与窗口等大、缩放至最短边填满窗口; 9. 长按屏幕,控制视频2.0倍速播放; 10. 上下滑动屏幕,可以设置视频播放音量; -11. 视频下方显示字幕; +11. 视频下方显示字幕,并可以点击语言切换按钮切换字幕; 12. 视频自动循环播放; 13. 点击左上角退出箭头,退出应用。 @@ -44,13 +35,13 @@ ``` ├──entry/src/main/ets // 代码区 │ ├──common -│ │ │──constants +│ │ ├──constants │ │ │ └──CommonConstants.ets // 公共常量 │ │ └──utils │ │ ├──GlobalContext.ets // 公共工具类 │ │ └──TimeUtils.ts // 视频时间帮助类 -│ ├──components -│ │ ├──ExitVideo.ets // 退出应用组件 +│ ├──views +│ │ ├──languageDialog.ets // 弹幕语言切换弹窗 │ │ ├──ScaleDialog.ets // 窗口缩放模式设置弹窗 │ │ ├──SetVolumn.ets // 设置音量组件 │ │ ├──SpeedDialog.ets // 播放倍速弹窗 @@ -63,12 +54,13 @@ │ │ └──VideoData.ets // 视频数据类 │ └──pages │ └──Index.ets // 首页视频界面 -└────entry/src/main/resources // 应用资源目录 +└──entry/src/main/resources // 应用资源目录 ``` ## 具体实现 -+ 视频倍速切换、暂停、播放、切换视频、视频跳转的功能接口都封装在AvPlayerController.ets,源码参考:[AvPlayerController.ets](entry/src/main/ets/controller/AvPlayerController.ets); ++ +视频倍速切换、暂停、播放、切换视频、视频跳转的功能接口都封装在AvPlayerController.ets,源码参考:[AvPlayerController.ets](entry/src/main/ets/controller/AvPlayerController.ets); + 使用media.createAVPlayer()来获取AVPlayer对象; + 倍速切换:选择不同倍速时调用avPlayer.setSpeed(speed: PlaybackSpeed); + 暂停、播放:点击暂停、播放按钮时调用avPlayer.pause()、avPlayer.play(); diff --git a/entry/src/main/ets/components/ExitVideo.ets b/entry/src/main/ets/components/ExitVideo.ets deleted file mode 100644 index 927dce1a29b0e232c8e4c3f1311fd60adb3d0e6d..0000000000000000000000000000000000000000 --- a/entry/src/main/ets/components/ExitVideo.ets +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { common } from '@kit.AbilityKit'; -import { GlobalContext } from '../common/utils/GlobalContext'; - -@Component -export struct ExitVideo { - @StorageLink('videoName') videoName: Resource = $r('app.string.video_res_1'); - - build() { - Row() { - // Exit - Column() { - Image($r('app.media.ic_video_back')) - .id('Exit') - .width($r('app.float.size_15')) - } - .width($r('app.float.size_40')) - .height($r('app.float.size_40')) - .borderRadius($r('app.float.size_20')) - .justifyContent(FlexAlign.Center) - .backgroundColor($r('app.color.back_button')) - .onClick(() => { - (GlobalContext.getContext().getObject('context') as (common.UIAbilityContext)).terminateSelf(); - }) - - Text(this.videoName) - .fontColor(Color.White) - .fontWeight(FontWeight.Medium) - .fontSize($r('app.float.size_24')) - .margin({ left: $r('app.float.size_16') }) - } - .margin({ top: $r('app.float.size_20'), left: $r('app.float.size_25') }) - } -} \ No newline at end of file diff --git a/entry/src/main/ets/controller/AvPlayerController.ets b/entry/src/main/ets/controller/AvPlayerController.ets index d8c27ce7ea64f8c992f1623745240a192a51fc45..585bfbd8099556ab64451471baa8219951eb7f5d 100644 --- a/entry/src/main/ets/controller/AvPlayerController.ets +++ b/entry/src/main/ets/controller/AvPlayerController.ets @@ -49,7 +49,7 @@ export class AvPlayerController { // [Start create_instance] // Create an AVPlayer instance - public async initAVPlayer(source: VideoData, surfaceId: string) { + public async initAVPlayer(source: VideoData, surfaceId: string, avPlayer?: media.AVPlayer) { if (!this.context) { hilog.info(CommonConstants.LOG_DOMAIN, TAG, `initPlayer failed context not set`); return @@ -75,7 +75,7 @@ export class AvPlayerController { try { hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'initPlayer videoPlay avPlayerDemo'); // Creates the avPlayer instance object. - this.avPlayer = await media.createAVPlayer(); + this.avPlayer = avPlayer ? avPlayer : await media.createAVPlayer() // Creates a callback function for state machine changes. this.setAVPlayerCallback(); hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'initPlayer videoPlay setAVPlayerCallback'); @@ -122,7 +122,7 @@ export class AvPlayerController { this.avPlayer.addSubtitleFromFd(fileDescriptorSub.fd, fileDescriptorSub.offset, fileDescriptorSub.length); hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'initPlayer videoPlay addSubtitleFromFd'); } - // [Start AddCaption] + // [End AddCaption] } catch (err) { hilog.error(CommonConstants.LOG_DOMAIN, TAG, `initPlayer initPlayer, code is ${err.code}, message is ${err.message}`); @@ -153,7 +153,7 @@ export class AvPlayerController { this.currentTime = time; AppStorage.setOrCreate('CurrentTime', time); hilog.info(CommonConstants.LOG_DOMAIN, TAG, - `setAVPlayerCallback timeUpdate success,and new time is = ${this.currentTime}`); + `setAVPlayerCallback timeUpdate success, and new time is = ${this.currentTime}`); }); // The error callback function is triggered when an error occurs during avPlayer operations, @@ -166,21 +166,7 @@ export class AvPlayerController { `Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`); this.avPlayer.reset(); // resets the resources and triggers the idle state }) - // [Start RegisterCaptionCallBack] - this.avPlayer.on('subtitleUpdate', (info: media.SubtitleInfo) => { - if (!!info) { - let text = (!info.text) ? '' : info.text; - let startTime = (!info.startTime) ? 0 : info.startTime; - let duration = (!info.duration) ? 0 : info.duration; - this.currentCaption = text; //update current caption content - hilog.info(CommonConstants.LOG_DOMAIN, TAG, - `subtitleUpdate info: text:${text},startTime:${startTime},duration:${duration}`); - } else { - this.currentCaption = ''; - hilog.error(CommonConstants.LOG_DOMAIN, TAG, 'subtitleUpdate info is null'); - } - }); - // [End RegisterCaptionCallBack] + this.subtitleUpdateFunction(); this.setStateChangeCallback(); } @@ -199,8 +185,8 @@ export class AvPlayerController { } switch (state) { // DocsDot + // [StartExclude state] case 'idle': // This state machine is triggered after the reset interface is successfully invoked. - this.avPlayer.release(); hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'setAVPlayerCallback AVPlayer state idle called.'); break; case 'initialized': // This status is reported after the playback source is set on the AVPlayer. @@ -211,12 +197,14 @@ export class AvPlayerController { `setAVPlayerCallback this.avPlayer.surfaceId = ${this.avPlayer.surfaceId}`); this.avPlayer.prepare(); break; - // DocsDot + // [EndExclude state] + // DocsDot case 'prepared': // This state machine is reported after the prepare interface is successfully invoked. hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'setAVPlayerCallback AVPlayer state prepared called.'); this.isReady = true; this.avPlayer.loop = true // DocsDot + // [StartExclude prepared] this.durationTime = this.avPlayer.duration; this.currentTime = this.avPlayer.currentTime; this.avPlayer.audioInterruptMode = audio.InterruptMode.SHARE_MODE; @@ -240,9 +228,11 @@ export class AvPlayerController { } this.setVideoSpeed(); + // [EndExclude prepared] // DocsDot break; - // DocsDot + // DocsDot + // [StartExclude other_state] case 'playing': // After the play interface is successfully invoked, the state machine is reported. hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'setAVPlayerCallback AVPlayer state playing called.'); this.isPlaying = true; @@ -274,6 +264,7 @@ export class AvPlayerController { default: hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'setAVPlayerCallback AVPlayer state unknown called.'); break; + // [EndExclude other_state] // DocsDot } }); @@ -296,16 +287,16 @@ export class AvPlayerController { private setVideoSpeed() { switch (this.speedSelect) { case CASE_ZERO: - this.videoSpeedOne(); + this.videoSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_00_X); break; case CASE_ONE: - this.videoSpeedOnePointTwentyFive(); + this.videoSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_25_X); break; case CASE_TWO: - this.videoSpeedOnePointSeventyFive(); + this.videoSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_75_X); break; case CASE_THREE: - this.videoSpeedTwo(); + this.videoSpeed(media.PlaybackSpeed.SPEED_FORWARD_2_00_X); break; default: break; @@ -331,7 +322,7 @@ export class AvPlayerController { this.isPlaying = false; hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'videoPause'); } catch (err) { - hilog.info(CommonConstants.LOG_DOMAIN, TAG, + hilog.error(CommonConstants.LOG_DOMAIN, TAG, `videoPause failed, code is ${err.code}, message is ${err.message}`); } } @@ -345,7 +336,7 @@ export class AvPlayerController { this.isPlaying = false; hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'videoPause'); } catch (err) { - hilog.info(CommonConstants.LOG_DOMAIN, TAG, + hilog.error(CommonConstants.LOG_DOMAIN, TAG, `videoPause failed, code is ${err.code}, message is ${err.message}`); } } @@ -364,7 +355,7 @@ export class AvPlayerController { await this.avPlayer!.setMediaMuted(media.MediaType.MEDIA_TYPE_AUD, isMuted) hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'videoMuted'); } catch (err) { - hilog.info(CommonConstants.LOG_DOMAIN, TAG, + hilog.error(CommonConstants.LOG_DOMAIN, TAG, `videoMuted failed, code is ${err.code}, message is ${err.message}`); } } @@ -373,61 +364,18 @@ export class AvPlayerController { // [End video_muted_fun] // [Start video_speed_fun] - - // [Start video_speed_1.0X_fun] - videoSpeedOne(): void { + videoSpeed(speed: number): void { if (this.avPlayer) { try { - this.avPlayer.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_00_X); - hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'videoSpeed_1_00'); + this.avPlayer.setSpeed(speed); + hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'videoSpeed'); } catch (err) { - hilog.info(CommonConstants.LOG_DOMAIN, TAG, - `videoSpeed_1_00 failed, code is ${err.code}, message is ${err.message}`); - } - } - } - - // [End video_speed_1.0X_fun] - - videoSpeedOnePointTwentyFive(): void { - if (this.avPlayer) { - try { - this.avPlayer.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_25_X); - hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'videoSpeed_1_25'); - } catch (err) { - hilog.info(CommonConstants.LOG_DOMAIN, TAG, - `videoSpeed_1_25 failed, code is ${err.code}, message is ${err.message}`); - } - } - } - - videoSpeedOnePointSeventyFive(): void { - if (this.avPlayer) { - try { - this.avPlayer.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_75_X); - hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'videoSpeed_1_75'); - } catch (err) { - hilog.info(CommonConstants.LOG_DOMAIN, TAG, - `videoSpeed_1_75 failed, code is ${err.code}, message is ${err.message}`); - } - } - } - - // [Start video_speed_2.0X_fun] - videoSpeedTwo(): void { - if (this.avPlayer) { - try { - this.avPlayer.setSpeed(media.PlaybackSpeed.SPEED_FORWARD_2_00_X); - hilog.info(CommonConstants.LOG_DOMAIN, TAG, `videoSpeed_2_0`); - } catch (err) { - hilog.info(CommonConstants.LOG_DOMAIN, TAG, - `videoSpeed_2_0 failed, code is ${err.code}, message is ${err.message}`); + hilog.error(CommonConstants.LOG_DOMAIN, TAG, + `videoSpeed failed, code is ${err.code}, message is ${err.message}`); } } } - // [End video_speed_2.0X_fun] - // [End video_speed_fun] videoSeek(seekTime: number): void { @@ -436,7 +384,8 @@ export class AvPlayerController { this.avPlayer.seek(seekTime, media.SeekMode.SEEK_CLOSEST); hilog.info(CommonConstants.LOG_DOMAIN, TAG, `videoSeek== ${seekTime}`); } catch (err) { - hilog.info(CommonConstants.LOG_DOMAIN, TAG, `videoSeek failed, code is ${err.code}, message is ${err.message}`); + hilog.error(CommonConstants.LOG_DOMAIN, TAG, + `videoSeek failed, code is ${err.code}, message is ${err.message}`); } } } @@ -448,7 +397,7 @@ export class AvPlayerController { try { await this.avPlayer.reset(); } catch (err) { - hilog.info(CommonConstants.LOG_DOMAIN, TAG, `videoReset failed, code is ${err.code}, message is ${err.message}`); + hilog.error(CommonConstants.LOG_DOMAIN, TAG, `videoReset failed, code is ${err.code}, message is ${err.message}`); } } @@ -458,7 +407,7 @@ export class AvPlayerController { return; } this.avPlayer.release((err) => { - if (err == null) { + if (err === null) { hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'videoRelease release success'); } else { hilog.error(CommonConstants.LOG_DOMAIN, TAG, @@ -485,7 +434,7 @@ export class AvPlayerController { this.avPlayer.videoScaleType = media.VideoScaleType.VIDEO_SCALE_TYPE_FIT hilog.info(CommonConstants.LOG_DOMAIN, TAG, `videoScaleType_0`); } catch (err) { - hilog.info(CommonConstants.LOG_DOMAIN, TAG, + hilog.error(CommonConstants.LOG_DOMAIN, TAG, `videoScaleType_0 failed, code is ${err.code}, message is ${err.message}`); } } @@ -497,11 +446,55 @@ export class AvPlayerController { this.avPlayer.videoScaleType = media.VideoScaleType.VIDEO_SCALE_TYPE_FIT_CROP hilog.info(CommonConstants.LOG_DOMAIN, TAG, `videoScaleType_1`); } catch (err) { - hilog.info(CommonConstants.LOG_DOMAIN, TAG, + hilog.error(CommonConstants.LOG_DOMAIN, TAG, `videoScaleType_1 failed, code is ${err.code}, message is ${err.message}`); } } } // [End window_scale_fun] + subtitleUpdateFunction(): void { + try { + if (this.avPlayer) { + // [Start RegisterCaptionCallBack] + this.avPlayer.on('subtitleUpdate', (info: media.SubtitleInfo) => { + if (info) { + let text = (!info.text) ? '' : info.text; + let startTime = (!info.startTime) ? 0 : info.startTime; + let duration = (!info.duration) ? 0 : info.duration; + this.currentCaption = text; //update current caption content + hilog.info(CommonConstants.LOG_DOMAIN, TAG, + `subtitleUpdate info: text:${text}, startTime:${startTime}, duration:${duration}`); + } else { + this.currentCaption = ''; + hilog.error(CommonConstants.LOG_DOMAIN, TAG, 'subtitleUpdate info is null'); + } + }); + // [End RegisterCaptionCallBack] + } + } catch (err) { + hilog.error(CommonConstants.LOG_DOMAIN, TAG, + `subtitleUpdateFunction failed, code is ${err.code}, message is ${err.message}`); + } + } + + // [Start languageSwitch] + async languageChange(languageSelect: number = 0): Promise { + if (this.avPlayer) { + try { + if (this.curSource && this.curSource.caption) { + this.curSource.caption = languageSelect === 0 ? 'captions.srt' : 'en_captions.srt' + this.curSource.seekTime = this.avPlayer.currentTime; + await this.avPlayer.reset(); + this.initAVPlayer(this.curSource, this.surfaceID, this.avPlayer); + hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'language change'); + } + } catch (err) { + hilog.error(CommonConstants.LOG_DOMAIN, TAG, + `languageChange failed, code is ${err.code}, message is ${err.message}`); + } + } + } + + // [End languageSwitch] } \ No newline at end of file diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets index 7546edc89e0c327533ac83f104165147cb60f946..9d939aec0e89269e2237344f3169a51914e37155 100644 --- a/entry/src/main/ets/entryability/EntryAbility.ets +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -23,8 +23,6 @@ import { CommonConstants } from '../common/constants/CommonConstants'; const TAG = '[EntryAbility]'; export default class EntryAbility extends UIAbility { - - onCreate(want: Want) { GlobalContext.getContext().setObject('abilityWant', want) GlobalContext.getContext().setObject('context', this.context) @@ -44,29 +42,36 @@ export default class EntryAbility extends UIAbility { onWindowStageCreate(windowStage: window.WindowStage): void { hilog.info(CommonConstants.LOG_DOMAIN, TAG, '%{public}s', 'Ability onWindowStageCreate'); - windowStage.getMainWindow().then((win: window.Window) => { - win.setWindowKeepScreenOn(true); - win.setWindowSystemBarProperties({ - statusBarColor: '#000000', - statusBarContentColor: '#FFFFFF' - }); - win.setWindowLayoutFullScreen(true); - win.on('windowSizeChange', (newSize: window.Size) => { - let eventWHData: emitter.EventData = { - data: { - 'width': newSize.width, - 'height': newSize.height - } - }; - emitter.emit(CommonConstants.innerEventWH, eventWHData); + try { + windowStage.getMainWindow().then((win: window.Window) => { + win.setWindowKeepScreenOn(true); + win.setWindowSystemBarProperties({ + statusBarColor: '#000000', + statusBarContentColor: '#FFFFFF' + }); + win.setWindowLayoutFullScreen(true); + win.on('windowSizeChange', (newSize: window.Size) => { + let eventWHData: emitter.EventData = { + data: { + 'width': newSize.width, + 'height': newSize.height + } + }; + emitter.emit(CommonConstants.innerEventWH, eventWHData); + }); }); - }); + } catch (err) { + hilog.error(CommonConstants.LOG_DOMAIN, TAG, + `getMainWindow failed, code is ${err.code}, message is ${err.message}`); + } windowStage.loadContent('pages/Index', (err, data) => { if (err.code) { - hilog.error(CommonConstants.LOG_DOMAIN, TAG, 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + hilog.error(CommonConstants.LOG_DOMAIN, TAG, 'Failed to load the content. Cause: %{public}s', + JSON.stringify(err) ?? ''); return; } - hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? ''); + hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'Succeeded in loading the content. Data: %{public}s', + JSON.stringify(data) ?? ''); }); } diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets index 2a15a9c978c914fc065c0b5230ae64d18b6d469d..eb581110dfbb861f8b94718ebcbb5635aa1d36ac 100644 --- a/entry/src/main/ets/pages/Index.ets +++ b/entry/src/main/ets/pages/Index.ets @@ -17,10 +17,10 @@ import { connection } from '@kit.NetworkKit'; import { display } from '@kit.ArkUI'; import { emitter } from '@kit.BasicServicesKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; +import { media } from '@kit.MediaKit'; import { AvPlayerController } from '../controller/AvPlayerController'; -import { VideoOperate } from '../components/VideoOperate'; -import { ExitVideo } from '../components/ExitVideo'; -import { SetVolume } from '../components/SetVolume' +import { VideoOperate } from '../views/VideoOperate'; +import { SetVolume } from '../views/SetVolume' import { timeConvert } from '../common/utils/TimeUtils'; import { GlobalContext } from '../common/utils/GlobalContext'; import { VideoDataType, CommonConstants } from '../common/constants/CommonConstants'; @@ -34,6 +34,8 @@ const SET_TIME_OUT = 8000; // Interval: 8s const SET_INTERVAL = 100; const SET_VOLUME_TIME_OUT = 5000 // VolumeTimer: 5s const TAG = '[Index]'; +const CASE_ZERO = 0; +const CASE_THREE = 3; @Entry @Component @@ -43,13 +45,15 @@ struct Index { @State isClickScreen: boolean = false; @State flag: boolean = true; // Pause Playback @State XComponentFlag: boolean = false; - @State speedSelect: number = 0; - @State videoListSelect: number = 0; + @State speedList: Resource[] = + [$r('app.string.video_speed_1_0X'), $r('app.string.video_speed_1_25X'), $r('app.string.video_speed_1_75X'), + $r('app.string.video_speed_2_0X')]; + @State @Watch('onSpeedSelectUpdate') speedSelect: number = 0; @State durationTime: number = 0; @State currentTime: number = 0; @State surfaceW: number = 0; @State surfaceH: number = 0; - @State volume: number = 5; + @State @Watch('onVolumeUpdate') volume: number = 5; @State volumeVisible: boolean = false @State show: boolean = false; // Indicates whether the videoPanel component is displayed. @State percent: number = 0; @@ -92,8 +96,13 @@ struct Index { } aboutToAppear() { - this.windowWidth = display.getDefaultDisplaySync().width; - this.windowHeight = display.getDefaultDisplaySync().height; + try { + this.windowWidth = display.getDefaultDisplaySync().width; + this.windowHeight = display.getDefaultDisplaySync().height; + } catch (err) { + hilog.error(CommonConstants.LOG_DOMAIN, TAG, + `getDefaultDisplaySync failed, code is ${err.code}, message is ${err.message}`); + } this.surfaceW = (GlobalContext.getContext().getObject('windowWidth') as number) * SURFACE_WIDTH; this.surfaceH = this.surfaceW / SURFACE_HEIGHT; this.flag = true; @@ -142,7 +151,7 @@ struct Index { this.setVideoWH(); } }); - if (this.flag == false) { + if (this.flag === false) { this.clearTimer(); } } @@ -157,19 +166,39 @@ struct Index { } } + onSpeedSelectUpdate() { + AppStorage.setOrCreate('speedName', this.speedList[this.speedSelect]); + AppStorage.setOrCreate('speedIndex', this.speedSelect); + } + + onVolumeUpdate() { + AppStorage.setOrCreate('isMuted', this.volume <= 0.0); + this.avPlayerController.videoMuted(this.volume <= 0.0); + } + async isInternet(): Promise { - if (connection.getAllNetsSync().length <= 0) { - this.toast(); - return false + try { + if (connection.getAllNetsSync().length <= 0) { + this.toast(); + return false + } + } catch (err) { + hilog.error(CommonConstants.LOG_DOMAIN, TAG, + `getAllNetsSync failed, code is ${err.code}, message is ${err.message}`); } return true; } async toast() { - this.getUIContext().getPromptAction().showToast({ - message: $r('app.string.video_warn'), - duration: 2000, - }); + try { + this.getUIContext().getPromptAction().showToast({ + message: $r('app.string.video_warn'), + duration: 2000, + }); + } catch (err) { + hilog.error(CommonConstants.LOG_DOMAIN, TAG, + `showToast failed, code is ${err.code}, message is ${err.message}`); + } } @Builder @@ -201,8 +230,9 @@ struct Index { SetVolume({ volume: this.volume, volumeVisible: this.volumeVisible }) Column() { + // [Start currentCaptionText] Stack({ alignContent: Alignment.Center }) { - Text(this.avPlayerController.currentCaption || '') + Text(this.avPlayerController.currentCaption) .fontColor(Color.White) .fontSize($r('app.float.size_20')) .fontFamily('Sans') @@ -211,6 +241,8 @@ struct Index { .position({ x: $r('app.float.size_zero'), y: $r('app.float.size_210') }) .zIndex(1) + // [End currentCaptionText] + this.CoverXComponent() } // [Start pan_gesture] @@ -223,8 +255,6 @@ struct Index { let curVolume = this.volume - this.getUIContext().vp2px(event.offsetY) / this.windowHeight; curVolume = curVolume >= 15.0 ? 15.0 : curVolume; curVolume = curVolume <= 0.0 ? 0.0 : curVolume; - AppStorage.setOrCreate('isMuted', curVolume <= 0.0); - this.avPlayerController.videoMuted(curVolume <= 0.0); this.volume = curVolume; hilog.info(CommonConstants.LOG_DOMAIN, TAG, 'AVPlayManage', 'AVPlayer', `this volumn is: ` + this.volume); }) @@ -260,12 +290,6 @@ struct Index { .visibility(this.isSwiping ? Visibility.Visible : Visibility.Hidden) Column() { - Row() { - ExitVideo() - } - .width('100%') - .justifyContent(FlexAlign.Start) - Blank() Column() { // Progress bar @@ -282,12 +306,12 @@ struct Index { .justifyContent(FlexAlign.Center) } .onTouch((event: TouchEvent) => { - if (event.type == TouchType.Down) { + if (event.type === TouchType.Down) { this.isClickScreen = true; this.clearTimer(); - } else if (event.type == TouchType.Up) { + } else if (event.type === TouchType.Up) { this.setTimer(); - } else if (event.type == TouchType.Move) { + } else if (event.type === TouchType.Move) { this.isClickScreen = true; this.clearTimer(); } @@ -308,14 +332,12 @@ struct Index { .gesture( LongPressGesture({ repeat: true }) .onAction(() => { - this.avPlayerController.videoSpeedTwo(); - AppStorage.setOrCreate('speedName', $r('app.string.video_speed_2_0X')); - AppStorage.setOrCreate('speedIndex', 3); + this.speedSelect = CASE_THREE + this.avPlayerController.videoSpeed(media.PlaybackSpeed.SPEED_FORWARD_2_00_X); }) .onActionEnd(() => { - this.avPlayerController.videoSpeedOne(); - AppStorage.setOrCreate('speedName', $r('app.string.video_speed_1_0X')); - AppStorage.setOrCreate('speedIndex', 0); + this.speedSelect = CASE_ZERO + this.avPlayerController.videoSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_00_X); }) ) // [End long_press_gesture] diff --git a/entry/src/main/ets/views/LanguageDialog.ets b/entry/src/main/ets/views/LanguageDialog.ets new file mode 100644 index 0000000000000000000000000000000000000000..9fe6e4a1a385389e381724296401bf2b662e057e --- /dev/null +++ b/entry/src/main/ets/views/LanguageDialog.ets @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AvPlayerController } from '../controller/AvPlayerController'; + +// Index of the playback rate list. +const ONE = 1; + +/** + * Set language dialog + */ +@CustomDialog +export struct LanguageDialog { + @State languageList: Resource[] = + [$r('app.string.Chinese'), $r('app.string.English')]; + @Link @Watch('onLanguageSelectUpdate') languageSelect: number; // Index of the current selection + @StorageLink('avPlayerController') avPlayerController: AvPlayerController = new AvPlayerController(); + private controller: CustomDialogController; + + onLanguageSelectUpdate() { + AppStorage.setOrCreate('currentLanguageType', this.languageSelect); + } + + build() { + Column() { + Text($r('app.string.language_switch')) + .fontSize($r('app.float.size_20')) + .fontWeight(FontWeight.Bold) + .width('90%') + .fontColor(Color.Black) + .textAlign(TextAlign.Center) + .margin({ top: $r('app.float.size_20'), bottom: $r('app.float.size_12') }) + + // [Start video_language_dialog] + List() { + ForEach(this.languageList, (item: Resource, index) => { + ListItem() { + Column() { + Row() { + Text(item) + // DocsDot + // [StartExclude text_style2] + .fontSize($r('app.float.size_16')) + .fontColor(Color.Black) + .fontWeight(FontWeight.Medium) + .textAlign(TextAlign.Center) + // [EndExclude text_style2] + // DocsDot + Blank() + Image(this.languageSelect === index ? $r('app.media.ic_radio_selected') : + $r('app.media.ic_radio')) + // DocsDot + // [StartExclude text_style3] + .width($r('app.float.size_24')) + .height($r('app.float.size_24')) + .objectFit(ImageFit.Contain) + // [EndExclude text_style3] + // DocsDot + } + // DocsDot + // [StartExclude text_style4] + .width('100%') + + if (index != this.languageList.length - ONE) { + Divider() + .vertical(false) + .strokeWidth(1) + .margin({ top: $r('app.float.size_10') }) + .color($r('app.color.speed_dialog')) + .width('100%') + } + // [EndExclude text_style4] + // DocsDot + } + .width('90%') + } + .width('100%') + .height($r('app.float.size_48')) + .onClick(() => { + this.languageSelect = index; + this.avPlayerController.languageChange(this.languageSelect); + this.controller.close(); + }) + }, (item: Resource, index) => index + '_' + JSON.stringify(item)) + } + + // [End video_language_dialog] + .width('100%') + .height('192vp') + .margin({ + top: $r('app.float.size_12') + }) + + Row() { + Text($r('app.string.dialog_cancel')) + .fontSize($r('app.float.size_16')) + .fontColor('#0A59F7') + .fontWeight(FontWeight.Medium) + .layoutWeight(1) + .textAlign(TextAlign.Center) + .onClick(() => { + this.controller.close(); + }) + } + .alignItems(VerticalAlign.Center) + .height($r('app.float.size_50')) + .padding({ bottom: $r('app.float.size_5') }) + .width('100%') + + } + .alignItems(HorizontalAlign.Center) + .width('100%') + .margin({ left: $r('app.float.size_16'), right: $r('app.float.size_16') }) + .borderRadius($r('app.float.size_24')) + .backgroundColor(Color.White) + + } +} \ No newline at end of file diff --git a/entry/src/main/ets/components/ScaleDialog.ets b/entry/src/main/ets/views/ScaleDialog.ets similarity index 82% rename from entry/src/main/ets/components/ScaleDialog.ets rename to entry/src/main/ets/views/ScaleDialog.ets index 0aacaa42dc004c939a4dfe492ddff032972d969a..91ff6a7230e9f7f24ac5fc63aab875a7d1ffced4 100644 --- a/entry/src/main/ets/components/ScaleDialog.ets +++ b/entry/src/main/ets/views/ScaleDialog.ets @@ -19,7 +19,6 @@ import { AvPlayerController } from '../controller/AvPlayerController'; const ZERO = 0; const ONE = 1; -// [Start window_scale_dialog] /** * Window scale dialog */ @@ -27,42 +26,52 @@ const ONE = 1; export struct ScaleDialog { @State scaleList: Resource[] = [$r('app.string.video_scale_fit'), $r('app.string.video_scale_fit_crop')]; - @Link windowScaleSelect: number; // Index of the current selection + @Link @Watch('onWindowScaleSelectUpdate') windowScaleSelect: number; // Index of the current selection @StorageLink('avPlayerController') avPlayerController: AvPlayerController = new AvPlayerController(); private controller: CustomDialogController; + onWindowScaleSelectUpdate() { + AppStorage.setOrCreate('videoScaleType', this.windowScaleSelect); + } + build() { Column() { - Text($r('app.string.dialog_play_scale'))// DocsDot + Text($r('app.string.dialog_play_scale')) .fontSize($r('app.float.size_20')) .fontWeight(FontWeight.Bold) .width('90%') .fontColor(Color.Black) .textAlign(TextAlign.Center) .margin({ top: $r('app.float.size_20'), bottom: $r('app.float.size_12') }) - // DocsDot + // [Start window_scale_dialog] List() { ForEach(this.scaleList, (item: Resource, index) => { ListItem() { Column() { Row() { - Text(item)// DocsDot + Text(item) + // DocsDot + // [StartExclude text_style2] .fontSize($r('app.float.size_16')) .fontColor(Color.Black) .fontWeight(FontWeight.Medium) .textAlign(TextAlign.Center) + // [EndExclude text_style2] // DocsDot Blank() - Image(this.windowScaleSelect == index ? $r('app.media.ic_radio_selected') : - $r('app.media.ic_radio'))// DocsDot + Image(this.windowScaleSelect === index ? $r('app.media.ic_radio_selected') : + $r('app.media.ic_radio')) + // DocsDot + // [StartExclude text_style3] .width($r('app.float.size_24')) .height($r('app.float.size_24')) .objectFit(ImageFit.Contain) + // [EndExclude text_style3] // DocsDot } // DocsDot + // [StartExclude text_style4] .width('100%') - if (index != this.scaleList.length - ONE) { Divider() .vertical(false) @@ -71,6 +80,7 @@ export struct ScaleDialog { .color($r('app.color.speed_dialog')) .width('100%') } + // [EndExclude text_style4] // DocsDot } .width('90%') @@ -79,7 +89,6 @@ export struct ScaleDialog { .height($r('app.float.size_48')) .onClick(() => { this.windowScaleSelect = index; - AppStorage.setOrCreate('videoScaleType', this.windowScaleSelect); switch (this.windowScaleSelect) { case ZERO: this.avPlayerController.videoScaleFit(); @@ -94,7 +103,7 @@ export struct ScaleDialog { }) }) } - // DocsDot + // [End window_scale_dialog] .width('100%') .height('192vp') .margin({ @@ -116,18 +125,12 @@ export struct ScaleDialog { .height($r('app.float.size_50')) .padding({ bottom: $r('app.float.size_5') }) .width('100%') - - // DocsDot } - // DocsDot .alignItems(HorizontalAlign.Center) .width('100%') .margin({ left: $r('app.float.size_16'), right: $r('app.float.size_16') }) .borderRadius($r('app.float.size_24')) .backgroundColor(Color.White) - // DocsDot } -} - -// [End window_scale_dialog] \ No newline at end of file +} \ No newline at end of file diff --git a/entry/src/main/ets/components/SetVolume.ets b/entry/src/main/ets/views/SetVolume.ets similarity index 100% rename from entry/src/main/ets/components/SetVolume.ets rename to entry/src/main/ets/views/SetVolume.ets diff --git a/entry/src/main/ets/components/SpeedDialog.ets b/entry/src/main/ets/views/SpeedDialog.ets similarity index 75% rename from entry/src/main/ets/components/SpeedDialog.ets rename to entry/src/main/ets/views/SpeedDialog.ets index 98734eddd3fc11564f29c08a7293965bb00fb9b2..a97c3d59793efb76238b06aa9c2b8ce0ae141861 100644 --- a/entry/src/main/ets/components/SpeedDialog.ets +++ b/entry/src/main/ets/views/SpeedDialog.ets @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import { media } from '@kit.MediaKit'; import { AvPlayerController } from '../controller/AvPlayerController'; // Index of the playback rate list. @@ -21,7 +21,6 @@ const ONE = 1; const TWO = 2; const THREE = 3; -// [Start video_speed_dialog] /** * Set speed dialog */ @@ -30,41 +29,53 @@ export struct SpeedDialog { @State speedList: Resource[] = [$r('app.string.video_speed_1_0X'), $r('app.string.video_speed_1_25X'), $r('app.string.video_speed_1_75X'), $r('app.string.video_speed_2_0X')]; - @Link speedSelect: number; // Index of the current selection + @Link @Watch('onSpeedSelectUpdate') speedSelect: number; // Index of the current selection @StorageLink('avPlayerController') avPlayerController: AvPlayerController = new AvPlayerController(); private controller: CustomDialogController; + onSpeedSelectUpdate() { + AppStorage.setOrCreate('speedName', this.speedList[this.speedSelect]); + AppStorage.setOrCreate('speedIndex', this.speedSelect); + } + build() { Column() { - Text($r('app.string.dialog_play_speed'))// DocsDot + Text($r('app.string.dialog_play_speed')) .fontSize($r('app.float.size_20')) .fontWeight(FontWeight.Bold) .width('90%') .fontColor(Color.Black) .textAlign(TextAlign.Center) .margin({ top: $r('app.float.size_20'), bottom: $r('app.float.size_12') }) - // DocsDot List() { + // [Start video_speed_dialog] ForEach(this.speedList, (item: Resource, index) => { ListItem() { Column() { Row() { - Text(item)// DocsDot + Text(item) + // DocsDot + // [StartExclude text_style2] .fontSize($r('app.float.size_16')) .fontColor(Color.Black) .fontWeight(FontWeight.Medium) .textAlign(TextAlign.Center) + // [EndExclude text_style2] // DocsDot Blank() - Image(this.speedSelect == index ? $r('app.media.ic_radio_selected') : - $r('app.media.ic_radio'))// DocsDot + Image(this.speedSelect === index ? $r('app.media.ic_radio_selected') : + $r('app.media.ic_radio')) + // DocsDot + // [StartExclude text_style3] .width($r('app.float.size_24')) .height($r('app.float.size_24')) .objectFit(ImageFit.Contain) + // [EndExclude text_style3] // DocsDot } // DocsDot + // [StartExclude text_style4] .width('100%') if (index != this.speedList.length - ONE) { @@ -75,6 +86,7 @@ export struct SpeedDialog { .color($r('app.color.speed_dialog')) .width('100%') } + // [EndExclude text_style4] // DocsDot } .width('90%') @@ -83,20 +95,18 @@ export struct SpeedDialog { .height($r('app.float.size_48')) .onClick(() => { this.speedSelect = index; - AppStorage.setOrCreate('speedName', this.speedList[this.speedSelect]); - AppStorage.setOrCreate('speedIndex', this.speedSelect); switch (this.speedSelect) { case ZERO: - this.avPlayerController.videoSpeedOne(); + this.avPlayerController.videoSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_00_X); break; case ONE: - this.avPlayerController.videoSpeedOnePointTwentyFive(); + this.avPlayerController.videoSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_25_X); break; case TWO: - this.avPlayerController.videoSpeedOnePointSeventyFive(); + this.avPlayerController.videoSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_75_X); break; case THREE: - this.avPlayerController.videoSpeedTwo(); + this.avPlayerController.videoSpeed(media.PlaybackSpeed.SPEED_FORWARD_2_00_X); break; default: break; @@ -105,7 +115,7 @@ export struct SpeedDialog { }) }, (item: Resource, index) => index + '_' + JSON.stringify(item)) } - // DocsDot + // [End video_speed_dialog] .width('100%') .height('192vp') .margin({ @@ -127,18 +137,12 @@ export struct SpeedDialog { .height($r('app.float.size_50')) .padding({ bottom: $r('app.float.size_5') }) .width('100%') - - // DocsDot } - // DocsDot .alignItems(HorizontalAlign.Center) .width('100%') .margin({ left: $r('app.float.size_16'), right: $r('app.float.size_16') }) .borderRadius($r('app.float.size_24')) .backgroundColor(Color.White) - // DocsDot } -} - -// [End video_speed_dialog] \ No newline at end of file +} \ No newline at end of file diff --git a/entry/src/main/ets/components/VideoOperate.ets b/entry/src/main/ets/views/VideoOperate.ets similarity index 37% rename from entry/src/main/ets/components/VideoOperate.ets rename to entry/src/main/ets/views/VideoOperate.ets index d5bb6a67681fad1fcda8a65563c0bf5e34eaa41b..715b82198bb968ae0366c0379d6be591b70775c4 100644 --- a/entry/src/main/ets/components/VideoOperate.ets +++ b/entry/src/main/ets/views/VideoOperate.ets @@ -16,13 +16,15 @@ import { media } from '@kit.MediaKit'; import { timeConvert } from '../common/utils/TimeUtils'; import { AvPlayerController } from '../controller/AvPlayerController'; -import { SpeedDialog } from '../components/SpeedDialog'; -import { ScaleDialog } from '../components/ScaleDialog'; +import { SpeedDialog } from './SpeedDialog'; +import { ScaleDialog } from './ScaleDialog'; +import { LanguageDialog } from './LanguageDialog'; @Component export struct VideoOperate { @State speedSelect: number = 0; // Speed Magnification Selection - @State windowScaleSelect: number = 0; + @State windowScaleSelect: number = 0 + @State languageSelect: number = 0 @Link currentTime: number; @Link durationTime: number; @Link isSwiping: boolean; @@ -34,6 +36,7 @@ export struct VideoOperate { @StorageLink('speedName') speedName: Resource = $r('app.string.video_speed_1_0X'); @StorageLink('isMuted') isMuted: boolean = false; @StorageLink('videoScaleType') videoScaleType: number = media.VideoScaleType.VIDEO_SCALE_TYPE_FIT; + @StorageLink('currentLanguageType') currentLanguageType: number = 0; private dialogController: CustomDialogController = new CustomDialogController({ builder: SpeedDialog({ speedSelect: $speedSelect }), alignment: DialogAlignment.Center, @@ -44,133 +47,167 @@ export struct VideoOperate { alignment: DialogAlignment.Center, offset: { dx: $r('app.float.size_zero'), dy: $r('app.float.size_down_20') } }); + private languageDialogController: CustomDialogController = new CustomDialogController({ + builder: LanguageDialog({ languageSelect: $languageSelect }), + alignment: DialogAlignment.Center, + offset: { dx: $r('app.float.size_zero'), dy: $r('app.float.size_down_20') } + }); build() { - Row() { - Row() { - Image(this.flag ? $r('app.media.ic_video_play') : $r('app.media.ic_video_pause'))// Play/Pause - .id('play') - .width($r('app.float.size_30')) - .height($r('app.float.size_30')) - .onClick(() => { - this.flag ? this.avPlayerController.videoPause() : this.avPlayerController.videoPlay(); - this.flag = !this.flag; - }) - - // Left side time - Text(timeConvert(this.currentTime)) - .fontColor(Color.White) - .textAlign(TextAlign.End) - .fontWeight(FontWeight.Regular) - .margin({ left: $r('app.float.size_5') }) - } - + Column(){ Row() { - // [Start progress_slider] + // [Start video_language_switch_button] /** - * Progress slider + * Video Language switch */ - Slider({ - value: this.currentTime, - min: 0, - max: this.durationTime, - style: SliderStyle.OutSet + Button() { + Image($r('app.media.ic_video_translate')) + .width($r('app.float.size_25')) + .height($r('app.float.size_25')) + } + .type(ButtonType.Normal) + .width($r('app.float.size_25')) + .height($r('app.float.size_25')) + .backgroundColor('rgba(0, 0, 0, 0)') + .margin({ left: $r('app.float.size_5') }) + .fontColor(Color.White) + .onClick(() => { + this.languageSelect = this.currentLanguageType; + this.languageDialogController.open(); }) - .id('Slider') - .blockColor(Color.White) - .trackColor(Color.Gray) - .selectedColor($r('app.color.slider_selected')) - .showTips(false) - .onChange((value: number, mode: SliderChangeMode) => { - if (mode == SliderChangeMode.Begin) { - this.isSwiping = true; - this.avPlayerController.videoPause(); - } - this.avPlayerController.videoSeek(value); - this.currentTime = value; - if (mode == SliderChangeMode.End) { - this.isSwiping = false; - this.flag = true; - this.avPlayerController.videoPlay(); - } - }) - // [End progress_slider] + + // [End video_language_switch_button] } - .layoutWeight(1) + .width('100%') + .padding({ left: $r('app.float.size_12'), right: $r('app.float.size_20') }) + .justifyContent(FlexAlign.End) + Row() { - // Right side time - Text(timeConvert(this.durationTime)) - .fontColor(Color.White) - .fontWeight(FontWeight.Regular) + Row() { + Image(this.flag ? $r('app.media.ic_video_play') : $r('app.media.ic_video_pause'))// Play/Pause + .id('play') + .width($r('app.float.size_30')) + .height($r('app.float.size_30')) + .onClick(() => { + this.flag ? this.avPlayerController.videoPause() : this.avPlayerController.videoPlay(); + this.flag = !this.flag; + }) + + // Left side time + Text(timeConvert(this.currentTime)) + .fontColor(Color.White) + .textAlign(TextAlign.End) + .fontWeight(FontWeight.Regular) + .margin({ left: $r('app.float.size_5') }) + } + + Row() { + // [Start progress_slider] + /** + * Progress slider + */ + Slider({ + value: this.currentTime, + min: 0, + max: this.durationTime, + style: SliderStyle.OutSet + }) + .id('Slider') + .blockColor(Color.White) + .trackColor(Color.Gray) + .selectedColor($r('app.color.slider_selected')) + .showTips(false) + .onChange((value: number, mode: SliderChangeMode) => { + if (mode === SliderChangeMode.Begin) { + this.isSwiping = true; + this.avPlayerController.videoPause(); + } + this.avPlayerController.videoSeek(value); + this.currentTime = value; + if (mode === SliderChangeMode.End) { + this.isSwiping = false; + this.flag = true; + this.avPlayerController.videoPlay(); + } + }) + // [End progress_slider] + } + .layoutWeight(1) + Row() { + // Right side time + Text(timeConvert(this.durationTime)) + .fontColor(Color.White) + .fontWeight(FontWeight.Regular) + + // [Start video_speed_button] + Button(this.speedName, { type: ButtonType.Normal }) + .border({ width: $r('app.float.size_1'), color: Color.White }) + .width($r('app.float.size_64')) + .height($r('app.float.size_30')) + .fontSize($r('app.float.size_15')) + .borderRadius($r('app.float.size_20')) + .fontColor(Color.White) + .backgroundColor('rgba(0, 0, 0, 0)') + .opacity($r('app.float.size_1')) + .padding({ left: $r('app.float.size_5'), right: $r('app.float.size_5') }) + .margin({ left: $r('app.float.size_8') }) + .id('Speed') + .onClick(() => { + this.speedSelect = this.speedIndex; + this.dialogController.open(); + }) + // [End video_speed_button] - // [Start video_speed_button] - Button(this.speedName, { type: ButtonType.Normal }) - .border({ width: $r('app.float.size_1'), color: Color.White }) - .width($r('app.float.size_64')) + // [Start video_muted_button] + /** + * Video Muted Button + */ + Button() { + Image(this.isMuted ? $r('app.media.ic_video_speaker_slash') : $r('app.media.ic_video_speaker')) + .width($r('app.float.size_30')) + .height($r('app.float.size_30')) + } + .type(ButtonType.Normal) + .width($r('app.float.size_30')) .height($r('app.float.size_30')) - .fontSize($r('app.float.size_15')) .borderRadius($r('app.float.size_20')) - .fontColor(Color.White) .backgroundColor('rgba(0, 0, 0, 0)') - .opacity($r('app.float.size_1')) - .padding({ left: $r('app.float.size_5'), right: $r('app.float.size_5') }) - .margin({ left: $r('app.float.size_8') }) - .id('Speed') + .margin({ left: $r('app.float.size_5') }) + .fontColor(Color.White) .onClick(() => { - this.speedSelect = this.speedIndex; - this.dialogController.open(); + this.isMuted = !this.isMuted; + this.avPlayerController.videoMuted(this.isMuted) }) - // [End video_speed_button] - // [Start video_muted_button] - /** - * Video Muted Button - */ - Button() { - Image(this.isMuted ? $r('app.media.ic_video_speaker_slash') : $r('app.media.ic_video_speaker')) - .width($r('app.float.size_30')) - .height($r('app.float.size_30')) - } - .type(ButtonType.Normal) - .width($r('app.float.size_30')) - .height($r('app.float.size_30')) - .borderRadius($r('app.float.size_20')) - .backgroundColor('rgba(0, 0, 0, 0)') - .margin({ left: $r('app.float.size_5') }) - .fontColor(Color.White) - .onClick(() => { - this.isMuted = !this.isMuted; - this.avPlayerController.videoMuted(this.isMuted) - }) + // [End video_muted_button] - // [End video_muted_button] + // [Start window_scale_button] + /** + * Window scale button + */ + Button() { + Image($r('app.media.ic_video_window_scale')) + .width($r('app.float.size_25')) + .height($r('app.float.size_25')) + } + .type(ButtonType.Normal) + .width($r('app.float.size_25')) + .height($r('app.float.size_25')) + .backgroundColor('rgba(0, 0, 0, 0)') + .margin({ left: $r('app.float.size_5') }) + .fontColor(Color.White) + .onClick(() => { + this.windowScaleSelect = this.videoScaleType; + this.scaleDialogController.open(); + }) - // [Start window_scale_button] - /** - * Window scale button - */ - Button() { - Image($r('app.media.ic_video_window_scale')) - .width($r('app.float.size_25')) - .height($r('app.float.size_25')) + // [End window_scale_button] } - .type(ButtonType.Normal) - .width($r('app.float.size_25')) - .height($r('app.float.size_25')) - .backgroundColor('rgba(0, 0, 0, 0)') - .margin({ left: $r('app.float.size_5') }) - .fontColor(Color.White) - .onClick(() => { - this.windowScaleSelect = this.videoScaleType; - this.scaleDialogController.open(); - }) - - // [End window_scale_button] } + .justifyContent(FlexAlign.Center) + .padding({ left: $r('app.float.size_12'), right: $r('app.float.size_20') }) + .width('100%') } - .justifyContent(FlexAlign.Center) - .padding({ left: $r('app.float.size_12'), right: $r('app.float.size_20') }) - .width('100%') } } \ No newline at end of file diff --git a/entry/src/main/module.json5 b/entry/src/main/module.json5 index 5caa9363487d06748da71f11e89c384e975a1267..847fceb6c68f30a164b4f35acad31e4ec1733554 100644 --- a/entry/src/main/module.json5 +++ b/entry/src/main/module.json5 @@ -20,8 +20,7 @@ "description": "$string:module_desc", "mainElement": "EntryAbility", "deviceTypes": [ - "phone", - "tablet" + "phone" ], "deliveryWithInstall": true, "installationFree": false, diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json index da436699c0ec274e7b58a4adae50012a3678cfac..83059752bdd5f7c8799c8d9994c2d3cf04c36221 100644 --- a/entry/src/main/resources/base/element/string.json +++ b/entry/src/main/resources/base/element/string.json @@ -79,7 +79,18 @@ { "name": "local_video", "value": "Local video" + }, + { + "name": "Chinese", + "value": "Chinese" + }, + { + "name": "English", + "value": "English" + }, + { + "name": "language_switch", + "value": "Language switch" } - ] } \ No newline at end of file diff --git a/entry/src/main/resources/base/media/background.png b/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/entry/src/main/resources/base/media/background.png differ diff --git a/entry/src/main/resources/base/media/foreground.png b/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..97014d3e10e5ff511409c378cd4255713aecd85f Binary files /dev/null and b/entry/src/main/resources/base/media/foreground.png differ diff --git a/entry/src/main/resources/base/media/ic_video_translate.svg b/entry/src/main/resources/base/media/ic_video_translate.svg new file mode 100644 index 0000000000000000000000000000000000000000..907ba2997bc6abcf55a3303a3e3a23d1bf42c4d8 --- /dev/null +++ b/entry/src/main/resources/base/media/ic_video_translate.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/entry/src/main/resources/base/media/layered_image.json b/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/entry/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/entry/src/main/resources/en_US/element/string.json b/entry/src/main/resources/en_US/element/string.json index c8388e9f3be5541020683c46c9426a95cc915289..8d5ec0fa074d19cbf56be3471ba6f2eff251022c 100644 --- a/entry/src/main/resources/en_US/element/string.json +++ b/entry/src/main/resources/en_US/element/string.json @@ -10,7 +10,7 @@ }, { "name": "entryAbility_label", - "value": "AVPlayer Basic Control" + "value": "AVPlayerBasicControl" }, { "name": "video_res_1", @@ -79,6 +79,18 @@ { "name": "local_video", "value": "Local video" + }, + { + "name": "Chinese", + "value": "Chinese" + }, + { + "name": "English", + "value": "English" + }, + { + "name": "language_switch", + "value": "Language switch" } ] } \ No newline at end of file diff --git a/entry/src/main/resources/rawfile/captions.srt b/entry/src/main/resources/rawfile/captions.srt index 845591e72e763e2784736471d7b26dc907c9199f..9a17e648059285d09261cd86c36e1dcfbcc18629 100644 --- a/entry/src/main/resources/rawfile/captions.srt +++ b/entry/src/main/resources/rawfile/captions.srt @@ -1,47 +1,47 @@ 1 -00:00:01,000 --> 00:00:03,000 +00:00:00,000 --> 00:00:01,000 360°立体天气动态展示 2 -00:00:03,000 --> 00:00:06,000 +00:00:01,000 --> 00:00:02,000 实时同步窗外真实气象 3 -00:00:07,000 --> 00:00:10,000 +00:00:02,000 --> 00:00:03,000 雷暴预警闪电特效提示 4 -00:00:10,000 --> 00:00:12,000 +00:00:03,000 --> 00:00:04,000 晨曦极光自动色调变换 5 -00:00:12,000 --> 00:00:14,000 +00:00:04,000 --> 00:00:05,000 指尖轻触触发季节音效 6 -00:00:14,000 --> 00:00:17,000 +00:00:05,000 --> 00:00:06,000 这是第一段比较长的字幕测试内容 7 -00:00:17,000 --> 00:00:20,000 +00:00:06,000 --> 00:00:07,000 这是第二段比较长的字幕测试内容 8 -00:00:20,000 --> 00:00:23,000 +00:00:07,000 --> 00:00:08,000 这是第三段比较长的字幕测试内容 9 -00:00:23,000 --> 00:00:26,000 +00:00:08,000 --> 00:00:09,000 这是第四段比较长的字幕测试内容 10 -00:00:26,000 --> 00:00:29,000 +00:00:09,000 --> 00:00:10,000 这是第五段比较长的字幕测试内容 11 -00:00:29,000 --> 00:00:32,000 +00:00:10,000 --> 00:00:11,000 这是第六段比较长的字幕测试内容 12 -00:00:32,000 --> 00:00:35,000 +00:00:11,000 --> 00:00:12,000 这是第七段比较长的字幕测试内容 \ No newline at end of file diff --git a/entry/src/main/resources/rawfile/en_captions.srt b/entry/src/main/resources/rawfile/en_captions.srt new file mode 100644 index 0000000000000000000000000000000000000000..0de4e872eb7f99848907ed59113fca1a5a8d1ce4 --- /dev/null +++ b/entry/src/main/resources/rawfile/en_captions.srt @@ -0,0 +1,47 @@ +1 +00:00:00,000 --> 00:00:01,000 +360° dynamic display of three-dimensional weather + +2 +00:00:01,000 --> 00:00:02,000 +Synchronize the real-time meteorological conditions outside the window + +3 +00:00:02,000 --> 00:00:03,000 +Thunderstorm warning: lightning effect prompt + +4 +00:00:03,000 --> 00:00:04,000 +Dawn Aurora Automatic Color Tone Transition + +5 +00:00:04,000 --> 00:00:05,000 +A light touch on the fingertip triggers seasonal sound effects + +6 +00:00:05,000 --> 00:00:06,000 +This is the first relatively long subtitle test content + +7 +00:00:06,000 --> 00:00:07,000 +This is the second relatively long subtitle test content + +8 +00:00:07,000 --> 00:00:08,000 +This is the third relatively long subtitle test content + +9 +00:00:08,000 --> 00:00:09,000 +This is the fourth relatively long subtitle test content + +10 +00:00:09,000 --> 00:00:10,000 +This is the fifth relatively long subtitle test content + +11 +00:00:10,000 --> 00:00:11,000 +This is the sixth relatively long subtitle test content + +12 +00:00:11,000 --> 00:00:12,000 +This is the seventh relatively long subtitle test content \ No newline at end of file diff --git a/entry/src/main/resources/zh_CN/element/string.json b/entry/src/main/resources/zh_CN/element/string.json index 5654ae5cc6021df3de4c4ec7e810ceb7ea870fe9..b074e3c1f8485a02902532389c4f119f719268b6 100644 --- a/entry/src/main/resources/zh_CN/element/string.json +++ b/entry/src/main/resources/zh_CN/element/string.json @@ -79,6 +79,18 @@ { "name": "local_video", "value": "本地视频" + }, + { + "name": "Chinese", + "value": "中文" + }, + { + "name": "English", + "value": "英文" + }, + { + "name": "language_switch", + "value": "语言切换" } ] } \ No newline at end of file diff --git a/hvigor/hvigor-config.json5 b/hvigor/hvigor-config.json5 index f70ecd4112d94f9aa555adf898d53f18bf58f3e9..5bebc9755447385d82ce4138f54d991b1f85f348 100644 --- a/hvigor/hvigor-config.json5 +++ b/hvigor/hvigor-config.json5 @@ -1,5 +1,22 @@ { - "modelVersion": "5.0.0", + "modelVersion": "5.0.5", "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ } -} \ No newline at end of file +} diff --git a/hvigorfile.ts b/hvigorfile.ts index 6478186902c0c1ad7c966a929c7d6b7d8ae7a9f3..47113e2e36ecefde41c136272a0bd6ff745cffe4 100644 --- a/hvigorfile.ts +++ b/hvigorfile.ts @@ -1,2 +1,6 @@ -// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. -export { appTasks } from '@ohos/hvigor-ohos-plugin'; \ No newline at end of file +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins: [] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/oh-package.json5 b/oh-package.json5 index 77a997818065cdc56973d6bb556dea03eccf28ba..2d9c74ff34f1383ec920815a40399dd0fe46a6c5 100644 --- a/oh-package.json5 +++ b/oh-package.json5 @@ -1,11 +1,8 @@ { - "modelVersion": "5.0.0", - "license": "ISC", - "devDependencies": { + "modelVersion": "5.0.5", + "description": "Please describe the basic information.", + "dependencies": { }, - "name": "myvideo", - "description": "example description", - "repository": {}, - "version": "1.0.0", - "dependencies": {} -} \ No newline at end of file + "devDependencies": { + } +} diff --git a/screenshots/devices/pause.png b/screenshots/devices/pause.png index e61bc6095867189a81b71fac950fcf951862cf6b..86813701956bc38e2c419b398fb3b7ee6a4d2703 100644 Binary files a/screenshots/devices/pause.png and b/screenshots/devices/pause.png differ diff --git a/screenshots/devices/playing.png b/screenshots/devices/playing.png index 481b5fe0e4d123851ce2a8ad50d6166d401d5ff8..ed81fd38eb1d70d89c46ab5949cb6404f596aa03 100644 Binary files a/screenshots/devices/playing.png and b/screenshots/devices/playing.png differ diff --git a/screenshots/devices/scale_fit.png b/screenshots/devices/scale_fit.png index 6769279939028f0c2e8772060f554605fc4f971f..67b8a911244de0e2344b2ac587dffe03466fa275 100644 Binary files a/screenshots/devices/scale_fit.png and b/screenshots/devices/scale_fit.png differ diff --git a/screenshots/devices/set_media_muted.png b/screenshots/devices/set_media_muted.png index 8f0db4592c578f47154f6d15d2e2e896fe2f20c0..a4a94bc62cbe343948dd686d73e937525a004821 100644 Binary files a/screenshots/devices/set_media_muted.png and b/screenshots/devices/set_media_muted.png differ diff --git a/screenshots/devices/set_volume.png b/screenshots/devices/set_volume.png index b84ee4d88688ed5ad531b0cdcfa306253a515bc0..c11e3f332a78c94df518552da031d975d406fa2b 100644 Binary files a/screenshots/devices/set_volume.png and b/screenshots/devices/set_volume.png differ diff --git a/screenshots/devices/speed_dialog.png b/screenshots/devices/speed_dialog.png index 7c5c1e4d45e5da6655f9e71be3e64411fbd3ca5b..9845b501bcd6b66f3013b14438666e66a085b6ac 100644 Binary files a/screenshots/devices/speed_dialog.png and b/screenshots/devices/speed_dialog.png differ