diff --git a/entry/src/main/ets/Components/VideoController.ets b/entry/src/main/ets/Components/VideoController.ets new file mode 100644 index 0000000000000000000000000000000000000000..ceed148ec461aeb79d0698b3b192be3aa715a312 --- /dev/null +++ b/entry/src/main/ets/Components/VideoController.ets @@ -0,0 +1,645 @@ + +import MediaPlayService from '../model/media/MediaPlayService' +import VideoGestureView from '../Components/VideoGestureView' +import LoadingView from './LoadingView'; +import { MediaPlayerState, MediaAsset } from '../model/media/MediaConstants'; +import DateTimeUtil from '../util/DateTimeUtils'; +import router from '@system.router'; +import mediaLibrary from '@ohos.multimedia.mediaLibrary' +import MediaUtils from '../model/MediaUtils' +import Logger from '../model/Logger' + +@CustomDialog +struct CustomDialogRename { + controller: CustomDialogController + private cancel: () => void + private confirm: () => void + @Link title: string + private name: string + + + aboutToAppear() + { + + } + + + + build() { + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Column() { + + Text('重命名') + .margin(10) + .fontSize(18) + .textAlign(TextAlign.End) + Divider().color(Color.Grey).strokeWidth(1).width('90%') + TextInput({ placeholder: this.title }) + .margin(20) + .width('80%') + .height(90) + .onChange((value: string) => { + this.name = value + + }) + Row() { + Button() { + Text('取消').fontSize(16) + } + .margin(8) + .fontSize(16) + .width('26%') + .onClick(() => { + console.info("CustomDialogRename cancel"); + this.controller.close() + this.cancel() + }) + .backgroundColor('#fff1f6f1') + + Button() { + Text('确定').fontSize(16) + } + .margin(8) + .fontSize(16) + .width('26%') + .onClick(() => { + console.info("CustomDialogRename confirm"); + if(this.name.length!=0) { + this.title=this.name + this.controller.close() + this.confirm() + } + + + }) + .backgroundColor('#fff1f6f1') + + + } + } + .borderStyle(BorderStyle.Solid) + + + } + } +} + +@CustomDialog +struct CustomDialogDelete { + controller: CustomDialogController + private cancel: () => void + private confirm: () => void + //renameText: string + //@Link renameTextBegin: string + + + aboutToAppear() + { + + } + + + + build() { + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + Column() { + + Text('确认删除所选内容') + .margin(10) + .fontSize(18) + .textAlign(TextAlign.End) + Divider().color(Color.Grey).strokeWidth(1).width('90%') + Row() { + Button() { + Text('取消').fontSize(16) + } + .margin(8) + .fontSize(16) + .width('26%') + .onClick(() => { + console.info("CustomDialogDelete cancel"); + this.controller.close() + this.cancel() + }) + .backgroundColor('#fff1f6f1') + + Button() { + Text('删除').fontSize(16) + } + .margin(8) + .fontSize(16) + .width('26%') + .onClick(() => { + console.info("CustomDialogDelete confirm"); + this.controller.close() + this.confirm() + + + }) + .backgroundColor('#fff1f6f1') + + + } + } + .borderStyle(BorderStyle.Solid) + + + } + } +} + + + + + +@Component +export default struct VideoController { + @State isMenuShow: boolean = true + @State isBuffering: boolean = false + @State title: string = '' + @State playBtnSrc: Resource = $r('app.media.ic_play') + private service: MediaPlayService + @State curDuration: number = 0 + @State totalDuration: number = 0 + private menuShowTasker: number = -1 + private durationTasker: number = -1 + + private TAG: string="VideoController" + @State uriPath: string = '' + private mediaUtils: MediaUtils = MediaUtils.getInstance() + private mediaTest: mediaLibrary.MediaLibrary = mediaLibrary.getMediaLibrary(globalThis.abilityContext) + @State mediaList: Array = [] + @State selectMedia: mediaLibrary.FileAsset = undefined + + @StorageLink('isAudio') isAudio: boolean = false //是否是音频 + + + + refreshData() { + this.mediaList = [] + let fileAsset + if(this.isAudio) + fileAsset = this.mediaUtils.getFileAssetsFromType(mediaLibrary.MediaType.AUDIO) + else + fileAsset = this.mediaUtils.getFileAssetsFromType(mediaLibrary.MediaType.VIDEO) + fileAsset.then(fileList => { + Logger.info(this.TAG, 'getFileList image callback') + Logger.info(this.TAG, 'getFileList fileList length='+fileList.length) + this.mediaList = fileList + Logger.info(this.TAG, 'getFileList this.mediaList.length='+this.mediaList.length) + }).catch(err => { + Logger.error(this.TAG, `getFileList image err,err = ${err}`) + }) + } + + + + dialogRename: CustomDialogController = new CustomDialogController({ + builder: CustomDialogRename({ cancel: this.onRenameCancel.bind(this), confirm: this.onRenameAccept.bind(this),title: $title}), + cancel: this.existRenameApp, + autoCancel: true + }) + + + + onRenameCancel() { + console.log(this.TAG+',Callback when the first button is clicked') + //this.dialogController.close() + } + onRenameAccept() { + console.log(this.TAG+',onRenameAccept,this.title='+this.title) + console.log(this.TAG+',onRenameAccept,this.image='+this.uriPath) + console.log(this.TAG+',onRenameAccept,this.mediaList.length='+this.mediaList.length) + + for (var index = 0;index < this.mediaList.length; index++) { + console.log(this.TAG+',onRenameAccept,this.mediaList[index].uri='+this.mediaList[index].uri) + if(this.mediaList[index].uri==this.uriPath) { + this.selectMedia=this.mediaList[index] + Logger.info(this.TAG, `renameMedia newName = ${this.selectMedia.displayName}`) + Logger.info(this.TAG, `renameMedia title = ${this.title}`) + + var arr = this.selectMedia.displayName.split('.') + var renameTextEnd=arr[1] + + this.selectMedia.title = this.title + this.selectMedia.displayName = this.title+"."+renameTextEnd + this.selectMedia.commitModify(err => { + if (err !== undefined) { + Logger.info(this.TAG, `commitModify,err: ${err}`) + return + } + Logger.info(this.TAG, 'commitModify success') + //this.dialogRename.close() + this.refreshData() + }) + } + } + + + + } + + existRenameApp() { + console.info('Click the callback in the blank area') + } + + + + dialogDelete: CustomDialogController = new CustomDialogController({ + builder: CustomDialogDelete({ cancel: this.onDeleteCancel.bind(this), confirm: this.onDeleteAccept.bind(this)}), + cancel: this.existDeleteApp, + autoCancel: true + }) + + + onDeleteCancel() { + console.log(this.TAG+',Callback when the first button is clicked') + //this.dialogController.close() + } + + onDeleteAccept() { + console.log(this.TAG+',onDeleteAccept this.uriPath='+this.uriPath) + console.log(this.TAG+',onDeleteAccept this.title='+this.title) + + //let path=this.mediaTest.getPublicDirectory(this.image) + + + let deleteResult =this.mediaTest.deleteAsset(this.uriPath) + deleteResult.then(() => { + console.info(this.TAG+",deleteFile callback"); + this.uriPath="" + this.title="" + }).catch(err => { + console.error(this.TAG+`,deleteFile err,err: ${err}`); + }) + + } + existDeleteApp() { + console.info('Click the callback in the blank area') + } + + + + + + + aboutToAppear() { + this.refreshData() + + if (this.service != null) { + this.service.addStatusChangedListener((state, extra) => { + switch (state) { + case MediaPlayerState.START: + break; + case MediaPlayerState.LOAD: + this.title = extra.asset.getTitle() + this.totalDuration = 0 + break; + case MediaPlayerState.PREPARED: + // 播放器准备完毕回调,更新控制菜单栏的播放总时长 + this.totalDuration = extra.duration + + console.log('&&&-->totalDuration:'+extra.duration) + + break + case MediaPlayerState.BUFFERING_START: + // 视频加载状态回调,显示加载动画 + this.isBuffering = true + break; + case MediaPlayerState.PLAY: + // 视频播放状态,修改播放按钮样式 + this.showMenu(5000) + this.playBtnSrc = $r('app.media.ic_pause') + case MediaPlayerState.BUFFERING_END: + this.isBuffering = false + //启动进度刷新任务 + this.startDurationTasker() + break; + case MediaPlayerState.IDLE: + case MediaPlayerState.ERROR: + // 停止进度刷新任务 + this.stopDurationTasker() + case MediaPlayerState.FINISH: + this.curDuration = 0 + case MediaPlayerState.STOP: + case MediaPlayerState.PAUSE: + //视频停止状态,修改播放按钮样式, + this.showMenu() + this.playBtnSrc = $r('app.media.ic_play') + break; + + } + }) + } + } + + private getIsAudio(){ + console.log('isAudio:'+this.isAudio) + if (this.isAudio) { + this.isMenuShow = true + } + return this.isAudio + } + + //开始动画任务 + private startDurationTasker() { + if (this.service != null && this.durationTasker == -1) { + this.durationTasker = setInterval(() => { + if (this.service.getPlayerState() == MediaPlayerState.PLAY) { + this.curDuration = this.service.getCurrentTime() + console.log('&&&-->curDuration:'+this.curDuration) + } + }, 1000) + } + } + + //停止动画任务 + private stopDurationTasker() { + if (this.durationTasker) { + clearInterval(this.durationTasker) + this.durationTasker = -1 + } + } + + //显示菜单栏 + private showMenu(duration?: number) { + clearTimeout(this.menuShowTasker) + this.isMenuShow = true + if (duration) { + this.hideMenu(duration) + } + } + + //隐藏菜单栏 + private hideMenu(delayTime?: number) { + clearTimeout(this.menuShowTasker) + this.menuShowTasker = setTimeout(() => { + this.menuShowTasker = -1 + this.isMenuShow = false + }, delayTime ? delayTime : 0) + } + + //顶部菜单栏 + @Builder TopMenu() { + Stack({ alignContent: Alignment.Start }) { + + //标题 + Text(this.title) + .fontSize('58px') + .fontColor('#ffffff') + .width('100%') + .height('100%') + .textAlign(TextAlign.Center) + + + //添加返回按钮 + Button({ type: ButtonType.Normal, stateEffect: true }) { + Image($r('app.media.ic_back')).width(30).height(30).objectFit(ImageFit.Fill) + } + .width(90) + .height(90) + .margin({ left: 20 }) + .backgroundColor('#00000000') + .onClick(() => { + + this.showMenu(5000) + router.back() +// //返回上一个Ability +// let context = globalThis.context +// var want = { +// "deviceId": "", +// "bundleName": "com.example.filemanager_sys", +// "abilityName": "MainAbility" +// }; +// var options = { windowMode: 0, displayId: 2 }; +// context.startAbility(want, null).then((data) => { +// console.log("qqqq--->Succeed to start ability with data: " + JSON.stringify(data)) +// }).catch((error) => { +// console.error("qqqq--->Failed to start ability with error: "+ JSON.stringify(error)) +// }) + + }) + + } + .position({ x: 0, y: 0 }) + .linearGradient({ + direction: GradientDirection.Bottom, + colors: [['#CC000000', 0.0], ['#88000000', 0.4], ['#44000000', 0.8], ['#00000000', 1.0]] + }) + .width('100%') + .height(this.isMenuShow ? '15%' : '0%') + .opacity(this.isMenuShow ? 1.0 : 0) + .animation({ + duration: 300, + curve: Curve.Smooth, + }) + } + //中间音频菜单栏 + @Builder CenterAudioMenu() { + Stack() { + Image($r('app.media.ic_audio_img')) + .width('50%') + .height('40%') + .objectFit(ImageFit.Contain) + .renderMode(ImageRenderMode.Original) + } + .width('100%') + .height('100%') + .backgroundColor(Color.White) + } + + //中间菜单栏 + @Builder CenterMenu() { + Stack() { + if (this.isBuffering) { + //加载动画页面 + LoadingView() + } else if (this.isMenuShow) { + //中间播放/暂停按钮 + Button({ type: ButtonType.Circle, stateEffect: true }) { + Image(this.playBtnSrc).width(30).height(30).objectFit(ImageFit.Fill) + } + .width('100%') + .height('100%') + .backgroundColor('#88000000') + .onClick(() => { + this.showMenu() + console.log('111111') + //点击支持对应的任务 + if (this.service != null) { + if (this.service.getPlayerState() == MediaPlayerState.PLAY) { + this.service.pause() + console.log('111111 333333') + + } else if (this.service.getPlayerState() == MediaPlayerState.PAUSE) { + this.service.resume() + console.log('111111 22222') + + } else { + this.service.play() + console.log('111111 555555') + + } + } + }) + } + }.width(this.isBuffering || this.isMenuShow ? 60 : 0) + .height(this.isBuffering || this.isMenuShow ? 60 : 0) + .position({ x: '50%', y: '50%' }) + .markAnchor({ x: '50%', y: '50%' }) + } + + //底部菜单栏 + @Builder BottomMenu() { + Row() { + Button({ type: ButtonType.Circle, stateEffect: true }) { + Image(this.playBtnSrc).width(20).height(20).objectFit(ImageFit.Fill) + } + .width(40) + .height(40) + .margin({right:10}) + .backgroundColor('#88000000') + .onClick(() => { + this.showMenu() + //点击支持对应的任务 + if (this.service != null) { + if (this.service.getPlayerState() == MediaPlayerState.PLAY) { + this.service.pause() + } else if (this.service.getPlayerState() == MediaPlayerState.PAUSE) { + this.service.resume() + } else { + this.service.play() + } + } + }) + + //播放时间 + Text(DateTimeUtil.ms2CountdownTime(this.curDuration)) + .fontSize(25) + .fontColor(Color.White) + //进度条 + Slider({ value: this.curDuration / this.service.getDuration() * 100, max: 100, min: 0 }) + .selectedColor(Color.White) + .layoutWeight(1) + .trackColor('#5a5a5a') + .trackThickness(5) + .selectedColor('#FF6103') + //监听变化事件 + .onChange((value: number, mode: SliderChangeMode) => { + switch (mode) { + case SliderChangeMode.Begin: + this.showMenu() + this.stopDurationTasker() + break; + case SliderChangeMode.Moving: + this.curDuration = Math.round(value / 100 * this.service.getDuration() / 1000) * 1000 + console.log('&&&-->curDuration2:'+this.curDuration) + break; + case 3: + this.curDuration = Math.round(value / 100 * this.service.getDuration() / 1000) * 1000 + console.log('&&&-->curDuration3:'+this.curDuration) + case SliderChangeMode.End: + this.showMenu(5000) + if (this.service != null) { + this.service.seek(value / 100 * this.service.getDuration()) + } + break; + } + }) + //资源总时长 + Text(DateTimeUtil.ms2CountdownTime(this.totalDuration)) + .fontSize(25) + .fontColor(Color.White) + } + .linearGradient({ + direction: GradientDirection.Top, + colors: [['#CC000000', 0.0], ['#88000000', 0.4], ['#44000000', 0.8], ['#00000000', 1.0]] + }) + .padding({ left: 20, right: 20 }) + .justifyContent(FlexAlign.Center) + .width('100%') + .height(this.isMenuShow ? '15%' : '0%') + .opacity(this.isMenuShow ? 1.0 : 0) + .animation({ + duration: 300, + curve: Curve.Smooth, + }) + + + + Row() { + + Column({ space: 20 }) { + //Image($r('app.media.move')).width(60).height(50).backgroundColor(Color.White) + Text("移动至" ) + .fontSize(20) + .fontColor(Color.Black) + .fontWeight(FontWeight.Regular) + } + .backgroundColor(Color.White) + + Column({ space: 20 }) { + //Image($r('app.media.rename')).width(60).height(50).backgroundColor(Color.White) + Text("重命名") + .fontSize(20) + .fontColor(Color.Black) + .fontWeight(FontWeight.Regular) + } + .backgroundColor(Color.White) + .margin({left:50}) + .onClick(() => { + console.info(this.TAG +',onClick Rename window') +// var arr = this.title.split('.') +// this.renameTextBegin=arr[0] +// this.renameTextEnd=arr[1] + this.dialogRename.open() + }) + + Column({ space: 20 }) { + //Image($r('app.media.delete')).width(60).height(50).backgroundColor(Color.White) + Text("删除") + .fontSize(20) + .fontColor(Color.Black) + .fontWeight(FontWeight.Regular) + } + .backgroundColor(Color.White) + .margin({left:50}) + .onClick(() => { + console.info(this.TAG +',onClick delete video') + this.dialogDelete.open() + }) + + } + .height(60 * 1) + .margin({top:50}) + } + + build() { + Stack({ alignContent: Alignment.Bottom }) { + + if (this.getIsAudio()) { + this.CenterAudioMenu() + }else { + //添加手势控制组件 + VideoGestureView({ + service: this.service, + onStateUpdate: (isGestureViewShow) => { + isGestureViewShow ? this.hideMenu() : this.showMenu(5000) + } + }) + this.CenterMenu() + } + + this.TopMenu() + this.BottomMenu() + + }.height('100%').width('100%').backgroundColor('#00000000') + .onClick(() => { + if (this.service != null) { + this.isMenuShow ? this.hideMenu() : this.showMenu(5000) + } + }) + } +} + + diff --git a/entry/src/main/ets/PlayerAbility/PlayerAbility.ts b/entry/src/main/ets/PlayerAbility/PlayerAbility.ts new file mode 100644 index 0000000000000000000000000000000000000000..c43505db866f680a638f49ce1bbff7e44312ca72 --- /dev/null +++ b/entry/src/main/ets/PlayerAbility/PlayerAbility.ts @@ -0,0 +1,53 @@ +import Ability from '@ohos.application.Ability' +import SysPermissionUtils from '../util/SysPermissionUtils'; + +export default class PlayerAbility extends Ability { + onCreate(want, launchParam) { + console.log("[Demo] MainAbility onCreate") + globalThis.abilityWant = want; + + let abilityInfo = this.context.abilityInfo; + globalThis.bundleName = abilityInfo.bundleName + globalThis.context = this.context + globalThis.LogTag = 'cwq' + globalThis.isShowLog = true + globalThis.display + globalThis.abilityContext= this.context + } + + onDestroy() { + console.log("[Demo] MainAbility onDestroy") + } + + onWindowStageCreate(windowStage) { + // Main window is created, set main page for this ability + console.log("[Demo] MainAbility onWindowStageCreate") + + globalThis.requestPermissions = (() => { + return SysPermissionUtils.requestWithinNecessary(null, + ["ohos.permission.READ_MEDIA", + "ohos.permission.WRITE_MEDIA", + "ohos.permission.MEDIA_LOCATION" + ] + ) + }) + + + windowStage.setUIContent(this.context, "pages/video_player", null) + } + + onWindowStageDestroy() { + // Main window is destroyed, release UI related resources + console.log("[Demo] MainAbility onWindowStageDestroy") + } + + onForeground() { + // Ability has brought to foreground + console.log("[Demo] MainAbility onForeground") + } + + onBackground() { + // Ability has back to background + console.log("[Demo] MainAbility onBackground") + } +};