From bed645747d590d6eff81c3120d3fc615e331cb1f Mon Sep 17 00:00:00 2001 From: XiuYun CHEN Date: Thu, 15 May 2025 13:11:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E5=85=A5=E4=BF=9D=E5=88=A9=E5=A8=81?= =?UTF-8?q?=E8=A7=86demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- commons/basic/oh-package-lock.json5 | 9 + commons/basic/oh-package.json5 | 4 +- features/Home/oh-package-lock.json5 | 148 +++++++- features/Home/oh-package.json5 | 6 +- .../Home/src/main/ets/components/ItemComp.ets | 26 +- .../ets/pages/PLVMediaPlayerSingleVideo.ets | 6 + .../polyv/PLVMockFeedResourceDataSource.ets | 16 + .../ets/polyv/PLVMockMediaResourceData.ts | 114 ++++++ polyv/.gitignore | 11 + polyv/Index.ets | 64 ++++ polyv/build-profile.json5 | 28 ++ polyv/hvigorfile.ts | 6 + polyv/obfuscation-rules.txt | 18 + polyv/oh-package.json5 | 12 + .../main/ets/common/di/PLVMPCommonModule.ts | 19 + .../auxiliary/di/PLVMPAuxiliaryModule.ts | 31 ++ .../mediator/PLVMPAuxiliaryMediator.ts | 16 + .../auxiliary/model/PLVMPAuxiliaryRepo.ts | 41 +++ .../viewmodel/PLVMPAuxiliaryViewModel.ts | 43 +++ .../usecases/AuxiliaryBeforePlayListener.ts | 26 ++ .../AuxiliaryUpdateMediaStateUseCase.ts | 48 +++ .../usecases/PLVMPAuxiliaryUseCases.ts | 10 + .../viewstate/PLVMPAuxiliaryInfoViewState.ts | 13 + .../viewstate/PLVMPAuxiliaryPlayViewState.ts | 5 + .../PLVMPAuxiliaryVideoInfoViewState.ts | 7 + .../list/PLVMPDownloadListViewModel.ts | 28 ++ .../list/di/PLVMPDownloadListModule.ts | 23 ++ .../mediator/PLVMPDownloadListMediator.ts | 7 + .../list/model/PLVMPDownloadListRepo.ts | 8 + .../DownloadRequestBackgroundTaskUseCase.ts | 59 ++++ .../list/usecase/DownloadUpdateListUseCase.ts | 94 +++++ .../list/usecase/PLVMPDownloadListUseCases.ts | 10 + .../viewstate/PLVMPDownloadListViewState.ts | 41 +++ .../single/di/PLVMPDownloadItemModule.ts | 28 ++ .../mediator/PLVMPDownloadItemMediator.ts | 12 + .../single/model/PLVMPDownloadItemRepo.ts | 10 + .../viewmodel/PLVMPDownloadItemViewModel.ts | 39 +++ .../usecase/DownloadItemUpdateStateUseCase.ts | 59 ++++ .../usecase/PLVMPDownloadItemUseCases.ts | 8 + .../viewstate/PLVMPDownloadItemViewState.ts | 12 + .../modules/media/di/PLVMPMediaModule.ts | 45 +++ .../media/mediator/PLVMPMediaMediator.ts | 49 +++ .../modules/media/model/PLVMPMediaRepo.ts | 103 ++++++ .../media/viewmodel/PLVMPMediaViewModel.ts | 109 ++++++ .../usecase/HandleAudioFocusUseCase.ts | 31 ++ .../usecase/ObserveNetworkPoorUseCase.ts | 126 +++++++ .../viewmodel/usecase/PLVMPMediaUseCases.ts | 25 ++ .../usecase/UpdateBufferingSpeedUseCase.ts | 45 +++ .../usecase/UpdateMediaStateUseCase.ts | 101 ++++++ .../viewstate/PLVMPMediaInfoViewState.ts | 24 ++ .../viewstate/PLVMPMediaPlayViewState.ts | 13 + .../di/PLVMPMediaControlModule.ts | 29 ++ .../mediator/PLVMPMediaControllerMediator.ts | 24 ++ .../model/PLVMPMediaControllerRepo.ts | 17 + .../PLVMPMediaControllerViewModel.ts | 213 +++++++++++ .../usecase/PLVMPMediaControllerUseCases.ts | 13 + .../usecase/UpdateMediaStopOverlayUseCase.ts | 36 ++ .../PLVMPMediaControllerViewState.ts | 33 ++ .../pagecontrol/di/PLVMPPageControlModule.ts | 6 + .../viewmodel/PLVMPPageControlViewModel.ts | 15 + .../di/PLVMPProgressImageModule.ts | 25 ++ .../model/PLVMPProgressImageRepo.ts | 36 ++ .../PLVMPProgressImageLocalDataSource.ts | 27 ++ .../PLVMPProgressImageNetworkDataSource.ts | 24 ++ .../model/vo/PLVMPProgressImageData.ts | 14 + .../viewmodel/PLVMPProgressImageViewModel.ts | 51 +++ .../usecase/DecodeProgressImageUseCase.ts | 34 ++ ...PLVMediaPlayerAudioModeCoverLayoutLand.ets | 121 +++++++ ...PLVMediaPlayerAudioModeCoverLayoutPort.ets | 121 +++++++ .../PLVMediaPlayerAutoContinueHintLayout.ets | 45 +++ ...VMediaPlayerAuxiliaryCountDownTextView.ets | 35 ++ .../component/PLVMediaPlayerBackImageView.ets | 44 +++ .../PLVMediaPlayerBitRateSelectLayoutLand.ets | 102 ++++++ .../PLVMediaPlayerBitRateTextView.ets | 58 +++ ...PlayerBrightnessVolumeUpdateHintLayout.ets | 79 +++++ .../PLVMediaPlayerBufferingSpeedLayout.ets | 83 +++++ ...ediaPlayerControllerGradientMaskLayout.ets | 62 ++++ .../PLVMediaPlayerGestureHandleLayout.ets | 237 +++++++++++++ ...PlayerHandleOnEnterBackgroundComponent.ets | 70 ++++ .../PLVMediaPlayerLockControllerImageView.ets | 44 +++ ...PLVMediaPlayerLongPressSpeedHintLayout.ets | 54 +++ .../component/PLVMediaPlayerMarqueeLayout.ets | 62 ++++ .../PLVMediaPlayerMoreActionImageView.ets | 36 ++ ...diaPlayerMoreLayoutAudioModeActionView.ets | 49 +++ ...ediaPlayerMoreLayoutDownloadActionView.ets | 141 ++++++++ .../PLVMediaPlayerMoreLayoutLand.ets | 127 +++++++ .../PLVMediaPlayerMoreLayoutPort.ets | 170 +++++++++ ...ediaPlayerMoreLayoutSubtitleActionView.ets | 49 +++ ...diaPlayerMoreSubtitleSettingLayoutLand.ets | 202 +++++++++++ ...diaPlayerMoreSubtitleSettingLayoutPort.ets | 222 ++++++++++++ ...LVMediaPlayerNetworkPoorIndicateLayout.ets | 103 ++++++ .../ui/component/PLVMediaPlayerPlayButton.ets | 44 +++ ...PlayerPlayCompleteAutoRestartComponent.ets | 24 ++ ...PlayCompleteManualRestartOverlayLayout.ets | 49 +++ .../PLVMediaPlayerPlayErrorOverlayLayout.ets | 63 ++++ .../PLVMediaPlayerProgressTextView.ets | 56 +++ .../PLVMediaPlayerScreenshotImageView.ets | 119 +++++++ ...LVMediaPlayerSeekProgressPreviewLayout.ets | 49 +++ ...MediaPlayerSeekProgressPreviewTextView.ets | 57 +++ .../PLVMediaPlayerSpeedSelectLayoutLand.ets | 99 ++++++ .../component/PLVMediaPlayerSpeedTextView.ets | 43 +++ .../PLVMediaPlayerSubtitleTextLayout.ets | 63 ++++ .../PLVMediaPlayerSwitchBitRateHintLayout.ets | 67 ++++ .../component/PLVMediaPlayerTitleTextView.ets | 44 +++ .../ui/component/marquee/PLVMarqueeView.ets | 59 ++++ .../model/PLVMarqueeAnimateSettingVO.ts | 72 ++++ .../marquee/model/PLVMarqueeModel.ts | 32 ++ .../marquee/model/PLVMarqueeTextSettingVO.ts | 60 ++++ .../PLVMarqueeDoubleMarqueeSubTextView.ets | 114 ++++++ .../view/PLVMarqueeFlicker15PercentView.ets | 133 +++++++ .../PLVMarqueeFlickerDoubleMarqueeView.ets | 19 + .../marquee/view/PLVMarqueeFlickerView.ets | 126 +++++++ .../view/PLVMarqueeRoll15PercentView.ets | 143 ++++++++ .../view/PLVMarqueeRollDoubleMarqueeView.ets | 19 + .../view/PLVMarqueeRollFlickerView.ets | 182 ++++++++++ .../marquee/view/PLVMarqueeRollView.ets | 134 +++++++ .../common/ui/enums/PLVMediaPlayerScenes.ts | 44 +++ .../main/ets/common/ui/ext/ImageKitExts.ets | 20 ++ .../ets/common/utils/PLVAbilityContexts.ts | 33 ++ .../common/utils/PLVBackgroundTaskManager.ts | 88 +++++ .../ets/common/utils/PLVBrightnessManager.ts | 45 +++ .../ets/common/utils/PLVComponentLifecycle.ts | 45 +++ .../main/ets/common/utils/PLVDisplayUtils.ts | 81 +++++ .../ets/common/utils/PLVOrientationManager.ts | 29 ++ .../utils/PLVOrientationManagerObserver.ets | 23 ++ .../src/main/ets/common/utils/PLVTimeUtils.ts | 24 ++ .../ets/common/utils/arkts-no-everything.ts | 85 +++++ polyv/src/main/module.json5 | 10 + .../main/resources/base/element/string.json | 132 +++++++ ...audio_mode_audio_volume_visualize_port.svg | 7 + ...ia_player_audio_mode_cover_placeholder.svg | 12 + ..._media_player_audio_mode_image_wrap_bg.svg | 44 +++ ...ia_player_audio_mode_switch_video_icon.svg | 14 + .../base/media/plv_media_player_back_icon.svg | 10 + .../plv_media_player_brightness_hint_icon.svg | 38 ++ ...plv_media_player_float_menu_close_icon.svg | 20 ++ .../plv_media_player_full_screen_icon.svg | 27 ++ ...a_player_lock_orientation_icon_locking.svg | 21 ++ ...a_player_lock_orientation_icon_no_lock.svg | 17 + ..._player_long_press_speed_control_anim.webp | Bin 0 -> 20884 bytes .../plv_media_player_more_action_icon.svg | 14 + ...yer_more_audio_mode_action_icon_active.svg | 23 ++ ...e_audio_mode_action_icon_inactive_land.svg | 23 ++ ...e_audio_mode_action_icon_inactive_port.svg | 23 ++ ..._player_more_download_action_icon_land.svg | 24 ++ ..._player_more_download_action_icon_port.svg | 24 ++ ..._more_subtitle_action_icon_active_land.svg | 27 ++ ..._more_subtitle_action_icon_active_port.svg | 27 ++ ...ore_subtitle_action_icon_inactive_land.svg | 28 ++ ...ore_subtitle_action_icon_inactive_port.svg | 28 ++ ...media_player_play_button_icon_to_pause.svg | 17 + ..._media_player_play_button_icon_to_play.svg | 12 + .../media/plv_media_player_restart_icon.svg | 10 + .../plv_media_player_screenshot_icon.png | Bin 0 -> 1204 bytes .../plv_media_player_volume_hint_icon.svg | 17 + .../main/resources/en_US/element/string.json | 8 + .../main/resources/zh_CN/element/string.json | 8 + products/expert/oh-package-lock.json5 | 196 ++++++++++- products/expert/oh-package.json5 | 10 +- .../main/ets/entryability/EntryAbility.ets | 7 +- .../PLVMediaPlayerSingleVideoPage.ets | 72 ++++ .../ets/startup/PLVMediaPlayerStartUp.ets | 104 ++++++ .../resources/base/profile/main_pages.json | 3 +- scene_single_video/.gitignore | 11 + scene_single_video/Index.ets | 1 + scene_single_video/build-profile.json5 | 28 ++ scene_single_video/hvigorfile.ts | 6 + scene_single_video/obfuscation-rules.txt | 18 + scene_single_video/oh-package.json5 | 12 + .../PLVMediaPlayerSingleVideoLayout.ets | 205 +++++++++++ .../PLVMediaPlayerAuxiliaryLayout.ets | 152 ++++++++ ...PLVMediaPlayerProgressSliderSingleLand.ets | 119 +++++++ ...PLVMediaPlayerProgressSliderSinglePort.ets | 119 +++++++ ...ayerSwitchToFullScreenButtonSinglePort.ets | 30 ++ ...MediaPlayerSingleVideoControllerLayout.ets | 29 ++ ...aPlayerSingleVideoControllerLayoutLand.ets | 330 ++++++++++++++++++ ...aPlayerSingleVideoControllerLayoutPort.ets | 286 +++++++++++++++ ...ediaPlayerSingleVideoFloatActionLayout.ets | 40 +++ scene_single_video/src/main/module.json5 | 10 + .../main/resources/base/element/string.json | 8 + .../main/resources/en_US/element/string.json | 8 + .../main/resources/zh_CN/element/string.json | 8 + 182 files changed, 9605 insertions(+), 8 deletions(-) create mode 100644 commons/basic/oh-package-lock.json5 create mode 100644 features/Home/src/main/ets/pages/PLVMediaPlayerSingleVideo.ets create mode 100644 features/Home/src/main/ets/polyv/PLVMockFeedResourceDataSource.ets create mode 100644 features/Home/src/main/ets/polyv/PLVMockMediaResourceData.ts create mode 100644 polyv/.gitignore create mode 100644 polyv/Index.ets create mode 100644 polyv/build-profile.json5 create mode 100644 polyv/hvigorfile.ts create mode 100644 polyv/obfuscation-rules.txt create mode 100644 polyv/oh-package.json5 create mode 100644 polyv/src/main/ets/common/di/PLVMPCommonModule.ts create mode 100644 polyv/src/main/ets/common/modules/auxiliary/di/PLVMPAuxiliaryModule.ts create mode 100644 polyv/src/main/ets/common/modules/auxiliary/mediator/PLVMPAuxiliaryMediator.ts create mode 100644 polyv/src/main/ets/common/modules/auxiliary/model/PLVMPAuxiliaryRepo.ts create mode 100644 polyv/src/main/ets/common/modules/auxiliary/viewmodel/PLVMPAuxiliaryViewModel.ts create mode 100644 polyv/src/main/ets/common/modules/auxiliary/viewmodel/usecases/AuxiliaryBeforePlayListener.ts create mode 100644 polyv/src/main/ets/common/modules/auxiliary/viewmodel/usecases/AuxiliaryUpdateMediaStateUseCase.ts create mode 100644 polyv/src/main/ets/common/modules/auxiliary/viewmodel/usecases/PLVMPAuxiliaryUseCases.ts create mode 100644 polyv/src/main/ets/common/modules/auxiliary/viewmodel/viewstate/PLVMPAuxiliaryInfoViewState.ts create mode 100644 polyv/src/main/ets/common/modules/auxiliary/viewmodel/viewstate/PLVMPAuxiliaryPlayViewState.ts create mode 100644 polyv/src/main/ets/common/modules/auxiliary/viewmodel/viewstate/PLVMPAuxiliaryVideoInfoViewState.ts create mode 100644 polyv/src/main/ets/common/modules/download/list/PLVMPDownloadListViewModel.ts create mode 100644 polyv/src/main/ets/common/modules/download/list/di/PLVMPDownloadListModule.ts create mode 100644 polyv/src/main/ets/common/modules/download/list/mediator/PLVMPDownloadListMediator.ts create mode 100644 polyv/src/main/ets/common/modules/download/list/model/PLVMPDownloadListRepo.ts create mode 100644 polyv/src/main/ets/common/modules/download/list/usecase/DownloadRequestBackgroundTaskUseCase.ts create mode 100644 polyv/src/main/ets/common/modules/download/list/usecase/DownloadUpdateListUseCase.ts create mode 100644 polyv/src/main/ets/common/modules/download/list/usecase/PLVMPDownloadListUseCases.ts create mode 100644 polyv/src/main/ets/common/modules/download/list/viewstate/PLVMPDownloadListViewState.ts create mode 100644 polyv/src/main/ets/common/modules/download/single/di/PLVMPDownloadItemModule.ts create mode 100644 polyv/src/main/ets/common/modules/download/single/mediator/PLVMPDownloadItemMediator.ts create mode 100644 polyv/src/main/ets/common/modules/download/single/model/PLVMPDownloadItemRepo.ts create mode 100644 polyv/src/main/ets/common/modules/download/single/viewmodel/PLVMPDownloadItemViewModel.ts create mode 100644 polyv/src/main/ets/common/modules/download/single/viewmodel/usecase/DownloadItemUpdateStateUseCase.ts create mode 100644 polyv/src/main/ets/common/modules/download/single/viewmodel/usecase/PLVMPDownloadItemUseCases.ts create mode 100644 polyv/src/main/ets/common/modules/download/single/viewmodel/viewstate/PLVMPDownloadItemViewState.ts create mode 100644 polyv/src/main/ets/common/modules/media/di/PLVMPMediaModule.ts create mode 100644 polyv/src/main/ets/common/modules/media/mediator/PLVMPMediaMediator.ts create mode 100644 polyv/src/main/ets/common/modules/media/model/PLVMPMediaRepo.ts create mode 100644 polyv/src/main/ets/common/modules/media/viewmodel/PLVMPMediaViewModel.ts create mode 100644 polyv/src/main/ets/common/modules/media/viewmodel/usecase/HandleAudioFocusUseCase.ts create mode 100644 polyv/src/main/ets/common/modules/media/viewmodel/usecase/ObserveNetworkPoorUseCase.ts create mode 100644 polyv/src/main/ets/common/modules/media/viewmodel/usecase/PLVMPMediaUseCases.ts create mode 100644 polyv/src/main/ets/common/modules/media/viewmodel/usecase/UpdateBufferingSpeedUseCase.ts create mode 100644 polyv/src/main/ets/common/modules/media/viewmodel/usecase/UpdateMediaStateUseCase.ts create mode 100644 polyv/src/main/ets/common/modules/media/viewmodel/viewstate/PLVMPMediaInfoViewState.ts create mode 100644 polyv/src/main/ets/common/modules/media/viewmodel/viewstate/PLVMPMediaPlayViewState.ts create mode 100644 polyv/src/main/ets/common/modules/mediacontroller/di/PLVMPMediaControlModule.ts create mode 100644 polyv/src/main/ets/common/modules/mediacontroller/mediator/PLVMPMediaControllerMediator.ts create mode 100644 polyv/src/main/ets/common/modules/mediacontroller/model/PLVMPMediaControllerRepo.ts create mode 100644 polyv/src/main/ets/common/modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel.ts create mode 100644 polyv/src/main/ets/common/modules/mediacontroller/viewmodel/usecase/PLVMPMediaControllerUseCases.ts create mode 100644 polyv/src/main/ets/common/modules/mediacontroller/viewmodel/usecase/UpdateMediaStopOverlayUseCase.ts create mode 100644 polyv/src/main/ets/common/modules/mediacontroller/viewmodel/viewstate/PLVMPMediaControllerViewState.ts create mode 100644 polyv/src/main/ets/common/modules/pagecontrol/di/PLVMPPageControlModule.ts create mode 100644 polyv/src/main/ets/common/modules/pagecontrol/viewmodel/PLVMPPageControlViewModel.ts create mode 100644 polyv/src/main/ets/common/modules/progressImage/di/PLVMPProgressImageModule.ts create mode 100644 polyv/src/main/ets/common/modules/progressImage/model/PLVMPProgressImageRepo.ts create mode 100644 polyv/src/main/ets/common/modules/progressImage/model/datasource/PLVMPProgressImageLocalDataSource.ts create mode 100644 polyv/src/main/ets/common/modules/progressImage/model/datasource/PLVMPProgressImageNetworkDataSource.ts create mode 100644 polyv/src/main/ets/common/modules/progressImage/model/vo/PLVMPProgressImageData.ts create mode 100644 polyv/src/main/ets/common/modules/progressImage/viewmodel/PLVMPProgressImageViewModel.ts create mode 100644 polyv/src/main/ets/common/modules/progressImage/viewmodel/usecase/DecodeProgressImageUseCase.ts create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerAudioModeCoverLayoutLand.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerAudioModeCoverLayoutPort.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerAutoContinueHintLayout.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerAuxiliaryCountDownTextView.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerBackImageView.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerBitRateSelectLayoutLand.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerBitRateTextView.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerBrightnessVolumeUpdateHintLayout.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerBufferingSpeedLayout.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerControllerGradientMaskLayout.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerGestureHandleLayout.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerHandleOnEnterBackgroundComponent.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerLockControllerImageView.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerLongPressSpeedHintLayout.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerMarqueeLayout.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreActionImageView.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutAudioModeActionView.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutDownloadActionView.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutLand.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutPort.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutSubtitleActionView.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreSubtitleSettingLayoutLand.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreSubtitleSettingLayoutPort.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerNetworkPoorIndicateLayout.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerPlayButton.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerPlayCompleteAutoRestartComponent.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerPlayCompleteManualRestartOverlayLayout.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerPlayErrorOverlayLayout.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerProgressTextView.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerScreenshotImageView.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerSeekProgressPreviewLayout.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerSeekProgressPreviewTextView.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerSpeedSelectLayoutLand.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerSpeedTextView.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerSubtitleTextLayout.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerSwitchBitRateHintLayout.ets create mode 100644 polyv/src/main/ets/common/ui/component/PLVMediaPlayerTitleTextView.ets create mode 100644 polyv/src/main/ets/common/ui/component/marquee/PLVMarqueeView.ets create mode 100644 polyv/src/main/ets/common/ui/component/marquee/model/PLVMarqueeAnimateSettingVO.ts create mode 100644 polyv/src/main/ets/common/ui/component/marquee/model/PLVMarqueeModel.ts create mode 100644 polyv/src/main/ets/common/ui/component/marquee/model/PLVMarqueeTextSettingVO.ts create mode 100644 polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeDoubleMarqueeSubTextView.ets create mode 100644 polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeFlicker15PercentView.ets create mode 100644 polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeFlickerDoubleMarqueeView.ets create mode 100644 polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeFlickerView.ets create mode 100644 polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeRoll15PercentView.ets create mode 100644 polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeRollDoubleMarqueeView.ets create mode 100644 polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeRollFlickerView.ets create mode 100644 polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeRollView.ets create mode 100644 polyv/src/main/ets/common/ui/enums/PLVMediaPlayerScenes.ts create mode 100644 polyv/src/main/ets/common/ui/ext/ImageKitExts.ets create mode 100644 polyv/src/main/ets/common/utils/PLVAbilityContexts.ts create mode 100644 polyv/src/main/ets/common/utils/PLVBackgroundTaskManager.ts create mode 100644 polyv/src/main/ets/common/utils/PLVBrightnessManager.ts create mode 100644 polyv/src/main/ets/common/utils/PLVComponentLifecycle.ts create mode 100644 polyv/src/main/ets/common/utils/PLVDisplayUtils.ts create mode 100644 polyv/src/main/ets/common/utils/PLVOrientationManager.ts create mode 100644 polyv/src/main/ets/common/utils/PLVOrientationManagerObserver.ets create mode 100644 polyv/src/main/ets/common/utils/PLVTimeUtils.ts create mode 100644 polyv/src/main/ets/common/utils/arkts-no-everything.ts create mode 100644 polyv/src/main/module.json5 create mode 100644 polyv/src/main/resources/base/element/string.json create mode 100644 polyv/src/main/resources/base/media/plv_media_player_audio_mode_audio_volume_visualize_port.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_audio_mode_cover_placeholder.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_audio_mode_image_wrap_bg.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_audio_mode_switch_video_icon.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_back_icon.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_brightness_hint_icon.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_float_menu_close_icon.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_full_screen_icon.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_lock_orientation_icon_locking.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_lock_orientation_icon_no_lock.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_long_press_speed_control_anim.webp create mode 100644 polyv/src/main/resources/base/media/plv_media_player_more_action_icon.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_more_audio_mode_action_icon_active.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_more_audio_mode_action_icon_inactive_land.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_more_audio_mode_action_icon_inactive_port.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_more_download_action_icon_land.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_more_download_action_icon_port.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_more_subtitle_action_icon_active_land.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_more_subtitle_action_icon_active_port.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_more_subtitle_action_icon_inactive_land.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_more_subtitle_action_icon_inactive_port.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_play_button_icon_to_pause.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_play_button_icon_to_play.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_restart_icon.svg create mode 100644 polyv/src/main/resources/base/media/plv_media_player_screenshot_icon.png create mode 100644 polyv/src/main/resources/base/media/plv_media_player_volume_hint_icon.svg create mode 100644 polyv/src/main/resources/en_US/element/string.json create mode 100644 polyv/src/main/resources/zh_CN/element/string.json create mode 100644 products/expert/src/main/ets/pages/VideoPage/PLVMediaPlayerSingleVideoPage.ets create mode 100644 products/expert/src/main/ets/startup/PLVMediaPlayerStartUp.ets create mode 100644 scene_single_video/.gitignore create mode 100644 scene_single_video/Index.ets create mode 100644 scene_single_video/build-profile.json5 create mode 100644 scene_single_video/hvigorfile.ts create mode 100644 scene_single_video/obfuscation-rules.txt create mode 100644 scene_single_video/oh-package.json5 create mode 100644 scene_single_video/src/main/ets/scene/single/PLVMediaPlayerSingleVideoLayout.ets create mode 100644 scene_single_video/src/main/ets/scene/single/component/PLVMediaPlayerAuxiliaryLayout.ets create mode 100644 scene_single_video/src/main/ets/scene/single/component/PLVMediaPlayerProgressSliderSingleLand.ets create mode 100644 scene_single_video/src/main/ets/scene/single/component/PLVMediaPlayerProgressSliderSinglePort.ets create mode 100644 scene_single_video/src/main/ets/scene/single/component/PLVMediaPlayerSwitchToFullScreenButtonSinglePort.ets create mode 100644 scene_single_video/src/main/ets/scene/single/layout/PLVMediaPlayerSingleVideoControllerLayout.ets create mode 100644 scene_single_video/src/main/ets/scene/single/layout/PLVMediaPlayerSingleVideoControllerLayoutLand.ets create mode 100644 scene_single_video/src/main/ets/scene/single/layout/PLVMediaPlayerSingleVideoControllerLayoutPort.ets create mode 100644 scene_single_video/src/main/ets/scene/single/layout/PLVMediaPlayerSingleVideoFloatActionLayout.ets create mode 100644 scene_single_video/src/main/module.json5 create mode 100644 scene_single_video/src/main/resources/base/element/string.json create mode 100644 scene_single_video/src/main/resources/en_US/element/string.json create mode 100644 scene_single_video/src/main/resources/zh_CN/element/string.json diff --git a/commons/basic/oh-package-lock.json5 b/commons/basic/oh-package-lock.json5 new file mode 100644 index 0000000..22f9d34 --- /dev/null +++ b/commons/basic/oh-package-lock.json5 @@ -0,0 +1,9 @@ +{ + "meta": { + "stableOrder": true + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": {}, + "packages": {} +} \ No newline at end of file diff --git a/commons/basic/oh-package.json5 b/commons/basic/oh-package.json5 index f15dd9f..65b9b86 100644 --- a/commons/basic/oh-package.json5 +++ b/commons/basic/oh-package.json5 @@ -5,5 +5,7 @@ "main": "Index.ets", "author": "", "license": "Apache-2.0", - "dependencies": {} + "dependencies": { + + } } diff --git a/features/Home/oh-package-lock.json5 b/features/Home/oh-package-lock.json5 index cc3bce5..b98e834 100644 --- a/features/Home/oh-package-lock.json5 +++ b/features/Home/oh-package-lock.json5 @@ -5,7 +5,21 @@ "lockfileVersion": 3, "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", "specifiers": { - "@itcast/basic@../../commons/basic": "@itcast/basic@../../commons/basic" + "@itcast/basic@../../commons/basic": "@itcast/basic@../../commons/basic", + "@ohos/crypto-js@2.0.3": "@ohos/crypto-js@2.0.4", + "@ohos/crypto-js@^2.0.2": "@ohos/crypto-js@2.0.4", + "@ohos/httpclient@2.0.2": "@ohos/httpclient@2.0.2", + "@polyvharmony/httpdns-api@1.0.2": "@polyvharmony/httpdns-api@1.0.2", + "@polyvharmony/media-player-business@2.5.0": "@polyvharmony/media-player-business@2.5.0", + "@polyvharmony/media-player-core-api@2.5.0": "@polyvharmony/media-player-core-api@2.5.0", + "@polyvharmony/media-player-core-ijk@2.5.0": "@polyvharmony/media-player-core-ijk@2.5.0", + "@polyvharmony/media-player-foundation@2.5.0": "@polyvharmony/media-player-foundation@2.5.0", + "@polyvharmony/media-player-sdk-addon-cache-down@2.5.0": "@polyvharmony/media-player-sdk-addon-cache-down@2.5.0", + "@polyvharmony/media-player-sdk@2.5.0": "@polyvharmony/media-player-sdk@2.5.0", + "base64-js@^1.5.1": "base64-js@1.5.1", + "media-player-common@../../polyv": "media-player-common@../../polyv", + "pako@^2.1.0": "pako@2.1.0", + "scene_single_video@../../scene_single_video": "scene_single_video@../../scene_single_video" }, "packages": { "@itcast/basic@../../commons/basic": { @@ -13,6 +27,138 @@ "version": "1.0.0", "resolved": "../../commons/basic", "registryType": "local" + }, + "@ohos/crypto-js@2.0.4": { + "name": "@ohos/crypto-js", + "version": "2.0.4", + "integrity": "sha512-589ur6oqU1UNibqefMly2cwEeEhkSoCAA3uc+oNUwRnYYtevn/kQnO+Coi36N+VJSeeg/uFzZk1K/wUMdovpOA==", + "resolved": "https://repo.harmonyos.com/ohpm/@ohos/crypto-js/-/crypto-js-2.0.4.har", + "registryType": "ohpm" + }, + "@ohos/httpclient@2.0.2": { + "name": "@ohos/httpclient", + "version": "2.0.2", + "integrity": "sha512-acFEfQ9ZJti4KEYDBErCq0W85uc2khen41LIkBLVcfFXmgZAGeRKV+CPiSyG8v+5nUD7Wf2ncjBxxPxr6moqVg==", + "resolved": "https://repo.harmonyos.com/ohpm/@ohos/httpclient/-/httpclient-2.0.2.har", + "registryType": "ohpm", + "dependencies": { + "pako": "^2.1.0", + "@ohos/crypto-js": "^2.0.2", + "base64-js": "^1.5.1" + } + }, + "@polyvharmony/httpdns-api@1.0.2": { + "name": "@polyvharmony/httpdns-api", + "version": "1.0.2", + "integrity": "sha512-p3vd3i4oClAp/rf2RD3vIdKRSisabOB/+K+wYB9nHuf5ZDTw3Yv4o+dLQewm0atqf00JGF3Y0IiwURCpryJX5Q==", + "resolved": "https://repo.harmonyos.com/ohpm/@polyvharmony/httpdns-api/-/httpdns-api-1.0.2.har", + "registryType": "ohpm" + }, + "@polyvharmony/media-player-business@2.5.0": { + "name": "@polyvharmony/media-player-business", + "version": "2.5.0", + "integrity": "sha512-ta9YBq8cvwNPuRwQpAopodFB0WHbAfRUhgosGqT+UDEHZtlXsajLFmplPhoqYpFb649FVe6vDjVdHgsp6bKmtQ==", + "resolved": "https://repo.harmonyos.com/ohpm/@polyvharmony/media-player-business/-/media-player-business-2.5.0.har", + "registryType": "ohpm", + "dependencies": { + "@polyvharmony/media-player-core-api": "2.5.0", + "@polyvharmony/media-player-foundation": "2.5.0", + "@ohos/httpclient": "2.0.2", + "@ohos/crypto-js": "2.0.3" + } + }, + "@polyvharmony/media-player-core-api@2.5.0": { + "name": "@polyvharmony/media-player-core-api", + "version": "2.5.0", + "integrity": "sha512-23Z4p0nPWAGJuifAr5qfhB39b0RdU7LQnOKPJTtPX4fc3qyGDoyQ7Y4/e+WulTm/JaDgsQ7xTn+Hmq+ryNl5cA==", + "resolved": "https://repo.harmonyos.com/ohpm/@polyvharmony/media-player-core-api/-/media-player-core-api-2.5.0.har", + "registryType": "ohpm", + "dependencies": { + "@polyvharmony/media-player-foundation": "2.5.0" + } + }, + "@polyvharmony/media-player-core-ijk@2.5.0": { + "name": "@polyvharmony/media-player-core-ijk", + "version": "2.5.0", + "integrity": "sha512-LieKq0vm8QpTNLzw+QSVRok9xoJV0fDVR4Aw1bRClnpd4Mmb99JvwuW6isCRYsrZJievsxbbNet/qSqjb+OJhw==", + "resolved": "https://repo.harmonyos.com/ohpm/@polyvharmony/media-player-core-ijk/-/media-player-core-ijk-2.5.0.har", + "registryType": "ohpm", + "dependencies": { + "@polyvharmony/media-player-core-api": "2.5.0", + "@polyvharmony/media-player-foundation": "2.5.0" + } + }, + "@polyvharmony/media-player-foundation@2.5.0": { + "name": "@polyvharmony/media-player-foundation", + "version": "2.5.0", + "integrity": "sha512-P1beEIZs2ySGrNGWo2RRi8/hQopZkLE6Cdi+53/UJI4iXFeciWZQMXold3KBqlMzNm369BEysaoprtCsWLMC8Q==", + "resolved": "https://repo.harmonyos.com/ohpm/@polyvharmony/media-player-foundation/-/media-player-foundation-2.5.0.har", + "registryType": "ohpm", + "dependencies": { + "@ohos/httpclient": "2.0.2", + "@ohos/crypto-js": "2.0.3", + "@polyvharmony/httpdns-api": "1.0.2" + } + }, + "@polyvharmony/media-player-sdk-addon-cache-down@2.5.0": { + "name": "@polyvharmony/media-player-sdk-addon-cache-down", + "version": "2.5.0", + "integrity": "sha512-wN6OQaQm65GPL39nVWYx9vRyua6TflbIXddoataxHrlW2Nf7N3PJZjmFW0zaTPC5EyzsnmkRh/4zN2zHvyQDUA==", + "resolved": "https://repo.harmonyos.com/ohpm/@polyvharmony/media-player-sdk-addon-cache-down/-/media-player-sdk-addon-cache-down-2.5.0.har", + "registryType": "ohpm", + "dependencies": { + "@polyvharmony/media-player-business": "2.5.0", + "@polyvharmony/media-player-foundation": "2.5.0", + "@polyvharmony/media-player-core-ijk": "2.5.0" + } + }, + "@polyvharmony/media-player-sdk@2.5.0": { + "name": "@polyvharmony/media-player-sdk", + "version": "2.5.0", + "integrity": "sha512-Of8xCFhAD0O8s7Q0jzwSf4potYJJcI/j+MrNvU23sgXnEAX6DG7azIehuCn+k6AMHvxgLF1LlEMp17v3pkc/XA==", + "resolved": "https://repo.harmonyos.com/ohpm/@polyvharmony/media-player-sdk/-/media-player-sdk-2.5.0.har", + "registryType": "ohpm", + "dependencies": { + "@polyvharmony/media-player-business": "2.5.0", + "@polyvharmony/media-player-core-api": "2.5.0", + "@polyvharmony/media-player-foundation": "2.5.0" + } + }, + "base64-js@1.5.1": { + "name": "base64-js", + "version": "1.5.1", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "resolved": "https://repo.harmonyos.com/ohpm/base64-js/-/base64-js-1.5.1.tgz", + "shasum": "1b1b440160a5bf7ad40b650f095963481903930a", + "registryType": "ohpm" + }, + "media-player-common@../../polyv": { + "name": "media-player-common", + "version": "2.5.0", + "resolved": "../../polyv", + "registryType": "local", + "dependencies": { + "@polyvharmony/media-player-sdk": "2.5.0", + "@polyvharmony/media-player-sdk-addon-cache-down": "2.5.0" + } + }, + "pako@2.1.0": { + "name": "pako", + "version": "2.1.0", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "resolved": "https://repo.harmonyos.com/ohpm/pako/-/pako-2.1.0.tgz", + "shasum": "266cc37f98c7d883545d11335c00fbd4062c9a86", + "registryType": "ohpm" + }, + "scene_single_video@../../scene_single_video": { + "name": "scene_single_video", + "version": "2.5.0", + "resolved": "../../scene_single_video", + "registryType": "local", + "dependencies": { + "media-player-common": "file:../polyv", + "@polyvharmony/media-player-sdk": "2.5.0" + } } } } \ No newline at end of file diff --git a/features/Home/oh-package.json5 b/features/Home/oh-package.json5 index 44c28bc..51d1ebc 100644 --- a/features/Home/oh-package.json5 +++ b/features/Home/oh-package.json5 @@ -6,6 +6,10 @@ "author": "", "license": "Apache-2.0", "dependencies": { - "@itcast/basic": "file:../../commons/basic" + "@itcast/basic": "file:../../commons/basic", + "@polyvharmony/media-player-sdk": "2.5.0", + "@polyvharmony/media-player-sdk-addon-cache-down": "2.5.0", + "scene_single_video": "file:../../scene_single_video", + "media-player-common": "file:../../polyv" } } diff --git a/features/Home/src/main/ets/components/ItemComp.ets b/features/Home/src/main/ets/components/ItemComp.ets index 9b5978d..e4cb8a5 100644 --- a/features/Home/src/main/ets/components/ItemComp.ets +++ b/features/Home/src/main/ets/components/ItemComp.ets @@ -1,10 +1,17 @@ - +import { runCatching, seconds } from '@polyvharmony/media-player-sdk'; import { MeetingItemModel,ItemModel } from '../model/ItemModel' import { BasicConstant } from '@itcast/basic' +import { promptAction, router } from '@kit.ArkUI'; +import { PLVMockMediaResourceData } from '../polyv/PLVMockMediaResourceData' +import { + PLVMediaPlayerSingleVideoPageParam +} from 'media-player-common'; @Preview @Component export struct ItemComp { item: MeetingItemModel = new MeetingItemModel({} as ItemModel) + + @State timeColor:ResourceStr=$r('app.color.ee432f') @State status:string='' @State heightss:Length=247 @@ -57,7 +64,23 @@ export struct ItemComp { } .height(162).backgroundColor(Color.Orange) .margin({top:10,right:5}).clip(true).id('rr') + .onClick(async()=>{ + const mediaResourcesResult = await runCatching(PLVMockMediaResourceData.getInstance().setupMediaResourcesFromLocal('e97dbe3e648aefc2eb6f68b96db9db6c_e')) + if (mediaResourcesResult.success === false) { + promptAction.showToast({ + message: `'视频数据初始化失败': ${mediaResourcesResult.error}`, + duration: seconds(3).toMillis() + }) + return + } + const mediaResource = mediaResourcesResult.data[0] + router.pushUrl({ + url:'pages/VideoPage/PLVMediaPlayerSingleVideoPage', + params: new PLVMediaPlayerSingleVideoPageParam(mediaResource) + }) + }) } + Row(){ Image($r('app.media.meetingtime')).width(13).height(13) Text(this.getTime(this.item.begin_date,this.item.end_date)).fontSize(14) @@ -89,6 +112,7 @@ export struct ItemComp { }.backgroundColor($r('app.color.e4e4e4')) .width('100%').padding({left:10,right:10}).clip(true) + } getTime(str1:string, str2:string) { diff --git a/features/Home/src/main/ets/pages/PLVMediaPlayerSingleVideo.ets b/features/Home/src/main/ets/pages/PLVMediaPlayerSingleVideo.ets new file mode 100644 index 0000000..ff75b03 --- /dev/null +++ b/features/Home/src/main/ets/pages/PLVMediaPlayerSingleVideo.ets @@ -0,0 +1,6 @@ + +@Component +export struct PLVMediaPlayerSingleVideo { + build() { + } +} \ No newline at end of file diff --git a/features/Home/src/main/ets/polyv/PLVMockFeedResourceDataSource.ets b/features/Home/src/main/ets/polyv/PLVMockFeedResourceDataSource.ets new file mode 100644 index 0000000..09b837c --- /dev/null +++ b/features/Home/src/main/ets/polyv/PLVMockFeedResourceDataSource.ets @@ -0,0 +1,16 @@ +import { PLVMediaResource } from '@polyvharmony/media-player-sdk'; +import { PLVMockMediaResourceData } from './PLVMockMediaResourceData'; + +export class PLVMockFeedResourceDataSource implements IPLVMediaPlayerFeedResourceDataSource { + async onLoadMoreData(fromIndex: number): Promise { + const result = await PLVMockMediaResourceData.getInstance().getMediaResources(); + return result.slice(fromIndex, fromIndex + 5); + } +} + + +export interface IPLVMediaPlayerFeedResourceDataSource { + + onLoadMoreData(fromIndex: number): Promise + +} \ No newline at end of file diff --git a/features/Home/src/main/ets/polyv/PLVMockMediaResourceData.ts b/features/Home/src/main/ets/polyv/PLVMockMediaResourceData.ts new file mode 100644 index 0000000..8a83ea8 --- /dev/null +++ b/features/Home/src/main/ets/polyv/PLVMockMediaResourceData.ts @@ -0,0 +1,114 @@ +import { + error, + PLVGeneralApiManager, + PLVMediaPlayerAppContext, + PLVMediaResource, + PLVUrlMediaResource, + PLVViewerParam, + PLVVodMainAccountAuthentication, + PLVVodMediaResource +} from '@polyvharmony/media-player-sdk'; +import {PLVMediaDownloadSetting} from '@polyvharmony/media-player-sdk-addon-cache-down'; + +// const mockAuthentication: PLVVodMainAccountAuthentication = { +// userId: "cfb7a69a75", +// secretKey: "4KtJhl9EqU" +// } +const mockAuthentication: PLVVodMainAccountAuthentication = { + userId: "e97dbe3e64", + secretKey: "zMV29c519P" +} + +const mockViewerParam: PLVViewerParam = { + viewerId: "123", + viewerName: "123" +} + +export class PLVMockMediaResourceData { + + // + + private static readonly instance = new PLVMockMediaResourceData() + + private constructor() { + this.setupMediaResources() + } + + static getInstance() { + return PLVMockMediaResourceData.instance + } + + // + + private mediaResources?: Promise + + private setupMediaResources() { + // this.setupMediaResourcesFromLocal() + // this.setupMediaResourcesFromServer() + } + + getMediaResources(): Promise { + if (!this.mediaResources) { + error("Must call setup before getMediaResources") + } + return this.mediaResources + } + + public setupMediaResourcesFromLocal(vid:string):Promise { + return this.mediaResources = Promise.resolve([ + vod(vid) + // vod("e97dbe3e648aefc2eb6f68b96db9db6c_e"), + // vod("e97dbe3e6401ea8f76617bafe32f57e9_e"), + // vod("e97dbe3e64ed6e0aac558e43787df1b4_e"), + // vod("e97dbe3e646f8f565c015f361025c51c_e"), + // vod("e97dbe3e64755eda79bbda0c8c9a939e_e"), + // vod("e97dbe3e6492596e7e680c4c7b99ca1b_e"), + // vod("e97dbe3e641a81a1e87750a2522b22c9_e"), + // vod("e97dbe3e64f6f6d1f75aa6d16a2d128e_e"), + // vod("e97dbe3e64b6dd24f868c16335570343_e"), + // vod("e97dbe3e64b2ab3301c3289a5731cbb0_e"), + // vod("e97dbe3e649c4f6743ca640bda94230c_e"), + // vod("e97dbe3e64ae88c87769c9dba0aad552_e"), + // vod("e97dbe3e640cd100431e12a8f8313c7d_e"), + // vod("e97dbe3e6423671524f4601cb652bd0d_e"), + // vod("e97dbe3e645083078cb42da5aac89b7f_e"), + // vod("e97dbe3e64414e6dad17196f652c021f_e"), + // vod("e97dbe3e64fba07447f7c37b283fd76d_e"), + // vod("e97dbe3e643ea0a62166780aeab7c43c_e"), + // vod("e97dbe3e64436506a71c7cbeecf001de_e"), + // vod("e97dbe3e64b564d0daac43002effcb48_e"), + // vod("e97dbe3e64e4f1bd6c28c395703fc4d8_e"), + // vod("e97dbe3e6400b7c243ecacf0a45e3fbb_e"), + // vod("e97dbe3e648ca60cad8017983a07ecc9_e"), + // vod("e97dbe3e646a24f4c61a45dcbf9354ce_e"), + // vod("e97dbe3e641b079d268330cc274fe3b4_e"), + // vod("e97dbe3e642eb6b15f9983949ffbdea7_e") + ]) + } + + private setupMediaResourcesFromServer() { + this.mediaResources = PLVGeneralApiManager.getVodVideoList(mockAuthentication, 1, 20) + .then(result => { + return result.data!.map(video => vod(video!.vid!)) + }) + } + +} + + function vod(videoId: string): PLVVodMediaResource { + // 默认下载路径 + const defaultDownloadRoot = PLVMediaDownloadSetting.defaultSetting(PLVMediaPlayerAppContext.getInstance().appContext!).downloadRootDirectory + return { + videoId: videoId, + authentication: mockAuthentication, + viewerParam: mockViewerParam, + scene: "vod", + localVideoSearchPaths: [defaultDownloadRoot] + } +} + +function url(url: string): PLVUrlMediaResource { + return { + url: url + } +} \ No newline at end of file diff --git a/polyv/.gitignore b/polyv/.gitignore new file mode 100644 index 0000000..28ab5cf --- /dev/null +++ b/polyv/.gitignore @@ -0,0 +1,11 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test +BuildProfile.ets +oh-package-lock.json5 +.npmrc +package.json +package-lock.json \ No newline at end of file diff --git a/polyv/Index.ets b/polyv/Index.ets new file mode 100644 index 0000000..efbef6a --- /dev/null +++ b/polyv/Index.ets @@ -0,0 +1,64 @@ +export * from './src/main/ets/common/di/PLVMPCommonModule' +export * from './src/main/ets/common/modules/auxiliary/viewmodel/viewstate/PLVMPAuxiliaryInfoViewState' +export * from './src/main/ets/common/modules/auxiliary/viewmodel/viewstate/PLVMPAuxiliaryPlayViewState' +export * from './src/main/ets/common/modules/auxiliary/viewmodel/viewstate/PLVMPAuxiliaryVideoInfoViewState' +export * from './src/main/ets/common/modules/auxiliary/viewmodel/PLVMPAuxiliaryViewModel' +export * from './src/main/ets/common/modules/download/list/viewstate/PLVMPDownloadListViewState' +export * from './src/main/ets/common/modules/download/list/PLVMPDownloadListViewModel' +export * from './src/main/ets/common/modules/download/single/viewmodel/viewstate/PLVMPDownloadItemViewState' +export * from './src/main/ets/common/modules/download/single/viewmodel/PLVMPDownloadItemViewModel' +export * from './src/main/ets/common/modules/media/viewmodel/viewstate/PLVMPMediaInfoViewState' +export * from './src/main/ets/common/modules/media/viewmodel/viewstate/PLVMPMediaPlayViewState' +export * from './src/main/ets/common/modules/media/viewmodel/PLVMPMediaViewModel' +export * from './src/main/ets/common/modules/mediacontroller/viewmodel/viewstate/PLVMPMediaControllerViewState' +export * from './src/main/ets/common/modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +export * from './src/main/ets/common/modules/pagecontrol/viewmodel/PLVMPPageControlViewModel' +export * from './src/main/ets/common/modules/progressImage/viewmodel/PLVMPProgressImageViewModel' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerAudioModeCoverLayoutPort' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerAudioModeCoverLayoutLand' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerAutoContinueHintLayout' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerAuxiliaryCountDownTextView' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerBackImageView' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerBitRateSelectLayoutLand' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerBitRateTextView' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerBrightnessVolumeUpdateHintLayout' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerBufferingSpeedLayout' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerControllerGradientMaskLayout' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerGestureHandleLayout' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerHandleOnEnterBackgroundComponent' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerLockControllerImageView' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerLongPressSpeedHintLayout' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerMarqueeLayout' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerMoreActionImageView' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutAudioModeActionView' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutDownloadActionView' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutLand' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutPort' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutSubtitleActionView' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerMoreSubtitleSettingLayoutLand' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerMoreSubtitleSettingLayoutPort' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerNetworkPoorIndicateLayout' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerPlayButton' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerPlayCompleteAutoRestartComponent' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerPlayCompleteManualRestartOverlayLayout' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerPlayErrorOverlayLayout' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerProgressTextView' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerScreenshotImageView' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerSeekProgressPreviewLayout' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerSeekProgressPreviewTextView' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerSpeedSelectLayoutLand' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerSpeedTextView' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerSubtitleTextLayout' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerSwitchBitRateHintLayout' +export * from './src/main/ets/common/ui/component/PLVMediaPlayerTitleTextView' +export * from './src/main/ets/common/ui/enums/PLVMediaPlayerScenes' +export * from './src/main/ets/common/ui/ext/ImageKitExts' +export * from './src/main/ets/common/utils/arkts-no-everything' +export * from './src/main/ets/common/utils/PLVAbilityContexts' +export * from './src/main/ets/common/utils/PLVBackgroundTaskManager' +export * from './src/main/ets/common/utils/PLVBrightnessManager' +export * from './src/main/ets/common/utils/PLVComponentLifecycle' +export * from './src/main/ets/common/utils/PLVDisplayUtils' +export * from './src/main/ets/common/utils/PLVOrientationManager' +export * from './src/main/ets/common/utils/PLVOrientationManagerObserver' +export * from './src/main/ets/common/utils/PLVTimeUtils' diff --git a/polyv/build-profile.json5 b/polyv/build-profile.json5 new file mode 100644 index 0000000..d9ca574 --- /dev/null +++ b/polyv/build-profile.json5 @@ -0,0 +1,28 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./obfuscation-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + } + ] +} diff --git a/polyv/hvigorfile.ts b/polyv/hvigorfile.ts new file mode 100644 index 0000000..4218707 --- /dev/null +++ b/polyv/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/polyv/obfuscation-rules.txt b/polyv/obfuscation-rules.txt new file mode 100644 index 0000000..985b2ae --- /dev/null +++ b/polyv/obfuscation-rules.txt @@ -0,0 +1,18 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope \ No newline at end of file diff --git a/polyv/oh-package.json5 b/polyv/oh-package.json5 new file mode 100644 index 0000000..c44600c --- /dev/null +++ b/polyv/oh-package.json5 @@ -0,0 +1,12 @@ +{ + "name": "media-player-common", + "version": "2.5.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "MIT", + "dependencies": { + "@polyvharmony/media-player-sdk": "2.5.0", + "@polyvharmony/media-player-sdk-addon-cache-down": "2.5.0" + } +} diff --git a/polyv/src/main/ets/common/di/PLVMPCommonModule.ts b/polyv/src/main/ets/common/di/PLVMPCommonModule.ts new file mode 100644 index 0000000..6b45a85 --- /dev/null +++ b/polyv/src/main/ets/common/di/PLVMPCommonModule.ts @@ -0,0 +1,19 @@ +import {DependModule} from '@polyvharmony/media-player-sdk'; +import {mediaModule} from '../modules/media/di/PLVMPMediaModule'; +import {mediaControllerModule} from '../modules/mediacontroller/di/PLVMPMediaControlModule'; +import {progressImageModule} from "../modules/progressImage/di/PLVMPProgressImageModule"; +import {pageControlModule} from "../modules/pagecontrol/di/PLVMPPageControlModule"; +import {auxiliaryModule} from "../modules/auxiliary/di/PLVMPAuxiliaryModule"; +import {downloadItemModule} from "../modules/download/single/di/PLVMPDownloadItemModule"; + +export const commonItemModule = new DependModule() + +commonItemModule.include(mediaModule) +commonItemModule.include(mediaControllerModule) +commonItemModule.include(progressImageModule) +commonItemModule.include(auxiliaryModule) +commonItemModule.include(downloadItemModule) + +export const commonPageModule = new DependModule() + +commonPageModule.include(pageControlModule) diff --git a/polyv/src/main/ets/common/modules/auxiliary/di/PLVMPAuxiliaryModule.ts b/polyv/src/main/ets/common/modules/auxiliary/di/PLVMPAuxiliaryModule.ts new file mode 100644 index 0000000..9ba2371 --- /dev/null +++ b/polyv/src/main/ets/common/modules/auxiliary/di/PLVMPAuxiliaryModule.ts @@ -0,0 +1,31 @@ +import {DependModule} from '@polyvharmony/media-player-sdk'; +import {PLVMPAuxiliaryViewModel} from "../viewmodel/PLVMPAuxiliaryViewModel"; +import {PLVMPAuxiliaryRepo} from "../model/PLVMPAuxiliaryRepo"; +import {PLVMPMediaMediator} from "../../media/mediator/PLVMPMediaMediator"; +import {PLVMPAuxiliaryUseCases} from "../viewmodel/usecases/PLVMPAuxiliaryUseCases"; +import {AuxiliaryUpdateMediaStateUseCase} from "../viewmodel/usecases/AuxiliaryUpdateMediaStateUseCase"; +import {AuxiliaryBeforePlayListener} from "../viewmodel/usecases/AuxiliaryBeforePlayListener"; +import {PLVMPAuxiliaryMediator} from "../mediator/PLVMPAuxiliaryMediator"; + +export const auxiliaryModule = new DependModule() + +auxiliaryModule.provide(PLVMPAuxiliaryMediator, () => new PLVMPAuxiliaryMediator()) + +auxiliaryModule.provide(PLVMPAuxiliaryRepo, (scope) => new PLVMPAuxiliaryRepo( + scope.get(PLVMPAuxiliaryMediator), + scope.get(PLVMPMediaMediator) +)) + +auxiliaryModule.provide(AuxiliaryBeforePlayListener, () => new AuxiliaryBeforePlayListener()) +auxiliaryModule.provide(AuxiliaryUpdateMediaStateUseCase, (scope) => new AuxiliaryUpdateMediaStateUseCase( + scope.get(PLVMPAuxiliaryRepo) +)) +auxiliaryModule.provide(PLVMPAuxiliaryUseCases, (scope) => new PLVMPAuxiliaryUseCases( + scope.get(AuxiliaryBeforePlayListener), + scope.get(AuxiliaryUpdateMediaStateUseCase) +)) + +auxiliaryModule.provide(PLVMPAuxiliaryViewModel, (scope) => new PLVMPAuxiliaryViewModel( + scope.get(PLVMPAuxiliaryRepo), + scope.get(PLVMPAuxiliaryUseCases) +)) \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/auxiliary/mediator/PLVMPAuxiliaryMediator.ts b/polyv/src/main/ets/common/modules/auxiliary/mediator/PLVMPAuxiliaryMediator.ts new file mode 100644 index 0000000..43bd1e6 --- /dev/null +++ b/polyv/src/main/ets/common/modules/auxiliary/mediator/PLVMPAuxiliaryMediator.ts @@ -0,0 +1,16 @@ +import {LifecycleAwareDependComponent, MutableSource, MutableState} from "@polyvharmony/media-player-sdk"; +import {PLVMPAuxiliaryInfoViewState} from "../viewmodel/viewstate/PLVMPAuxiliaryInfoViewState"; +import {PLVMPAuxiliaryPlayViewState} from "../viewmodel/viewstate/PLVMPAuxiliaryPlayViewState"; +import {PLVMPAuxiliaryVideoInfoViewState} from "../viewmodel/viewstate/PLVMPAuxiliaryVideoInfoViewState"; + +export class PLVMPAuxiliaryMediator implements LifecycleAwareDependComponent { + + readonly auxiliaryInfoViewState = new MutableState(null); + readonly auxiliaryPlayViewState = new MutableState(new PLVMPAuxiliaryPlayViewState()); + readonly auxiliaryVideoInfoViewState = new MutableState(new PLVMPAuxiliaryVideoInfoViewState()); + + onDestroy() { + MutableSource.disposeAll(this) + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/auxiliary/model/PLVMPAuxiliaryRepo.ts b/polyv/src/main/ets/common/modules/auxiliary/model/PLVMPAuxiliaryRepo.ts new file mode 100644 index 0000000..1b5712f --- /dev/null +++ b/polyv/src/main/ets/common/modules/auxiliary/model/PLVMPAuxiliaryRepo.ts @@ -0,0 +1,41 @@ +import { + IPLVAuxiliaryMediaPlayer, + LifecycleAwareDependComponent, + PLVAuxiliaryMediaPlayer +} from '@polyvharmony/media-player-sdk'; +import {PLVMPMediaMediator} from "../../media/mediator/PLVMPMediaMediator"; +import {PLVMPAuxiliaryMediator} from "../mediator/PLVMPAuxiliaryMediator"; + +export class PLVMPAuxiliaryRepo implements LifecycleAwareDependComponent { + + readonly mediator: PLVMPAuxiliaryMediator + private readonly mediaMediator: PLVMPMediaMediator + + readonly auxiliaryMediaPlayer: IPLVAuxiliaryMediaPlayer = new PLVAuxiliaryMediaPlayer() + + constructor( + mediator: PLVMPAuxiliaryMediator, + mediaMediator: PLVMPMediaMediator + ) { + this.mediator = mediator + this.mediaMediator = mediaMediator + } + + setXComponent(component: any) { + this.auxiliaryMediaPlayer.setXComponent(component) + } + + bind() { + this.mediaMediator.bindAuxiliaryPlayer?.(this.auxiliaryMediaPlayer) + } + + unbind() { + this.mediaMediator.unbindAuxiliaryPlayer?.(this.auxiliaryMediaPlayer) + } + + onDestroy() { + this.unbind() + this.auxiliaryMediaPlayer.destroy() + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/auxiliary/viewmodel/PLVMPAuxiliaryViewModel.ts b/polyv/src/main/ets/common/modules/auxiliary/viewmodel/PLVMPAuxiliaryViewModel.ts new file mode 100644 index 0000000..0b0da78 --- /dev/null +++ b/polyv/src/main/ets/common/modules/auxiliary/viewmodel/PLVMPAuxiliaryViewModel.ts @@ -0,0 +1,43 @@ +import {PLVMPAuxiliaryRepo} from "../model/PLVMPAuxiliaryRepo"; +import {MutableState} from '@polyvharmony/media-player-sdk'; +import {PLVMPAuxiliaryInfoViewState} from "./viewstate/PLVMPAuxiliaryInfoViewState"; +import {PLVMPAuxiliaryUseCases} from "./usecases/PLVMPAuxiliaryUseCases"; +import {PLVMPAuxiliaryVideoInfoViewState} from "./viewstate/PLVMPAuxiliaryVideoInfoViewState"; +import {PLVMPAuxiliaryPlayViewState} from "./viewstate/PLVMPAuxiliaryPlayViewState"; + +export class PLVMPAuxiliaryViewModel { + + private readonly repo: PLVMPAuxiliaryRepo + private readonly useCases: PLVMPAuxiliaryUseCases + + readonly auxiliaryInfoViewState: MutableState + readonly auxiliaryPlayViewState: MutableState + readonly auxiliaryVideoInfoViewState: MutableState + + constructor( + repo: PLVMPAuxiliaryRepo, + useCases: PLVMPAuxiliaryUseCases + ) { + this.repo = repo + this.useCases = useCases + + this.auxiliaryInfoViewState = this.repo.mediator.auxiliaryInfoViewState + this.auxiliaryPlayViewState = this.repo.mediator.auxiliaryPlayViewState + this.auxiliaryVideoInfoViewState = this.repo.mediator.auxiliaryVideoInfoViewState + + this.repo.auxiliaryMediaPlayer.getAuxiliaryListenerRegistry().onBeforeAdvertListener = this.useCases.beforePlayListener + } + + setXComponent(component: any) { + this.repo.setXComponent(component) + } + + bind() { + this.repo.bind() + } + + unbind() { + this.repo.unbind() + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/auxiliary/viewmodel/usecases/AuxiliaryBeforePlayListener.ts b/polyv/src/main/ets/common/modules/auxiliary/viewmodel/usecases/AuxiliaryBeforePlayListener.ts new file mode 100644 index 0000000..d35afa8 --- /dev/null +++ b/polyv/src/main/ets/common/modules/auxiliary/viewmodel/usecases/AuxiliaryBeforePlayListener.ts @@ -0,0 +1,26 @@ +import { + IPLVAuxiliaryOnBeforeAdvertListener, + PLVAdvertMediaDataSource, + PLVMediaPlayStage +} from '@polyvharmony/media-player-sdk'; + +export class AuxiliaryBeforePlayListener implements IPLVAuxiliaryOnBeforeAdvertListener { + + private readonly playedAdvertIds = new Set() + + onBeforeAdvert(dataSource: PLVAdvertMediaDataSource, stage: PLVMediaPlayStage): boolean { + let playAdvert = true + + // 已播放过的片头片尾广告,不再播放 + if (stage === PLVMediaPlayStage.HEAD_ADVERT || stage === PLVMediaPlayStage.TAIL_ADVERT) { + if (this.playedAdvertIds.has(dataSource.advertId)) { + playAdvert = false + } else { + this.playedAdvertIds.add(dataSource.advertId) + } + } + + return playAdvert + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/auxiliary/viewmodel/usecases/AuxiliaryUpdateMediaStateUseCase.ts b/polyv/src/main/ets/common/modules/auxiliary/viewmodel/usecases/AuxiliaryUpdateMediaStateUseCase.ts new file mode 100644 index 0000000..97334af --- /dev/null +++ b/polyv/src/main/ets/common/modules/auxiliary/viewmodel/usecases/AuxiliaryUpdateMediaStateUseCase.ts @@ -0,0 +1,48 @@ +import {PLVMPAuxiliaryRepo} from "../../model/PLVMPAuxiliaryRepo"; +import {LifecycleAwareDependComponent, MutableObserver} from '@polyvharmony/media-player-sdk'; + +export class AuxiliaryUpdateMediaStateUseCase implements LifecycleAwareDependComponent { + + private readonly repo: PLVMPAuxiliaryRepo + + private observers: MutableObserver[] = [] + + constructor( + repo: PLVMPAuxiliaryRepo + ) { + this.repo = repo + + this.observeState() + } + + private observeState() { + this.repo.auxiliaryMediaPlayer.getAuxiliaryListenerRegistry().onShowAdvertEvent.observe((event) => { + this.repo.mediator.auxiliaryInfoViewState.value = { + stage: event.stage, + ...event.dataSource + } + }).pushTo(this.observers) + + this.repo.auxiliaryMediaPlayer.getAuxiliaryListenerRegistry().onFinishAdvertEvent.observe(() => { + this.repo.mediator.auxiliaryInfoViewState.value = null + }).pushTo(this.observers) + + this.repo.auxiliaryMediaPlayer.getStateListenerRegistry().videoSize.observe((videoSize) => { + this.repo.mediator.auxiliaryVideoInfoViewState.mutate({ + videoSize: videoSize + }) + }).pushTo(this.observers) + + this.repo.auxiliaryMediaPlayer.getAuxiliaryListenerRegistry().onTimeLeftCountDownEvent.observe((event) => { + this.repo.mediator.auxiliaryPlayViewState.mutate({ + timeLeftInSeconds: event.timeLeftInSeconds + }) + }).pushTo(this.observers) + } + + onDestroy() { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/auxiliary/viewmodel/usecases/PLVMPAuxiliaryUseCases.ts b/polyv/src/main/ets/common/modules/auxiliary/viewmodel/usecases/PLVMPAuxiliaryUseCases.ts new file mode 100644 index 0000000..9a47ed0 --- /dev/null +++ b/polyv/src/main/ets/common/modules/auxiliary/viewmodel/usecases/PLVMPAuxiliaryUseCases.ts @@ -0,0 +1,10 @@ +import {AuxiliaryUpdateMediaStateUseCase} from "./AuxiliaryUpdateMediaStateUseCase"; +import {AuxiliaryBeforePlayListener} from "./AuxiliaryBeforePlayListener"; + +export class PLVMPAuxiliaryUseCases { + constructor( + readonly beforePlayListener: AuxiliaryBeforePlayListener, + readonly updateMediaStateUseCase: AuxiliaryUpdateMediaStateUseCase + ) { + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/auxiliary/viewmodel/viewstate/PLVMPAuxiliaryInfoViewState.ts b/polyv/src/main/ets/common/modules/auxiliary/viewmodel/viewstate/PLVMPAuxiliaryInfoViewState.ts new file mode 100644 index 0000000..520f701 --- /dev/null +++ b/polyv/src/main/ets/common/modules/auxiliary/viewmodel/viewstate/PLVMPAuxiliaryInfoViewState.ts @@ -0,0 +1,13 @@ +import {Duration, PLVMediaPlayStage, seconds} from '@polyvharmony/media-player-sdk'; + +export class PLVMPAuxiliaryInfoViewState { + + url: string = "" + isImage: boolean = false + clickNavigationUrl: string | null = null + showDuration: Duration = seconds(0) + canSkip: boolean = false + beforeSkipDuration: Duration | null = null + stage: PLVMediaPlayStage = PLVMediaPlayStage.HEAD_ADVERT + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/auxiliary/viewmodel/viewstate/PLVMPAuxiliaryPlayViewState.ts b/polyv/src/main/ets/common/modules/auxiliary/viewmodel/viewstate/PLVMPAuxiliaryPlayViewState.ts new file mode 100644 index 0000000..72ac8de --- /dev/null +++ b/polyv/src/main/ets/common/modules/auxiliary/viewmodel/viewstate/PLVMPAuxiliaryPlayViewState.ts @@ -0,0 +1,5 @@ +export class PLVMPAuxiliaryPlayViewState { + + timeLeftInSeconds: number = 0 + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/auxiliary/viewmodel/viewstate/PLVMPAuxiliaryVideoInfoViewState.ts b/polyv/src/main/ets/common/modules/auxiliary/viewmodel/viewstate/PLVMPAuxiliaryVideoInfoViewState.ts new file mode 100644 index 0000000..110318b --- /dev/null +++ b/polyv/src/main/ets/common/modules/auxiliary/viewmodel/viewstate/PLVMPAuxiliaryVideoInfoViewState.ts @@ -0,0 +1,7 @@ +import {Rect} from '@polyvharmony/media-player-sdk'; + +export class PLVMPAuxiliaryVideoInfoViewState { + + videoSize: Rect = new Rect() + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/download/list/PLVMPDownloadListViewModel.ts b/polyv/src/main/ets/common/modules/download/list/PLVMPDownloadListViewModel.ts new file mode 100644 index 0000000..a11aaba --- /dev/null +++ b/polyv/src/main/ets/common/modules/download/list/PLVMPDownloadListViewModel.ts @@ -0,0 +1,28 @@ +import {createDependScope} from "@polyvharmony/media-player-sdk"; +import {internalDownloadListModule} from "./di/PLVMPDownloadListModule"; +import {PLVMPDownloadListRepo} from "./model/PLVMPDownloadListRepo"; +import {PLVMPDownloadListUseCases} from "./usecase/PLVMPDownloadListUseCases"; + +export class PLVMPDownloadListViewModel { + + // + + private static readonly instance = new PLVMPDownloadListViewModel(); + + private constructor() { + } + + static getInstance() { + return PLVMPDownloadListViewModel.instance; + } + + // + + private readonly dependScope = createDependScope(internalDownloadListModule) + private readonly repo = this.dependScope.get(PLVMPDownloadListRepo) + private readonly useCases = this.dependScope.get(PLVMPDownloadListUseCases) + + readonly downloadingList = this.repo.mediator.downloadingList + readonly downloadedList = this.repo.mediator.downloadedList + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/download/list/di/PLVMPDownloadListModule.ts b/polyv/src/main/ets/common/modules/download/list/di/PLVMPDownloadListModule.ts new file mode 100644 index 0000000..089fe39 --- /dev/null +++ b/polyv/src/main/ets/common/modules/download/list/di/PLVMPDownloadListModule.ts @@ -0,0 +1,23 @@ +import {DependModule} from "@polyvharmony/media-player-sdk"; +import {PLVMPDownloadListMediator} from "../mediator/PLVMPDownloadListMediator"; +import {PLVMPDownloadListRepo} from "../model/PLVMPDownloadListRepo"; +import {DownloadRequestBackgroundTaskUseCase} from '../usecase/DownloadRequestBackgroundTaskUseCase'; +import {DownloadUpdateListUseCase} from "../usecase/DownloadUpdateListUseCase"; +import {PLVMPDownloadListUseCases} from "../usecase/PLVMPDownloadListUseCases"; + +export const internalDownloadListModule = new DependModule() + +internalDownloadListModule.provide(PLVMPDownloadListMediator, () => new PLVMPDownloadListMediator()); + +internalDownloadListModule.provide(PLVMPDownloadListRepo, (scope) => new PLVMPDownloadListRepo( + scope.get(PLVMPDownloadListMediator) +)) + +internalDownloadListModule.provide(DownloadUpdateListUseCase, (scope) => new DownloadUpdateListUseCase( + scope.get(PLVMPDownloadListRepo) +)) +internalDownloadListModule.provide(DownloadRequestBackgroundTaskUseCase, () => new DownloadRequestBackgroundTaskUseCase()) +internalDownloadListModule.provide(PLVMPDownloadListUseCases, (scope) => new PLVMPDownloadListUseCases( + scope.get(DownloadUpdateListUseCase), + scope.get(DownloadRequestBackgroundTaskUseCase) +)) \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/download/list/mediator/PLVMPDownloadListMediator.ts b/polyv/src/main/ets/common/modules/download/list/mediator/PLVMPDownloadListMediator.ts new file mode 100644 index 0000000..ee921d2 --- /dev/null +++ b/polyv/src/main/ets/common/modules/download/list/mediator/PLVMPDownloadListMediator.ts @@ -0,0 +1,7 @@ +import {MutableState} from "@polyvharmony/media-player-sdk"; +import {PLVMPDownloadListViewState} from "../viewstate/PLVMPDownloadListViewState"; + +export class PLVMPDownloadListMediator { + readonly downloadedList = new MutableState() + readonly downloadingList = new MutableState() +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/download/list/model/PLVMPDownloadListRepo.ts b/polyv/src/main/ets/common/modules/download/list/model/PLVMPDownloadListRepo.ts new file mode 100644 index 0000000..2ecd33a --- /dev/null +++ b/polyv/src/main/ets/common/modules/download/list/model/PLVMPDownloadListRepo.ts @@ -0,0 +1,8 @@ +import {PLVMPDownloadListMediator} from "../mediator/PLVMPDownloadListMediator"; + +export class PLVMPDownloadListRepo { + constructor( + readonly mediator: PLVMPDownloadListMediator + ) { + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/download/list/usecase/DownloadRequestBackgroundTaskUseCase.ts b/polyv/src/main/ets/common/modules/download/list/usecase/DownloadRequestBackgroundTaskUseCase.ts new file mode 100644 index 0000000..7ea6096 --- /dev/null +++ b/polyv/src/main/ets/common/modules/download/list/usecase/DownloadRequestBackgroundTaskUseCase.ts @@ -0,0 +1,59 @@ +import {LifecycleAwareDependComponent, MutableObserver} from '@polyvharmony/media-player-sdk'; +import { + PLVMediaDownloader, + PLVMediaDownloaderManager, + PLVMediaDownloadStatusDownloading, + PLVMediaDownloadStatusWaiting +} from '@polyvharmony/media-player-sdk-addon-cache-down'; +import {PLVBackgroundTaskManager} from '../../../../utils/PLVBackgroundTaskManager'; +import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager'; + +export class DownloadRequestBackgroundTaskUseCase implements LifecycleAwareDependComponent { + + private isAnyDownloading: boolean = false + private observers: MutableObserver[] = [] + + constructor() { + PLVMediaDownloaderManager.getInstance().downloaderList.observe(() => this.onDownloaderUpdated()) + } + + private onDownloaderUpdated() { + const list = PLVMediaDownloaderManager.getInstance().downloaderList.value ?? [] + + MutableObserver.disposeAll(this.observers) + this.observers = [] + + list.forEach(downloader => { + const downloading = this.isDownloading(downloader) + downloader.listenerRegistry.status.observe(() => { + if (downloading != this.isDownloading(downloader)) { + this.onDownloaderUpdated() + } + }).pushTo(this.observers) + }) + + const newAnyDownloading = list.some(downloader => this.isDownloading(downloader)) + + if (newAnyDownloading !== this.isAnyDownloading) { + this.isAnyDownloading = newAnyDownloading + if (this.isAnyDownloading) { + PLVBackgroundTaskManager.getInstance().pushBackgroundTask(backgroundTaskManager.BackgroundMode.DATA_TRANSFER) + } else { + PLVBackgroundTaskManager.getInstance().removeBackgroundTask(backgroundTaskManager.BackgroundMode.DATA_TRANSFER) + } + } + + } + + onDestroy(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } + + private isDownloading(downloader: PLVMediaDownloader): boolean { + const status = downloader.listenerRegistry.status.value + return status instanceof PLVMediaDownloadStatusWaiting + || status instanceof PLVMediaDownloadStatusDownloading + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/download/list/usecase/DownloadUpdateListUseCase.ts b/polyv/src/main/ets/common/modules/download/list/usecase/DownloadUpdateListUseCase.ts new file mode 100644 index 0000000..710e159 --- /dev/null +++ b/polyv/src/main/ets/common/modules/download/list/usecase/DownloadUpdateListUseCase.ts @@ -0,0 +1,94 @@ +import {PLVMPDownloadListRepo} from "../model/PLVMPDownloadListRepo"; +import { + PLVMediaDownloader, + PLVMediaDownloaderManager, + PLVMediaDownloadStatusCompleted, + PLVMediaDownloadStatusDownloading, + PLVMediaDownloadStatusError, + PLVMediaDownloadStatusNotStarted, + PLVMediaDownloadStatusPaused, + PLVMediaDownloadStatusWaiting +} from "@polyvharmony/media-player-sdk-addon-cache-down"; +import { + DerivedState, + LifecycleAwareDependComponent, + MutableObserver, + PLVMediaBitRate, + seconds, + State +} from "@polyvharmony/media-player-sdk"; +import {PLVMPDownloadListItemViewState, PLVMPDownloadListViewState} from "../viewstate/PLVMPDownloadListViewState"; + +export class DownloadUpdateListUseCase implements LifecycleAwareDependComponent { + + private observersAnyDownloaderStatusChanged: MutableObserver[] = [] + + constructor( + private readonly repo: PLVMPDownloadListRepo + ) { + PLVMediaDownloaderManager.getInstance().downloaderList.observe(() => this.updateDownloadList()) + } + + private updateDownloadList() { + const list = PLVMediaDownloaderManager.getInstance().downloaderList.value ?? [] + + MutableObserver.disposeAll(this.observersAnyDownloaderStatusChanged) + this.observersAnyDownloaderStatusChanged = [] + + list.forEach(downloader => { + const downloading = this.isDownloading(downloader) + const downloadCompleted = this.isDownloadCompleted(downloader) + downloader.listenerRegistry.status.observe(() => { + if (downloading != this.isDownloading(downloader) || downloadCompleted != this.isDownloadCompleted(downloader)) { + this.updateDownloadList() + } + }).pushTo(this.observersAnyDownloaderStatusChanged) + }) + + const downloading = list + .filter(it => this.isDownloading(it)) + .map((downloader) => { + return this.convertDownloadItemState(downloader) + }) + + const downloaded = list + .filter(it => this.isDownloadCompleted(it)) + .map(it => this.convertDownloadItemState(it)) + + this.repo.mediator.downloadingList.value = new PLVMPDownloadListViewState(downloading) + this.repo.mediator.downloadedList.value = new PLVMPDownloadListViewState(downloaded) + } + + onDestroy(): void { + MutableObserver.disposeAll(this.observersAnyDownloaderStatusChanged) + this.observersAnyDownloaderStatusChanged = [] + } + + private isDownloading(downloader: PLVMediaDownloader): boolean { + const status = downloader.listenerRegistry.status.value + return status instanceof PLVMediaDownloadStatusPaused + || status instanceof PLVMediaDownloadStatusWaiting + || status instanceof PLVMediaDownloadStatusDownloading + || status instanceof PLVMediaDownloadStatusError + } + + private isDownloadCompleted(downloader: PLVMediaDownloader): boolean { + const status = downloader.listenerRegistry.status.value + return status instanceof PLVMediaDownloadStatusCompleted + } + + private convertDownloadItemState(downloader: PLVMediaDownloader): State { + return new DerivedState(() => new PLVMPDownloadListItemViewState( + downloader, + downloader.listenerRegistry.vodVideoJson.value?.title ?? "", + downloader.listenerRegistry.coverImage.value ?? null, + downloader.listenerRegistry.downloadBitRate.value ?? PLVMediaBitRate.BITRATE_UNKNOWN, + downloader.listenerRegistry.duration.value ?? seconds(0), + downloader.listenerRegistry.progress.value ?? 0, + downloader.listenerRegistry.fileSize.value ?? 0, + downloader.listenerRegistry.status.value ?? PLVMediaDownloadStatusNotStarted.instance, + downloader.listenerRegistry.downloadBytesPerSecond.value ?? 0 + )) + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/download/list/usecase/PLVMPDownloadListUseCases.ts b/polyv/src/main/ets/common/modules/download/list/usecase/PLVMPDownloadListUseCases.ts new file mode 100644 index 0000000..fffe859 --- /dev/null +++ b/polyv/src/main/ets/common/modules/download/list/usecase/PLVMPDownloadListUseCases.ts @@ -0,0 +1,10 @@ +import {DownloadRequestBackgroundTaskUseCase} from './DownloadRequestBackgroundTaskUseCase'; +import {DownloadUpdateListUseCase} from "./DownloadUpdateListUseCase"; + +export class PLVMPDownloadListUseCases { + constructor( + readonly downloadUpdateListUseCase: DownloadUpdateListUseCase, + readonly downloadRequestBackgroundTaskUseCase: DownloadRequestBackgroundTaskUseCase + ) { + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/download/list/viewstate/PLVMPDownloadListViewState.ts b/polyv/src/main/ets/common/modules/download/list/viewstate/PLVMPDownloadListViewState.ts new file mode 100644 index 0000000..6837d78 --- /dev/null +++ b/polyv/src/main/ets/common/modules/download/list/viewstate/PLVMPDownloadListViewState.ts @@ -0,0 +1,41 @@ +import {Duration, PLVMediaBitRate, State} from "@polyvharmony/media-player-sdk"; +import { + PLVMediaDownloader, + PLVMediaDownloaderManager, + PLVMediaDownloadStatus +} from "@polyvharmony/media-player-sdk-addon-cache-down"; + +export class PLVMPDownloadListViewState { + constructor( + readonly list: State[] = [] + ) { + } +} + +export class PLVMPDownloadListItemViewState { + constructor( + readonly downloader: PLVMediaDownloader, + readonly title: string, + readonly coverImage: string | null, + readonly bitRate: PLVMediaBitRate, + readonly duration: Duration, + readonly progress: number, + readonly fileSize: number, + readonly downloadStatus: PLVMediaDownloadStatus, + readonly downloadBytesPerSecond: number + ) { + } + + startDownload() { + PLVMediaDownloaderManager.getInstance().startDownloader(this.downloader) + } + + pauseDownload() { + PLVMediaDownloaderManager.getInstance().pauseDownloader(this.downloader) + } + + deleteDownload() { + PLVMediaDownloaderManager.getInstance().deleteDownloadContent(this.downloader) + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/download/single/di/PLVMPDownloadItemModule.ts b/polyv/src/main/ets/common/modules/download/single/di/PLVMPDownloadItemModule.ts new file mode 100644 index 0000000..49899bd --- /dev/null +++ b/polyv/src/main/ets/common/modules/download/single/di/PLVMPDownloadItemModule.ts @@ -0,0 +1,28 @@ +import {DependModule} from "@polyvharmony/media-player-sdk"; +import {PLVMPDownloadItemMediator} from "../mediator/PLVMPDownloadItemMediator"; +import {PLVMPDownloadItemRepo} from "../model/PLVMPDownloadItemRepo"; +import {PLVMPMediaMediator} from "../../../media/mediator/PLVMPMediaMediator"; +import {DownloadItemUpdateStateUseCase} from "../viewmodel/usecase/DownloadItemUpdateStateUseCase"; +import {PLVMPDownloadItemUseCases} from "../viewmodel/usecase/PLVMPDownloadItemUseCases"; +import {PLVMPDownloadItemViewModel} from "../viewmodel/PLVMPDownloadItemViewModel"; + +export const downloadItemModule = new DependModule() + +downloadItemModule.provide(PLVMPDownloadItemMediator, () => new PLVMPDownloadItemMediator()); + +downloadItemModule.provide(PLVMPDownloadItemRepo, (scope) => new PLVMPDownloadItemRepo( + scope.get(PLVMPDownloadItemMediator), + scope.get(PLVMPMediaMediator) +)) + +downloadItemModule.provide(DownloadItemUpdateStateUseCase, (scope) => new DownloadItemUpdateStateUseCase( + scope.get(PLVMPDownloadItemRepo) +)) +downloadItemModule.provide(PLVMPDownloadItemUseCases, (scope) => new PLVMPDownloadItemUseCases( + scope.get(DownloadItemUpdateStateUseCase) +)) + +downloadItemModule.provide(PLVMPDownloadItemViewModel, (scope) => new PLVMPDownloadItemViewModel( + scope.get(PLVMPDownloadItemRepo), + scope.get(PLVMPDownloadItemUseCases) +)) \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/download/single/mediator/PLVMPDownloadItemMediator.ts b/polyv/src/main/ets/common/modules/download/single/mediator/PLVMPDownloadItemMediator.ts new file mode 100644 index 0000000..9eb36d9 --- /dev/null +++ b/polyv/src/main/ets/common/modules/download/single/mediator/PLVMPDownloadItemMediator.ts @@ -0,0 +1,12 @@ +import {LifecycleAwareDependComponent, MutableSource, MutableState} from "@polyvharmony/media-player-sdk"; +import {PLVMPDownloadItemViewState} from "../viewmodel/viewstate/PLVMPDownloadItemViewState"; + +export class PLVMPDownloadItemMediator implements LifecycleAwareDependComponent { + + readonly downloadItem = new MutableState(null) + + onDestroy() { + MutableSource.disposeAll(this) + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/download/single/model/PLVMPDownloadItemRepo.ts b/polyv/src/main/ets/common/modules/download/single/model/PLVMPDownloadItemRepo.ts new file mode 100644 index 0000000..357cef4 --- /dev/null +++ b/polyv/src/main/ets/common/modules/download/single/model/PLVMPDownloadItemRepo.ts @@ -0,0 +1,10 @@ +import {PLVMPDownloadItemMediator} from "../mediator/PLVMPDownloadItemMediator"; +import {PLVMPMediaMediator} from "../../../media/mediator/PLVMPMediaMediator"; + +export class PLVMPDownloadItemRepo { + constructor( + readonly mediator: PLVMPDownloadItemMediator, + readonly mediaMediator: PLVMPMediaMediator + ) { + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/download/single/viewmodel/PLVMPDownloadItemViewModel.ts b/polyv/src/main/ets/common/modules/download/single/viewmodel/PLVMPDownloadItemViewModel.ts new file mode 100644 index 0000000..79c5912 --- /dev/null +++ b/polyv/src/main/ets/common/modules/download/single/viewmodel/PLVMPDownloadItemViewModel.ts @@ -0,0 +1,39 @@ +import {PLVMPDownloadItemRepo} from "../model/PLVMPDownloadItemRepo"; +import {PLVMPDownloadItemUseCases} from "./usecase/PLVMPDownloadItemUseCases"; +import {PLVMediaDownloaderManager} from "@polyvharmony/media-player-sdk-addon-cache-down"; +import {PLVMPDownloadListViewModel} from '../../list/PLVMPDownloadListViewModel'; + +export class PLVMPDownloadItemViewModel { + + constructor( + readonly repo: PLVMPDownloadItemRepo, + readonly useCases: PLVMPDownloadItemUseCases + ) { + } + + readonly downloadItem = this.repo.mediator.downloadItem + + startDownload() { + // 加载 DownloadRequestBackgroundTask 确保后台任务 + PLVMPDownloadListViewModel.getInstance() + + const downloader = this.repo.mediator.downloadItem.value?.downloader + if (downloader === undefined) { + return + } + PLVMediaDownloaderManager.getInstance().startDownloader(downloader) + } + + pauseDownload() { + const downloader = this.repo.mediator.downloadItem.value?.downloader + if (downloader === undefined) { + return + } + PLVMediaDownloaderManager.getInstance().pauseDownloader(downloader) + } + + setDownloadActionVisible(isVisible: boolean) { + this.useCases.downloadItemUpdateStateUseCase.setDownloadActionVisible(isVisible) + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/download/single/viewmodel/usecase/DownloadItemUpdateStateUseCase.ts b/polyv/src/main/ets/common/modules/download/single/viewmodel/usecase/DownloadItemUpdateStateUseCase.ts new file mode 100644 index 0000000..7b87727 --- /dev/null +++ b/polyv/src/main/ets/common/modules/download/single/viewmodel/usecase/DownloadItemUpdateStateUseCase.ts @@ -0,0 +1,59 @@ +import {PLVMPDownloadItemRepo} from "../../model/PLVMPDownloadItemRepo"; +import { + DerivedState, + LifecycleAwareDependComponent, + MutableObserver, + MutableState, + PLVMediaBitRate, + runCatching +} from "@polyvharmony/media-player-sdk"; +import {PLVMPDownloadItemViewState} from "../viewstate/PLVMPDownloadItemViewState"; +import { + PLVMediaDownloaderManager, + PLVMediaDownloadStatusNotStarted +} from "@polyvharmony/media-player-sdk-addon-cache-down"; + +export class DownloadItemUpdateStateUseCase implements LifecycleAwareDependComponent { + + constructor( + readonly repo: PLVMPDownloadItemRepo + ) { + this.init() + } + + private readonly downloadActionVisibleState = new MutableState(true) + private observers: MutableObserver[] = [] + + private init() { + new DerivedState(() => { + const mediaResource = this.repo.mediaMediator.mediaResource.value + const bitRate = this.repo.mediaMediator.mediaInfoViewState.value?.bitRate ?? PLVMediaBitRate.BITRATE_AUTO + if (mediaResource === undefined) { + return null + } + const downloaderResult = runCatching(() => PLVMediaDownloaderManager.getInstance().getDownloader(mediaResource, bitRate)) + if (downloaderResult.success === false) { + return null + } + const downloader = downloaderResult.data + return new PLVMPDownloadItemViewState( + downloader, + downloader.listenerRegistry.progress.value ?? 0, + downloader.listenerRegistry.fileSize.value ?? 0, + downloader.listenerRegistry.status.value ?? PLVMediaDownloadStatusNotStarted.instance, + this.downloadActionVisibleState.value ?? true + ) + }).relayTo(this.repo.mediator.downloadItem) + .pushTo(this.observers) + } + + setDownloadActionVisible(isVisible: boolean) { + this.downloadActionVisibleState.setValue(isVisible) + } + + onDestroy() { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/download/single/viewmodel/usecase/PLVMPDownloadItemUseCases.ts b/polyv/src/main/ets/common/modules/download/single/viewmodel/usecase/PLVMPDownloadItemUseCases.ts new file mode 100644 index 0000000..a94c153 --- /dev/null +++ b/polyv/src/main/ets/common/modules/download/single/viewmodel/usecase/PLVMPDownloadItemUseCases.ts @@ -0,0 +1,8 @@ +import {DownloadItemUpdateStateUseCase} from "./DownloadItemUpdateStateUseCase"; + +export class PLVMPDownloadItemUseCases { + constructor( + readonly downloadItemUpdateStateUseCase: DownloadItemUpdateStateUseCase + ) { + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/download/single/viewmodel/viewstate/PLVMPDownloadItemViewState.ts b/polyv/src/main/ets/common/modules/download/single/viewmodel/viewstate/PLVMPDownloadItemViewState.ts new file mode 100644 index 0000000..b16f50d --- /dev/null +++ b/polyv/src/main/ets/common/modules/download/single/viewmodel/viewstate/PLVMPDownloadItemViewState.ts @@ -0,0 +1,12 @@ +import {PLVMediaDownloader, PLVMediaDownloadStatus} from "@polyvharmony/media-player-sdk-addon-cache-down"; + +export class PLVMPDownloadItemViewState { + constructor( + readonly downloader: PLVMediaDownloader, + readonly progress: number, + readonly fileSize: number, + readonly status: PLVMediaDownloadStatus, + readonly isVisible: boolean + ) { + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/media/di/PLVMPMediaModule.ts b/polyv/src/main/ets/common/modules/media/di/PLVMPMediaModule.ts new file mode 100644 index 0000000..6c6ba0f --- /dev/null +++ b/polyv/src/main/ets/common/modules/media/di/PLVMPMediaModule.ts @@ -0,0 +1,45 @@ +import {DependModule, runCatching} from '@polyvharmony/media-player-sdk'; +import {PLVMPMediaRepo} from '../model/PLVMPMediaRepo'; +import {PLVMPMediaViewModel} from '../viewmodel/PLVMPMediaViewModel'; +import {PLVMPMediaUseCases} from '../viewmodel/usecase/PLVMPMediaUseCases'; +import {UpdateMediaStateUseCase} from '../viewmodel/usecase/UpdateMediaStateUseCase'; +import {PLVMPMediaMediator} from '../mediator/PLVMPMediaMediator'; +import {ObserveNetworkPoorUseCase} from "../viewmodel/usecase/ObserveNetworkPoorUseCase"; +import {UpdateBufferingSpeedUseCase} from "../viewmodel/usecase/UpdateBufferingSpeedUseCase"; +import {HandleAudioFocusUseCase} from "../viewmodel/usecase/HandleAudioFocusUseCase"; + +export const mediaModule = new DependModule() + +mediaModule.provide(PLVMPMediaMediator, () => new PLVMPMediaMediator()) + +mediaModule.provide(PLVMPMediaRepo, (scope) => new PLVMPMediaRepo( + scope.get(PLVMPMediaMediator) +)) + +mediaModule.provide(HandleAudioFocusUseCase, (scope) => new HandleAudioFocusUseCase( + scope.get(PLVMPMediaRepo) +)) +mediaModule.provide(UpdateBufferingSpeedUseCase, (scope) => new UpdateBufferingSpeedUseCase( + scope.get(PLVMPMediaRepo) +)) +mediaModule.provide(UpdateMediaStateUseCase, (scope) => new UpdateMediaStateUseCase( + scope.get(PLVMPMediaRepo) +)) +mediaModule.provide(ObserveNetworkPoorUseCase, (scope) => new ObserveNetworkPoorUseCase( + scope.get(PLVMPMediaRepo) +)) +mediaModule.provide(PLVMPMediaUseCases, (scope) => new PLVMPMediaUseCases( + scope.get(HandleAudioFocusUseCase), + scope.get(UpdateMediaStateUseCase), + scope.get(ObserveNetworkPoorUseCase), + scope.get(UpdateBufferingSpeedUseCase) +)) + +mediaModule.provide(PLVMPMediaViewModel, (scope) => new PLVMPMediaViewModel( + scope.get(PLVMPMediaRepo), + scope.get(PLVMPMediaUseCases) +)) + +mediaModule.afterCreate(PLVMPMediaMediator, (scope) => { + runCatching(() => scope.get(PLVMPMediaViewModel)) +}) \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/media/mediator/PLVMPMediaMediator.ts b/polyv/src/main/ets/common/modules/media/mediator/PLVMPMediaMediator.ts new file mode 100644 index 0000000..67955b3 --- /dev/null +++ b/polyv/src/main/ets/common/modules/media/mediator/PLVMPMediaMediator.ts @@ -0,0 +1,49 @@ +import { + IPLVAuxiliaryMediaPlayer, + LifecycleAwareDependComponent, + MutableEvent, + MutableSource, + MutableState, + PLVMediaBitRate, + PLVMediaPlayerAutoContinueEvent, + PLVMediaPlayerBusinessErrorEnum, + PLVMediaPlayerOnCompletedEvent, + PLVMediaPlayerOnInfoEvent, + PLVMediaPlayerOnPreparedEvent, + PLVMediaPlayerPlayingState, + PLVMediaPlayerState, + PLVMediaResource +} from '@polyvharmony/media-player-sdk' +import {PLVMPMediaInfoViewState} from "../viewmodel/viewstate/PLVMPMediaInfoViewState"; +import {PLVMPMediaPlayViewState} from "../viewmodel/viewstate/PLVMPMediaPlayViewState"; + +export class PLVMPMediaMediator implements LifecycleAwareDependComponent { + + readonly mediaResource = new MutableState() + readonly mediaPlayViewState = new MutableState(new PLVMPMediaPlayViewState()); + readonly mediaInfoViewState = new MutableState(new PLVMPMediaInfoViewState()); + readonly networkPoorEvent = new MutableEvent(); + readonly onChangeBitRateEvent = new MutableEvent(); + readonly onPreparedEvent = new MutableEvent(); + readonly onAutoContinueEvent = new MutableEvent(); + readonly onInfoEvent = new MutableEvent(); + readonly onCompleteEvent = new MutableEvent(); + readonly playingState = new MutableState(PLVMediaPlayerPlayingState.PAUSED); + readonly playerState = new MutableState(PLVMediaPlayerState.STATE_IDLE); + readonly bufferingSpeed = new MutableState(0) + readonly businessErrorState = new MutableState(null) + + seekTo?: (progress: number) => void + getSpeed?: () => number + setSpeed?: (speed: number) => void + isPlaying?: () => boolean + getVolume?: () => number + setVolume?: (volume: number) => void + bindAuxiliaryPlayer?: (auxiliaryPlayer: IPLVAuxiliaryMediaPlayer) => void + unbindAuxiliaryPlayer?: (auxiliaryPlayer: IPLVAuxiliaryMediaPlayer) => void + + onDestroy() { + MutableSource.disposeAll(this) + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/media/model/PLVMPMediaRepo.ts b/polyv/src/main/ets/common/modules/media/model/PLVMPMediaRepo.ts new file mode 100644 index 0000000..88784a5 --- /dev/null +++ b/polyv/src/main/ets/common/modules/media/model/PLVMPMediaRepo.ts @@ -0,0 +1,103 @@ +import { + LifecycleAwareDependComponent, + MutableObserver, + PLVMediaBitRate, + PLVMediaOutputMode, + PLVMediaPlayer, + PLVMediaPlayerOption, + PLVMediaPlayerPlayingState, + PLVMediaResource, + PLVMediaSubtitle +} from '@polyvharmony/media-player-sdk'; +import {PLVMPMediaMediator} from '../mediator/PLVMPMediaMediator'; + +export class PLVMPMediaRepo implements LifecycleAwareDependComponent { + + readonly mediator: PLVMPMediaMediator + readonly player: PLVMediaPlayer = new PLVMediaPlayer() + + private observers: MutableObserver[] = [] + + constructor( + mediator: PLVMPMediaMediator + ) { + this.mediator = mediator + + this.mediator.seekTo = (position: number) => this.seekTo(position) + this.mediator.getSpeed = () => this.player.getStateListenerRegistry().speed.value ?? 1 + this.mediator.setSpeed = (speed: number) => this.setSpeed(speed) + this.mediator.isPlaying = () => this.player.getStateListenerRegistry().playingState.value === PLVMediaPlayerPlayingState.PLAYING + this.mediator.getVolume = () => this.player.getStateListenerRegistry().volume.value ?? 100 + this.mediator.setVolume = (volume: number) => this.setVolume(volume) + this.mediator.bindAuxiliaryPlayer = (auxiliaryPlayer) => this.player.bindAuxiliaryPlayer(auxiliaryPlayer) + this.mediator.unbindAuxiliaryPlayer = (auxiliaryPlayer) => this.player.unbindAuxiliaryPlayer(auxiliaryPlayer) + this.player.getEventListenerRegistry().onPrepared.relayTo(this.mediator.onPreparedEvent).pushTo(this.observers) + this.player.getBusinessListenerRegistry().onAutoContinueEvent.relayTo(this.mediator.onAutoContinueEvent).pushTo(this.observers) + this.player.getEventListenerRegistry().onInfo.relayTo(this.mediator.onInfoEvent).pushTo(this.observers) + this.player.getEventListenerRegistry().onCompleted.relayTo(this.mediator.onCompleteEvent).pushTo(this.observers) + this.player.getStateListenerRegistry().playingState.relayTo(this.mediator.playingState).pushTo(this.observers) + this.player.getStateListenerRegistry().playerState.relayTo(this.mediator.playerState).pushTo(this.observers) + this.player.getBusinessListenerRegistry().businessErrorState.relayTo(this.mediator.businessErrorState).pushTo(this.observers) + } + + setMediaResource(mediaResource: PLVMediaResource) { + this.player.setMediaResource(mediaResource) + this.mediator.mediaResource.value = mediaResource + } + + setXComponent(xComponent: any) { + this.player.setXComponent(xComponent) + } + + setAutoContinue(autoContinue: boolean) { + this.player.setAutoContinue(autoContinue) + } + + setPlayerOption(options: PLVMediaPlayerOption[]) { + this.player.setPlayerOption(options) + } + + start() { + this.player.start() + } + + pause() { + this.player.pause() + } + + seekTo(position: number) { + this.player.seek(position) + } + + restart() { + this.player.restart() + } + + setSpeed(speed: number) { + this.player.setSpeed(speed) + } + + setVolume(volume: number) { + this.player.setVolume(volume) + } + + changeBitRate(bitRate: PLVMediaBitRate) { + this.player.changeBitRate(bitRate) + this.mediator.onChangeBitRateEvent.value = bitRate + } + + changeMediaOutputMode(outputMode: PLVMediaOutputMode) { + this.player.changeMediaOutputMode(outputMode) + } + + setShowSubtitles(subtitles: PLVMediaSubtitle[]) { + this.player.setShowSubtitles(subtitles) + } + + onDestroy(): void { + this.player.destroy() + MutableObserver.disposeAll(this.observers) + this.observers = [] + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/media/viewmodel/PLVMPMediaViewModel.ts b/polyv/src/main/ets/common/modules/media/viewmodel/PLVMPMediaViewModel.ts new file mode 100644 index 0000000..77eddf6 --- /dev/null +++ b/polyv/src/main/ets/common/modules/media/viewmodel/PLVMPMediaViewModel.ts @@ -0,0 +1,109 @@ +import {PLVMPMediaRepo} from '../model/PLVMPMediaRepo'; +import {PLVMPMediaUseCases} from './usecase/PLVMPMediaUseCases'; +import { + LifecycleAwareDependComponent, + MutableEvent, + MutableState, + PLVMediaBitRate, + PLVMediaOutputMode, + PLVMediaPlayerAutoContinueEvent, + PLVMediaPlayerOnCompletedEvent, + PLVMediaPlayerOnInfoEvent, + PLVMediaPlayerOnPreparedEvent, + PLVMediaPlayerOption, + PLVMediaPlayerState, + PLVMediaResource, + PLVMediaSubtitle, + State +} from '@polyvharmony/media-player-sdk'; +import {PLVMPMediaPlayViewState} from './viewstate/PLVMPMediaPlayViewState'; +import {PLVMPMediaInfoViewState} from './viewstate/PLVMPMediaInfoViewState'; +import {image} from '@kit.ImageKit'; + +export class PLVMPMediaViewModel implements LifecycleAwareDependComponent { + + private readonly repo: PLVMPMediaRepo + private readonly useCases: PLVMPMediaUseCases + + readonly mediaPlayViewState: State + readonly mediaInfoViewState: State + readonly networkPoorEvent: MutableEvent + readonly onChangeBitRateEvent: MutableEvent + readonly onPreparedEvent: MutableEvent + readonly onAutoContinueEvent: MutableEvent + readonly onInfoEvent: MutableEvent + readonly onCompleteEvent: MutableEvent + readonly playerState: MutableState + + onScreenshot: (() => Promise) | null = null + + constructor( + repo: PLVMPMediaRepo, + useCases: PLVMPMediaUseCases + ) { + this.repo = repo + this.useCases = useCases + this.mediaPlayViewState = this.repo.mediator.mediaPlayViewState + this.mediaInfoViewState = this.repo.mediator.mediaInfoViewState + this.networkPoorEvent = this.repo.mediator.networkPoorEvent + this.onChangeBitRateEvent = this.repo.mediator.onChangeBitRateEvent + this.onPreparedEvent = this.repo.mediator.onPreparedEvent + this.onAutoContinueEvent = this.repo.mediator.onAutoContinueEvent + this.onInfoEvent = this.repo.mediator.onInfoEvent + this.onCompleteEvent = this.repo.mediator.onCompleteEvent + this.playerState = this.repo.mediator.playerState + } + + setMediaResource(mediaResource: PLVMediaResource) { + this.repo.setMediaResource(mediaResource) + } + + setXComponent(xComponent: any) { + this.repo.setXComponent(xComponent) + } + + setAutoContinue(autoContinue: boolean) { + this.repo.setAutoContinue(autoContinue) + } + + setPlayerOption(options: PLVMediaPlayerOption[]) { + this.repo.setPlayerOption(options) + } + + start() { + this.repo.start() + } + + pause() { + this.repo.pause() + } + + seekTo(position: number) { + this.repo.seekTo(position) + } + + restart() { + this.repo.restart() + } + + setSpeed(speed: number) { + this.repo.setSpeed(speed) + } + + changeBitRate(bitRate: PLVMediaBitRate) { + this.repo.changeBitRate(bitRate) + } + + changeMediaOutputMode(outputMode: PLVMediaOutputMode) { + this.repo.changeMediaOutputMode(outputMode) + } + + setShowSubtitles(subtitles: PLVMediaSubtitle[]) { + this.repo.setShowSubtitles(subtitles) + } + + onDestroy(): void { + this.onScreenshot = null + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/media/viewmodel/usecase/HandleAudioFocusUseCase.ts b/polyv/src/main/ets/common/modules/media/viewmodel/usecase/HandleAudioFocusUseCase.ts new file mode 100644 index 0000000..81a0117 --- /dev/null +++ b/polyv/src/main/ets/common/modules/media/viewmodel/usecase/HandleAudioFocusUseCase.ts @@ -0,0 +1,31 @@ +import {PLVMPMediaRepo} from "../../model/PLVMPMediaRepo"; +import {PLVMediaPlayerOnAudioFocusInterruptEvent, PLVMediaPlayerPlayingState} from '@polyvharmony/media-player-sdk'; + +export class HandleAudioFocusUseCase { + + private readonly repo: PLVMPMediaRepo + private pauseByAudioFocusInterrupted = false + + constructor(repo: PLVMPMediaRepo) { + this.repo = repo + + this.observeAudioFocusEvent() + } + + private observeAudioFocusEvent() { + this.repo.player.getEventListenerRegistry().onAudioFocusInterruptEvent.observe((event) => { + if (event.arg === PLVMediaPlayerOnAudioFocusInterruptEvent.INTERRUPTED_TO_PAUSE) { + this.pauseByAudioFocusInterrupted = this.repo.player.getStateListenerRegistry().playingState.value === PLVMediaPlayerPlayingState.PLAYING + if (this.pauseByAudioFocusInterrupted) { + this.repo.pause() + } + } else if (event.arg === PLVMediaPlayerOnAudioFocusInterruptEvent.INTERRUPTED_RESUME_TO_PLAY) { + if (this.pauseByAudioFocusInterrupted) { + this.repo.start() + } + this.pauseByAudioFocusInterrupted = false + } + }) + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/media/viewmodel/usecase/ObserveNetworkPoorUseCase.ts b/polyv/src/main/ets/common/modules/media/viewmodel/usecase/ObserveNetworkPoorUseCase.ts new file mode 100644 index 0000000..1286530 --- /dev/null +++ b/polyv/src/main/ets/common/modules/media/viewmodel/usecase/ObserveNetworkPoorUseCase.ts @@ -0,0 +1,126 @@ +import { + extendArray, + LifecycleAwareDependComponent, + MutableObserver, + PLVMediaPlayerOnInfoEvent, + seconds +} from '@polyvharmony/media-player-sdk' +import {PLVMPMediaRepo} from "../../model/PLVMPMediaRepo"; + +// + +// 计时:5秒卡顿进行一次提示 +const INDICATE_BUFFERING_TIMEOUT = seconds(5).toMillis(); +// 计次:计算10秒内卡顿次数 +const INDICATE_COUNT_BUFFERING_DURATION = seconds(10).toMillis(); +// 计次:2次卡顿进行一次提示 +const INDICATE_BUFFERING_COUNT_TOO_MORE_THRESHOLD = 2; + +// + +export class ObserveNetworkPoorUseCase implements LifecycleAwareDependComponent { + + private readonly repo: PLVMPMediaRepo + private bufferingEvents: BufferingEventVO[] = [] + private lastSeekTimestamp: number = 0 + private isIndicatedNetworkPoor: boolean = false + private observers: MutableObserver[] = [] + + constructor( + repo: PLVMPMediaRepo + ) { + this.repo = repo + + this.observePlayerEvent() + } + + private observePlayerEvent() { + this.repo.player.getEventListenerRegistry().onInfo.observe((onInfo: PLVMediaPlayerOnInfoEvent) => { + if (onInfo.what === PLVMediaPlayerOnInfoEvent.MEDIA_INFO_BUFFERING_START) { + this.onBufferingStart() + } + if (onInfo.what === PLVMediaPlayerOnInfoEvent.MEDIA_INFO_BUFFERING_END) { + this.onBufferingEnd() + } + }).pushTo(this.observers) + this.repo.player.getEventListenerRegistry().onSeekStartEvent.observe(() => { + this.onSeekStart() + }).pushTo(this.observers) + this.repo.player.getEventListenerRegistry().onPrepared.observe(() => { + this.reset() + }) + } + + private onBufferingStart() { + const event = new BufferingEventVO() + event.bySeek = Date.now() - this.lastSeekTimestamp < 500 + this.bufferingEvents.push(event) + + this.checkToShowIndicate() + setTimeout(() => this.checkToShowIndicate(), INDICATE_BUFFERING_TIMEOUT) + } + + private onBufferingEnd() { + const event = extendArray(this.bufferingEvents).lastOrNull_ext() + if (!event) { + return + } + event.endTimestamp = Date.now() + } + + private onSeekStart() { + this.lastSeekTimestamp = Date.now() + } + + private reset() { + this.bufferingEvents = [] + this.isIndicatedNetworkPoor = false + this.lastSeekTimestamp = 0 + } + + private checkToShowIndicate() { + this.dropExpireBufferingCache() + const isNetworkPoor = this.checkBufferTooLong() || this.checkBufferTooMore() + if (isNetworkPoor && !this.isIndicatedNetworkPoor) { + this.repo.mediator.networkPoorEvent.value = Date.now() + this.isIndicatedNetworkPoor = true + } + } + + private dropExpireBufferingCache() { + this.bufferingEvents = this.bufferingEvents + .filter((event) => { + return event.endTimestamp < 0 || event.startTimestamp > Date.now() - INDICATE_COUNT_BUFFERING_DURATION + }) + } + + private checkBufferTooLong(): boolean { + const event = extendArray(this.bufferingEvents).lastOrNull_ext() + if (!event) { + return false + } + if (event.endTimestamp < 0) { + return event.startTimestamp < Date.now() - INDICATE_BUFFERING_TIMEOUT + } else { + return event.endTimestamp - event.startTimestamp > INDICATE_BUFFERING_TIMEOUT + } + } + + private checkBufferTooMore(): boolean { + return this.bufferingEvents + .filter((event) => !event.bySeek) + .length >= INDICATE_BUFFERING_COUNT_TOO_MORE_THRESHOLD + } + + onDestroy() { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } + +} + +class BufferingEventVO { + startTimestamp: number = Date.now() + endTimestamp: number = -1 + bySeek: boolean = false +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/media/viewmodel/usecase/PLVMPMediaUseCases.ts b/polyv/src/main/ets/common/modules/media/viewmodel/usecase/PLVMPMediaUseCases.ts new file mode 100644 index 0000000..5b94b4f --- /dev/null +++ b/polyv/src/main/ets/common/modules/media/viewmodel/usecase/PLVMPMediaUseCases.ts @@ -0,0 +1,25 @@ +import {UpdateMediaStateUseCase} from './UpdateMediaStateUseCase'; +import {ObserveNetworkPoorUseCase} from "./ObserveNetworkPoorUseCase"; +import {UpdateBufferingSpeedUseCase} from "./UpdateBufferingSpeedUseCase"; +import {HandleAudioFocusUseCase} from "./HandleAudioFocusUseCase"; + +export class PLVMPMediaUseCases { + + readonly handleAudioFocusUseCase: HandleAudioFocusUseCase + readonly updateMediaStateUseCase: UpdateMediaStateUseCase + readonly observeNetworkPoorUseCase: ObserveNetworkPoorUseCase + readonly updateBufferingSpeedUseCase: UpdateBufferingSpeedUseCase + + constructor( + handleAudioFocusUseCase: HandleAudioFocusUseCase, + updateMediaStateUseCase: UpdateMediaStateUseCase, + observeNetworkPoorUseCase: ObserveNetworkPoorUseCase, + updateBufferingSpeedUseCase: UpdateBufferingSpeedUseCase + ) { + this.handleAudioFocusUseCase = handleAudioFocusUseCase + this.updateMediaStateUseCase = updateMediaStateUseCase + this.observeNetworkPoorUseCase = observeNetworkPoorUseCase + this.updateBufferingSpeedUseCase = updateBufferingSpeedUseCase + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/media/viewmodel/usecase/UpdateBufferingSpeedUseCase.ts b/polyv/src/main/ets/common/modules/media/viewmodel/usecase/UpdateBufferingSpeedUseCase.ts new file mode 100644 index 0000000..e0a0ff1 --- /dev/null +++ b/polyv/src/main/ets/common/modules/media/viewmodel/usecase/UpdateBufferingSpeedUseCase.ts @@ -0,0 +1,45 @@ +import {extendNumber, LifecycleAwareDependComponent} from '@polyvharmony/media-player-sdk' +import {PLVMPMediaRepo} from "../../model/PLVMPMediaRepo"; + +export class UpdateBufferingSpeedUseCase implements LifecycleAwareDependComponent { + + private readonly repo: PLVMPMediaRepo + + private trafficSpeedIntervalId: number | null = null + private lastTrafficCount: number = 0 + private lastTrafficTimestamp: number = 0 + + constructor( + repo: PLVMPMediaRepo + ) { + this.repo = repo + + this.observePlayerTrafficSpeed() + } + + private observePlayerTrafficSpeed() { + this.trafficSpeedIntervalId = setInterval(() => { + if (this.lastTrafficTimestamp === 0) { + this.lastTrafficCount = this.repo.player.getTrafficStatisticByteCount() + this.lastTrafficTimestamp = Date.now() + return + } + const newTrafficCount = this.repo.player.getTrafficStatisticByteCount() + const newTrafficTimestamp = Date.now() + const diffCount = newTrafficCount - this.lastTrafficCount + const duration = newTrafficTimestamp - this.lastTrafficTimestamp + const speed = diffCount / duration * 1000 + this.lastTrafficCount = newTrafficCount + this.lastTrafficTimestamp = newTrafficTimestamp + + this.repo.mediator.bufferingSpeed.value = extendNumber(speed).coerceAtLeast_ext(0) + }, 500) + } + + onDestroy() { + if (this.trafficSpeedIntervalId !== null) { + clearInterval(this.trafficSpeedIntervalId) + } + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/media/viewmodel/usecase/UpdateMediaStateUseCase.ts b/polyv/src/main/ets/common/modules/media/viewmodel/usecase/UpdateMediaStateUseCase.ts new file mode 100644 index 0000000..a201d28 --- /dev/null +++ b/polyv/src/main/ets/common/modules/media/viewmodel/usecase/UpdateMediaStateUseCase.ts @@ -0,0 +1,101 @@ +import {PLVMPMediaRepo} from '../../model/PLVMPMediaRepo'; +import { + DerivedState, + isLiteralTrue, + LifecycleAwareDependComponent, + MutableObserver, + PLVMediaOutputMode, + PLVMediaPlayer, + PLVMediaPlayerPlayingState, + PLVMediaPlayerState, + PLVMediaSubtitle, + Rect +} from '@polyvharmony/media-player-sdk'; +import {PLVMPMediaPlayViewState} from '../viewstate/PLVMPMediaPlayViewState'; +import {PLVMPMediaInfoViewState, PLVMPSubtitleTextStyle} from '../viewstate/PLVMPMediaInfoViewState'; + +export class UpdateMediaStateUseCase implements LifecycleAwareDependComponent { + + private readonly repo: PLVMPMediaRepo + + private observers: MutableObserver[] = [] + + constructor( + repo: PLVMPMediaRepo + ) { + this.repo = repo + + new DerivedState(() => { + const viewState = new PLVMPMediaPlayViewState(); + viewState.currentProgress = this.repo.player.getStateListenerRegistry().progressState.value ?? 0 + viewState.duration = this.repo.player.getStateListenerRegistry().durationState.value ?? 0 + viewState.isPlaying = this.repo.player.getStateListenerRegistry().playingState.value === PLVMediaPlayerPlayingState.PLAYING + viewState.playerState = this.repo.player.getStateListenerRegistry().playerState.value ?? PLVMediaPlayerState.STATE_IDLE + viewState.isBuffering = this.repo.player.getStateListenerRegistry().isBuffering.value ?? false + viewState.bufferingSpeed = this.repo.mediator.bufferingSpeed.value ?? 0 + viewState.speed = this.repo.player.getStateListenerRegistry().speed.value ?? 1 + viewState.subtitleTexts = this.repo.player.getBusinessListenerRegistry().vodCurrentSubTitleTexts.value ?? [] + return viewState; + }).relayTo(this.repo.mediator.mediaPlayViewState) + .pushTo(this.observers); + + new DerivedState(() => { + const viewState = new PLVMPMediaInfoViewState(); + viewState.title = this.repo.player.getBusinessListenerRegistry().vodVideoJson.value?.title ?? "" + viewState.videoSize = this.repo.player.getStateListenerRegistry().videoSize.value ?? new Rect() + viewState.bitRate = this.repo.player.getBusinessListenerRegistry().currentMediaBitRate.value ?? null + viewState.supportBitRates = this.repo.player.getBusinessListenerRegistry().supportMediaBitRates.value ?? [] + viewState.outputMode = this.repo.player.getBusinessListenerRegistry().currentMediaOutputMode.value ?? PLVMediaOutputMode.AUDIO_VIDEO + viewState.supportOutputModes = this.repo.player.getBusinessListenerRegistry().supportMediaOutputModes.value ?? [] + viewState.currentSubtitle = this.repo.player.getBusinessListenerRegistry().currentShowSubTitles.value ?? null + viewState.supportSubtitles = this.getSupportSubtitles(this.repo.player) + viewState.progressPreviewImage = this.repo.player.getBusinessListenerRegistry().vodVideoJson.value?.progressImage ?? null + viewState.progressPreviewImageInterval = this.repo.player.getBusinessListenerRegistry().vodVideoJson.value?.progressImageInterval?.toSeconds() ?? -1 + viewState.audioModeCoverImage = this.repo.player.getBusinessListenerRegistry().vodVideoJson.value?.first_image ?? null + viewState.topSubtitleTextStyle = this.getSubtitleTextStyle(this.repo.player, "top") + viewState.bottomSubtitleTextStyle = this.getSubtitleTextStyle(this.repo.player, "bottom") + return viewState; + }).relayTo(this.repo.mediator.mediaInfoViewState) + .pushTo(this.observers); + } + + private getSupportSubtitles(player: PLVMediaPlayer): PLVMediaSubtitle[][] { + const subtitleSetting = player.getBusinessListenerRegistry().supportSubtitleSetting.value + if (!subtitleSetting || !subtitleSetting.available) { + return [] + } + const singleSubtitles = subtitleSetting.availableSubtitles.map((subtitle) => [subtitle]) + if (subtitleSetting.defaultDoubleSubtitles) { + return [subtitleSetting.defaultDoubleSubtitles, ...singleSubtitles] + } else { + return singleSubtitles + } + } + + private getSubtitleTextStyle(player: PLVMediaPlayer, position: 'top' | 'bottom'): PLVMPSubtitleTextStyle { + const result = new PLVMPSubtitleTextStyle() + const isDoubleSubtitle = (player.getBusinessListenerRegistry().currentShowSubTitles.value?.length ?? 0) >= 2 + const targetSubtitleStyle = player.getBusinessListenerRegistry().vodVideoJson.value?.player?.subtitles + ?.find((subtitle) => { + if (!isDoubleSubtitle) { + return subtitle.style === "single" + } else { + return subtitle.style === "double" && subtitle.position === position + } + }) + if (targetSubtitleStyle === undefined) { + return result + } + result.fontColor = targetSubtitleStyle.fontColor ?? "#FFFFFF" + result.isBold = isLiteralTrue(targetSubtitleStyle.fontBold) + result.isItalic = isLiteralTrue(targetSubtitleStyle.fontItalics) + result.backgroundColor = targetSubtitleStyle.backgroundColor ?? "#000000" + return result + } + + onDestroy() { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/media/viewmodel/viewstate/PLVMPMediaInfoViewState.ts b/polyv/src/main/ets/common/modules/media/viewmodel/viewstate/PLVMPMediaInfoViewState.ts new file mode 100644 index 0000000..4e17147 --- /dev/null +++ b/polyv/src/main/ets/common/modules/media/viewmodel/viewstate/PLVMPMediaInfoViewState.ts @@ -0,0 +1,24 @@ +import {PLVMediaBitRate, PLVMediaOutputMode, PLVMediaSubtitle, Rect} from '@polyvharmony/media-player-sdk' + +export class PLVMPMediaInfoViewState { + title: string = "" + videoSize: Rect = new Rect() + bitRate: PLVMediaBitRate | null = null + supportBitRates: PLVMediaBitRate[] = [] + outputMode: PLVMediaOutputMode = PLVMediaOutputMode.AUDIO_VIDEO + supportOutputModes: PLVMediaOutputMode[] = [] + currentSubtitle: PLVMediaSubtitle[] | null = null + supportSubtitles: PLVMediaSubtitle[][] = [] + progressPreviewImage: string | null = null + progressPreviewImageInterval: number = -1 + audioModeCoverImage: string | null = null + topSubtitleTextStyle: PLVMPSubtitleTextStyle = new PLVMPSubtitleTextStyle() + bottomSubtitleTextStyle: PLVMPSubtitleTextStyle = new PLVMPSubtitleTextStyle() +} + +export class PLVMPSubtitleTextStyle { + fontColor: string = "#FFFFFF" + isBold: boolean = false + isItalic: boolean = false + backgroundColor: string = "#000000" +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/media/viewmodel/viewstate/PLVMPMediaPlayViewState.ts b/polyv/src/main/ets/common/modules/media/viewmodel/viewstate/PLVMPMediaPlayViewState.ts new file mode 100644 index 0000000..80b3a53 --- /dev/null +++ b/polyv/src/main/ets/common/modules/media/viewmodel/viewstate/PLVMPMediaPlayViewState.ts @@ -0,0 +1,13 @@ +import {PLVMediaPlayerState, PLVVodSubtitleText} from '@polyvharmony/media-player-sdk'; + +export class PLVMPMediaPlayViewState { + currentProgress: number = 0 + duration: number = 0 + isPlaying: boolean = false + playerState: PLVMediaPlayerState = PLVMediaPlayerState.STATE_IDLE + isBuffering: boolean = false + // bytes per second + bufferingSpeed: number = 0 + speed: number = 1 + subtitleTexts: PLVVodSubtitleText[] = [] +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/mediacontroller/di/PLVMPMediaControlModule.ts b/polyv/src/main/ets/common/modules/mediacontroller/di/PLVMPMediaControlModule.ts new file mode 100644 index 0000000..ca52d2e --- /dev/null +++ b/polyv/src/main/ets/common/modules/mediacontroller/di/PLVMPMediaControlModule.ts @@ -0,0 +1,29 @@ +import {DependModule} from '@polyvharmony/media-player-sdk'; +import {PLVMPMediaControllerViewModel} from '../viewmodel/PLVMPMediaControllerViewModel'; +import {PLVMPMediaMediator} from '../../media/mediator/PLVMPMediaMediator'; +import {PLVMPMediaControllerUseCases} from "../viewmodel/usecase/PLVMPMediaControllerUseCases"; +import {PLVMPMediaControllerRepo} from "../model/PLVMPMediaControllerRepo"; +import {UpdateMediaStopOverlayUseCase} from "../viewmodel/usecase/UpdateMediaStopOverlayUseCase"; +import {PLVMPMediaControllerMediator} from "../mediator/PLVMPMediaControllerMediator"; + +export const mediaControllerModule = new DependModule() + +mediaControllerModule.provide(PLVMPMediaControllerMediator, () => new PLVMPMediaControllerMediator()) + +mediaControllerModule.provide(PLVMPMediaControllerRepo, (scope) => new PLVMPMediaControllerRepo( + scope.get(PLVMPMediaControllerMediator), + scope.get(PLVMPMediaMediator) +)) + +mediaControllerModule.provide(UpdateMediaStopOverlayUseCase, (scope) => new UpdateMediaStopOverlayUseCase( + scope.get(PLVMPMediaControllerRepo) +)) + +mediaControllerModule.provide(PLVMPMediaControllerUseCases, (scope) => new PLVMPMediaControllerUseCases( + scope.get(UpdateMediaStopOverlayUseCase) +)) + +mediaControllerModule.provide(PLVMPMediaControllerViewModel, (scope) => new PLVMPMediaControllerViewModel( + scope.get(PLVMPMediaControllerRepo), + scope.get(PLVMPMediaControllerUseCases) +)) \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/mediacontroller/mediator/PLVMPMediaControllerMediator.ts b/polyv/src/main/ets/common/modules/mediacontroller/mediator/PLVMPMediaControllerMediator.ts new file mode 100644 index 0000000..97af2dc --- /dev/null +++ b/polyv/src/main/ets/common/modules/mediacontroller/mediator/PLVMPMediaControllerMediator.ts @@ -0,0 +1,24 @@ +import { + LifecycleAwareDependComponent, + MutableEvent, + MutableSource, + MutableState, + PLVMediaPlayerBusinessErrorEnum +} from "@polyvharmony/media-player-sdk"; +import {PLVMPMediaControllerViewState} from "../viewmodel/viewstate/PLVMPMediaControllerViewState"; + +export class PLVMPMediaControllerMediator implements LifecycleAwareDependComponent { + + readonly mediaControllerViewState = new MutableState(new PLVMPMediaControllerViewState()) + // 亮度更新事件,范围 0.0 ~ 1.0 + readonly brightnessUpdateEvent = new MutableEvent() + // 音量更新事件,范围 0 ~ 100 + readonly volumeUpdateEvent = new MutableEvent() + readonly businessErrorState = new MutableState(null) + readonly playCompleteState = new MutableState(false) + + onDestroy() { + MutableSource.disposeAll(this) + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/mediacontroller/model/PLVMPMediaControllerRepo.ts b/polyv/src/main/ets/common/modules/mediacontroller/model/PLVMPMediaControllerRepo.ts new file mode 100644 index 0000000..6addc95 --- /dev/null +++ b/polyv/src/main/ets/common/modules/mediacontroller/model/PLVMPMediaControllerRepo.ts @@ -0,0 +1,17 @@ +import {PLVMPMediaMediator} from "../../media/mediator/PLVMPMediaMediator"; +import {PLVMPMediaControllerMediator} from "../mediator/PLVMPMediaControllerMediator"; + +export class PLVMPMediaControllerRepo { + + readonly mediator: PLVMPMediaControllerMediator + readonly mediaMediator: PLVMPMediaMediator + + constructor( + mediator: PLVMPMediaControllerMediator, + mediaMediator: PLVMPMediaMediator + ) { + this.mediator = mediator + this.mediaMediator = mediaMediator + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel.ts b/polyv/src/main/ets/common/modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel.ts new file mode 100644 index 0000000..8c36a8d --- /dev/null +++ b/polyv/src/main/ets/common/modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel.ts @@ -0,0 +1,213 @@ +import { + extendNumber, + LifecycleAwareDependComponent, + MutableEvent, + MutableObserver, + MutableState, + PLVMediaPlayerOnInfoEvent, + runCatching +} from '@polyvharmony/media-player-sdk'; +import { + PLVMPMediaControllerFloatAction, + PLVMPMediaControllerViewState, + PLVMPVideoViewLocation +} from './viewstate/PLVMPMediaControllerViewState'; +import {PLVBrightnessManager} from "../../../utils/PLVBrightnessManager"; +import {PLVMPMediaControllerUseCases} from "./usecase/PLVMPMediaControllerUseCases"; +import {PLVMPMediaControllerRepo} from "../model/PLVMPMediaControllerRepo"; + +export class PLVMPMediaControllerViewModel implements LifecycleAwareDependComponent { + + private readonly repo: PLVMPMediaControllerRepo + private readonly useCases: PLVMPMediaControllerUseCases + + readonly mediaControllerViewState: MutableState + // 亮度更新事件,范围 0.0 ~ 1.0 + readonly brightnessUpdateEvent: MutableEvent + // 音量更新事件,范围 0 ~ 100 + readonly volumeUpdateEvent: MutableEvent + + private observers: MutableObserver[] = [] + + constructor( + repo: PLVMPMediaControllerRepo, + useCases: PLVMPMediaControllerUseCases + ) { + this.repo = repo + this.useCases = useCases + this.mediaControllerViewState = this.repo.mediator.mediaControllerViewState + this.brightnessUpdateEvent = this.repo.mediator.brightnessUpdateEvent + this.volumeUpdateEvent = this.repo.mediator.volumeUpdateEvent + + this.observeSeekFinishEvent() + this.observeMediaStopState() + } + + handleDragSlider(event: 'slide' | 'slideFinish' | 'click', progress: number) { + switch (event) { + case "slide": + this.mediaControllerViewState.mutate({ + progressSliderDragging: true, + progressSliderWaitSeekFinish: true, + progressSliderDragPosition: progress + }) + break; + case "slideFinish": + if (this.mediaControllerViewState.value?.progressSliderDragging) { + this.repo.mediaMediator.seekTo?.apply(null, [this.mediaControllerViewState.value?.progressSliderDragPosition ?? 0]) + } + this.mediaControllerViewState.mutate({ + progressSliderDragging: false, + progressSliderDragPosition: 0 + }) + break; + case "click": + this.repo.mediaMediator.seekTo?.apply(null, [progress]) + this.mediaControllerViewState.mutate({ + progressSliderWaitSeekFinish: true + }) + break; + } + } + + handleDragGestureSeek(event: 'update' | 'end', progress: number) { + switch (event) { + case "update": + this.handleDragSlider('slide', progress) + break; + case "end": + this.handleDragSlider('slideFinish', progress) + break; + } + } + + changeControllerVisible(toVisible?: boolean) { + const currentVisible = this.mediaControllerViewState.value?.controllerVisible ?? false + this.mediaControllerViewState.mutate({ + controllerVisible: toVisible ?? !currentVisible + }) + } + + handleLongPressSpeeding(event: 'start' | 'end') { + switch (event) { + case "start": + if (!this.repo.mediaMediator.isPlaying?.apply(null)) { + break; + } + this.mediaControllerViewState.mutate({ + longPressSpeeding: true, + speedBeforeLongPress: this.repo.mediaMediator.getSpeed?.apply(null) + }) + this.repo.mediaMediator.setSpeed?.apply(null, [2]) + break; + case "end": + if (!this.mediaControllerViewState.value?.longPressSpeeding) { + break; + } + this.repo.mediaMediator.setSpeed?.apply(null, [this.mediaControllerViewState.value?.speedBeforeLongPress ?? 1]) + this.mediaControllerViewState.mutate({ + longPressSpeeding: false, + speedBeforeLongPress: 1 + }) + break; + } + } + + async changeBrightness(direction: 'up' | 'down') { + const currentBrightnessResult = await runCatching(PLVBrightnessManager.getInstance().getBrightness()) + if (!currentBrightnessResult.success || currentBrightnessResult.data === undefined) { + return + } + const currentBrightness = currentBrightnessResult.data < 0 ? 0.5 : currentBrightnessResult.data + let nextBrightness = direction === 'up' ? currentBrightness + 0.1 : currentBrightness - 0.1 + nextBrightness = extendNumber(nextBrightness).coerceIn_ext(0, 1) + const updateResult = await runCatching(PLVBrightnessManager.getInstance().setBrightness(nextBrightness)) + if (updateResult.success) { + this.brightnessUpdateEvent.value = nextBrightness + } + } + + changeVolume(direction: 'up' | 'down') { + const currentVolume = this.repo.mediaMediator.getVolume?.apply(null) + if (currentVolume === undefined) { + return + } + let nextVolume = direction === 'up' ? currentVolume + 10 : currentVolume - 10 + nextVolume = extendNumber(nextVolume).coerceIn_ext(0, 100) + this.repo.mediaMediator.setVolume?.apply(null, [nextVolume]) + this.volumeUpdateEvent.value = nextVolume + } + + lockMediaController(action: 'lock' | 'unlock') { + switch (action) { + case "lock": + this.mediaControllerViewState.mutate({ + controllerLocking: true + }) + break; + case "unlock": + this.mediaControllerViewState.mutate({ + controllerLocking: false + }) + break; + } + } + + pushFloatActionLayout(layout: PLVMPMediaControllerFloatAction) { + this.mediaControllerViewState.mutate({ + floatActionLayouts: [...this.mediaControllerViewState.value?.floatActionLayouts ?? [], layout] + }) + } + + popFloatActionLayout() { + const floatActionLayouts = this.mediaControllerViewState.value?.floatActionLayouts ?? [] + if (floatActionLayouts.length === 0) { + return + } + floatActionLayouts.pop() + this.mediaControllerViewState.mutate({ + floatActionLayouts: floatActionLayouts + }) + } + + updateVideoViewLocation(videoViewLocation: PLVMPVideoViewLocation) { + this.mediaControllerViewState.mutate({ + videoViewLocation: videoViewLocation + }) + } + + private observeSeekFinishEvent() { + this.repo.mediaMediator.onInfoEvent?.observe((onInfo) => { + if (this.mediaControllerViewState.value?.progressSliderWaitSeekFinish + && [PLVMediaPlayerOnInfoEvent.MEDIA_INFO_AUDIO_SEEK_RENDERING_START, + PLVMediaPlayerOnInfoEvent.MEDIA_INFO_VIDEO_SEEK_RENDERING_START, + PLVMediaPlayerOnInfoEvent.MEDIA_INFO_AUDIO_RENDERING_START, + PLVMediaPlayerOnInfoEvent.MEDIA_INFO_VIDEO_RENDERING_START + ].includes(onInfo.what)) { + this.mediaControllerViewState.mutate({ + progressSliderWaitSeekFinish: false + }) + } + }).pushTo(this.observers) + } + + private observeMediaStopState() { + this.repo.mediator.businessErrorState.observe((error) => { + this.mediaControllerViewState.mutate({ + errorOverlayLayoutVisible: error !== null + }) + }).pushTo(this.observers) + + this.repo.mediator.playCompleteState.observe((complete) => { + this.mediaControllerViewState.mutate({ + completeOverlayLayoutVisible: complete + }) + }).pushTo(this.observers) + } + + onDestroy() { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/mediacontroller/viewmodel/usecase/PLVMPMediaControllerUseCases.ts b/polyv/src/main/ets/common/modules/mediacontroller/viewmodel/usecase/PLVMPMediaControllerUseCases.ts new file mode 100644 index 0000000..5748112 --- /dev/null +++ b/polyv/src/main/ets/common/modules/mediacontroller/viewmodel/usecase/PLVMPMediaControllerUseCases.ts @@ -0,0 +1,13 @@ +import {UpdateMediaStopOverlayUseCase} from "./UpdateMediaStopOverlayUseCase"; + +export class PLVMPMediaControllerUseCases { + + readonly updateMediaStopOverlayUseCase: UpdateMediaStopOverlayUseCase + + constructor( + updateMediaStopOverlayUseCase: UpdateMediaStopOverlayUseCase + ) { + this.updateMediaStopOverlayUseCase = updateMediaStopOverlayUseCase + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/mediacontroller/viewmodel/usecase/UpdateMediaStopOverlayUseCase.ts b/polyv/src/main/ets/common/modules/mediacontroller/viewmodel/usecase/UpdateMediaStopOverlayUseCase.ts new file mode 100644 index 0000000..389ea77 --- /dev/null +++ b/polyv/src/main/ets/common/modules/mediacontroller/viewmodel/usecase/UpdateMediaStopOverlayUseCase.ts @@ -0,0 +1,36 @@ +import {PLVMPMediaControllerRepo} from "../../model/PLVMPMediaControllerRepo"; +import {LifecycleAwareDependComponent, MutableObserver, PLVMediaPlayerState} from '@polyvharmony/media-player-sdk'; + +export class UpdateMediaStopOverlayUseCase implements LifecycleAwareDependComponent { + + private readonly repo: PLVMPMediaControllerRepo + + private observers: MutableObserver[] = [] + + constructor( + repo: PLVMPMediaControllerRepo + ) { + this.repo = repo + + this.observeErrorState() + this.observeCompleteState() + } + + private observeErrorState() { + this.repo.mediaMediator.businessErrorState?.observe((error) => { + this.repo.mediator.businessErrorState.value = error + }).pushTo(this.observers) + } + + private observeCompleteState() { + this.repo.mediaMediator.playerState?.observe((playerState) => { + this.repo.mediator.playCompleteState.value = playerState === PLVMediaPlayerState.STATE_COMPLETED + }) + } + + onDestroy() { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/mediacontroller/viewmodel/viewstate/PLVMPMediaControllerViewState.ts b/polyv/src/main/ets/common/modules/mediacontroller/viewmodel/viewstate/PLVMPMediaControllerViewState.ts new file mode 100644 index 0000000..10e4df5 --- /dev/null +++ b/polyv/src/main/ets/common/modules/mediacontroller/viewmodel/viewstate/PLVMPMediaControllerViewState.ts @@ -0,0 +1,33 @@ +export class PLVMPMediaControllerViewState { + controllerVisible: boolean = true + controllerLocking: boolean = false + progressSliderDragging: boolean = false + progressSliderDragPosition: number = 0 + progressSliderWaitSeekFinish: boolean = false + longPressSpeeding: boolean = false + speedBeforeLongPress: number = 1 + floatActionLayouts: PLVMPMediaControllerFloatAction[] = [] + videoViewLocation: PLVMPVideoViewLocation = {width: 0, height: 0, offset: {x: 0, y: 0}} + errorOverlayLayoutVisible: boolean = false + completeOverlayLayoutVisible: boolean = false + + isFloatActionLayoutVisible(): boolean { + return this.floatActionLayouts.length > 0 + } + + isMediaStopOverlayVisible(): boolean { + return this.errorOverlayLayoutVisible || this.completeOverlayLayoutVisible + } + +} + +export type PLVMPMediaControllerFloatAction = 'more' | 'bitRate' | 'speed' | 'subtitle' + +export type PLVMPVideoViewLocation = { + width: number, + height: number, + offset: { + x: number, + y: number + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/pagecontrol/di/PLVMPPageControlModule.ts b/polyv/src/main/ets/common/modules/pagecontrol/di/PLVMPPageControlModule.ts new file mode 100644 index 0000000..1da2b9c --- /dev/null +++ b/polyv/src/main/ets/common/modules/pagecontrol/di/PLVMPPageControlModule.ts @@ -0,0 +1,6 @@ +import {DependModule} from '@polyvharmony/media-player-sdk'; +import {PLVMPPageControlViewModel} from "../viewmodel/PLVMPPageControlViewModel"; + +export const pageControlModule = new DependModule() + +pageControlModule.provide(PLVMPPageControlViewModel, () => new PLVMPPageControlViewModel()) \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/pagecontrol/viewmodel/PLVMPPageControlViewModel.ts b/polyv/src/main/ets/common/modules/pagecontrol/viewmodel/PLVMPPageControlViewModel.ts new file mode 100644 index 0000000..cb9f1b7 --- /dev/null +++ b/polyv/src/main/ets/common/modules/pagecontrol/viewmodel/PLVMPPageControlViewModel.ts @@ -0,0 +1,15 @@ +import {MutableState, OnBackPressHandler} from '@polyvharmony/media-player-sdk'; +import {PLVComponentLifecycle} from "../../../utils/PLVComponentLifecycle"; + +export class PLVMPPageControlViewModel { + + readonly currentItemIndex: MutableState = new MutableState(0) + readonly pageLifecycle: PLVComponentLifecycle = new PLVComponentLifecycle() + readonly onBackPressHandler = new OnBackPressHandler() + navPathStack?: any | undefined = undefined + + onCurrentItemChanged(currentIndex: number) { + this.currentItemIndex.value = currentIndex + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/progressImage/di/PLVMPProgressImageModule.ts b/polyv/src/main/ets/common/modules/progressImage/di/PLVMPProgressImageModule.ts new file mode 100644 index 0000000..ed4546d --- /dev/null +++ b/polyv/src/main/ets/common/modules/progressImage/di/PLVMPProgressImageModule.ts @@ -0,0 +1,25 @@ +import {DependModule} from '@polyvharmony/media-player-sdk'; +import {PLVMPProgressImageLocalDataSource} from "../model/datasource/PLVMPProgressImageLocalDataSource"; +import {PLVMPProgressImageNetworkDataSource} from "../model/datasource/PLVMPProgressImageNetworkDataSource"; +import {PLVMPProgressImageRepo} from "../model/PLVMPProgressImageRepo"; +import {PLVMPProgressImageViewModel} from "../viewmodel/PLVMPProgressImageViewModel"; +import {PLVMPMediaMediator} from "../../media/mediator/PLVMPMediaMediator"; +import {DecodeProgressImageUseCase} from "../viewmodel/usecase/DecodeProgressImageUseCase"; + +export const progressImageModule = new DependModule() + +progressImageModule.provide(PLVMPProgressImageLocalDataSource, () => new PLVMPProgressImageLocalDataSource()) +progressImageModule.provide(PLVMPProgressImageNetworkDataSource, () => new PLVMPProgressImageNetworkDataSource()) + +progressImageModule.provide(PLVMPProgressImageRepo, (scope) => new PLVMPProgressImageRepo( + scope.get(PLVMPProgressImageLocalDataSource), + scope.get(PLVMPProgressImageNetworkDataSource), + scope.get(PLVMPMediaMediator) +)) + +progressImageModule.provide(DecodeProgressImageUseCase, () => new DecodeProgressImageUseCase()) + +progressImageModule.provide(PLVMPProgressImageViewModel, (scope) => new PLVMPProgressImageViewModel( + scope.get(PLVMPProgressImageRepo), + scope.get(DecodeProgressImageUseCase) +)) \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/progressImage/model/PLVMPProgressImageRepo.ts b/polyv/src/main/ets/common/modules/progressImage/model/PLVMPProgressImageRepo.ts new file mode 100644 index 0000000..1da4eaf --- /dev/null +++ b/polyv/src/main/ets/common/modules/progressImage/model/PLVMPProgressImageRepo.ts @@ -0,0 +1,36 @@ +import {PLVMPProgressImageLocalDataSource} from "./datasource/PLVMPProgressImageLocalDataSource"; +import {PLVMPProgressImageNetworkDataSource} from "./datasource/PLVMPProgressImageNetworkDataSource"; +import {PLVMPMediaMediator} from "../../media/mediator/PLVMPMediaMediator"; +import {PLVMPProgressImageData} from "./vo/PLVMPProgressImageData"; + +export class PLVMPProgressImageRepo { + + private readonly localDataSource: PLVMPProgressImageLocalDataSource + private readonly networkDataSource: PLVMPProgressImageNetworkDataSource + readonly mediaMediator: PLVMPMediaMediator + + constructor( + localDataSource: PLVMPProgressImageLocalDataSource, + networkDataSource: PLVMPProgressImageNetworkDataSource, + mediaMediator: PLVMPMediaMediator + ) { + this.localDataSource = localDataSource + this.networkDataSource = networkDataSource + this.mediaMediator = mediaMediator + } + + async getProgressImage(): Promise { + const mediaInfo = this.mediaMediator.mediaInfoViewState.value + if (mediaInfo === undefined || mediaInfo.progressPreviewImage === null) { + return null + } + let data = this.localDataSource.getProgressImage(mediaInfo.progressPreviewImage) + if (data !== null) { + return data + } + data = await this.networkDataSource.getProgressImage(mediaInfo.progressPreviewImage) + this.localDataSource.cacheProgressImage(data) + return data + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/progressImage/model/datasource/PLVMPProgressImageLocalDataSource.ts b/polyv/src/main/ets/common/modules/progressImage/model/datasource/PLVMPProgressImageLocalDataSource.ts new file mode 100644 index 0000000..f2963f2 --- /dev/null +++ b/polyv/src/main/ets/common/modules/progressImage/model/datasource/PLVMPProgressImageLocalDataSource.ts @@ -0,0 +1,27 @@ +import {LifecycleAwareDependComponent} from "@polyvharmony/media-player-sdk"; +import {PLVMPProgressImageData} from "../vo/PLVMPProgressImageData"; + +export class PLVMPProgressImageLocalDataSource implements LifecycleAwareDependComponent { + + private cachedProgressImageData: PLVMPProgressImageData | null = null + + getProgressImage(url: string): PLVMPProgressImageData | null { + if (this.cachedProgressImageData === null) { + return null + } + if (this.cachedProgressImageData.url !== url) { + this.cachedProgressImageData = null + return null + } + return this.cachedProgressImageData + } + + cacheProgressImage(data: PLVMPProgressImageData | null) { + this.cachedProgressImageData = data + } + + onDestroy() { + this.cachedProgressImageData = null + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/progressImage/model/datasource/PLVMPProgressImageNetworkDataSource.ts b/polyv/src/main/ets/common/modules/progressImage/model/datasource/PLVMPProgressImageNetworkDataSource.ts new file mode 100644 index 0000000..91e6d0c --- /dev/null +++ b/polyv/src/main/ets/common/modules/progressImage/model/datasource/PLVMPProgressImageNetworkDataSource.ts @@ -0,0 +1,24 @@ +import {PLVMPProgressImageData} from "../vo/PLVMPProgressImageData"; +import {runCatching} from '@polyvharmony/media-player-sdk'; +import http from '@ohos.net.http'; + +export class PLVMPProgressImageNetworkDataSource { + + async getProgressImage(url: string): Promise { + const networkResult = await runCatching(this.getHttpProgressImage(url)) + if (!networkResult.success || networkResult.data === null) { + return null + } + + return new PLVMPProgressImageData(url, networkResult.data) + } + + private async getHttpProgressImage(url: string): Promise { + const response = await http.createHttp().request(url) + if (response.responseCode !== http.ResponseCode.OK) { + return null + } + return response.result as ArrayBuffer + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/progressImage/model/vo/PLVMPProgressImageData.ts b/polyv/src/main/ets/common/modules/progressImage/model/vo/PLVMPProgressImageData.ts new file mode 100644 index 0000000..1b70ccb --- /dev/null +++ b/polyv/src/main/ets/common/modules/progressImage/model/vo/PLVMPProgressImageData.ts @@ -0,0 +1,14 @@ +export class PLVMPProgressImageData { + + readonly url: string + readonly buffer: ArrayBuffer + + constructor( + url: string, + buffer: ArrayBuffer + ) { + this.url = url + this.buffer = buffer + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/progressImage/viewmodel/PLVMPProgressImageViewModel.ts b/polyv/src/main/ets/common/modules/progressImage/viewmodel/PLVMPProgressImageViewModel.ts new file mode 100644 index 0000000..1ceae6c --- /dev/null +++ b/polyv/src/main/ets/common/modules/progressImage/viewmodel/PLVMPProgressImageViewModel.ts @@ -0,0 +1,51 @@ +import {MutableState} from '@polyvharmony/media-player-sdk'; +import {PLVMPProgressImageRepo} from "../model/PLVMPProgressImageRepo"; +import {DecodeProgressImageUseCase} from './usecase/DecodeProgressImageUseCase'; +import image from '@ohos.multimedia.image'; + +export class PLVMPProgressImageViewModel { + + private readonly repo: PLVMPProgressImageRepo + private readonly decodeProgressImageUseCase: DecodeProgressImageUseCase + + readonly progressImage: MutableState = new MutableState(null) + + private lastUpdateProgressIndex: number = -1 + private isUpdatingProgressImage: boolean = false + + constructor( + repo: PLVMPProgressImageRepo, + decodeProgressImageUseCase: DecodeProgressImageUseCase + ) { + this.repo = repo + this.decodeProgressImageUseCase = decodeProgressImageUseCase + } + + async updateProgressImage(positionMs: number): Promise { + const mediaInfo = this.repo.mediaMediator.mediaInfoViewState.value + if (mediaInfo === undefined || mediaInfo.progressPreviewImageInterval <= 0) { + this.progressImage.value = null + return + } + const index = Math.floor(positionMs / 1000 / mediaInfo.progressPreviewImageInterval) + if (index === this.lastUpdateProgressIndex || this.isUpdatingProgressImage) { + return + } + this.lastUpdateProgressIndex = index + this.isUpdatingProgressImage = true + + try { + const sourceImage = await this.repo.getProgressImage() + if (sourceImage === null) { + this.progressImage.value = null + return + } + this.progressImage.value = await this.decodeProgressImageUseCase.decode(sourceImage, index) + } catch (e) { + this.progressImage.value = null + } finally { + this.isUpdatingProgressImage = false + } + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/modules/progressImage/viewmodel/usecase/DecodeProgressImageUseCase.ts b/polyv/src/main/ets/common/modules/progressImage/viewmodel/usecase/DecodeProgressImageUseCase.ts new file mode 100644 index 0000000..fd3776c --- /dev/null +++ b/polyv/src/main/ets/common/modules/progressImage/viewmodel/usecase/DecodeProgressImageUseCase.ts @@ -0,0 +1,34 @@ +import {PLVMPProgressImageData} from "../../model/vo/PLVMPProgressImageData"; +import image from '@ohos.multimedia.image'; + +const PREVIEW_IMAGES_EACH_ROW = 50; +const PREVIEW_IMAGES_WIDTH = 160; +const PREVIEW_IMAGES_HEIGHT = 90; + +export class DecodeProgressImageUseCase { + + async decode(data: PLVMPProgressImageData, index: number): Promise { + const imageSource = image.createImageSource(data.buffer) + if (imageSource === null || imageSource === undefined) { + return null + } + return imageSource.createPixelMap({ + desiredRegion: this.getRegion(index), + desiredPixelFormat: image.PixelMapFormat.RGB_565 + }) + } + + private getRegion(index: number): image.Region { + const column = index % PREVIEW_IMAGES_EACH_ROW + const row = Math.floor(index / PREVIEW_IMAGES_EACH_ROW) + return { + x: column * PREVIEW_IMAGES_WIDTH, + y: row * PREVIEW_IMAGES_HEIGHT, + size: { + width: PREVIEW_IMAGES_WIDTH, + height: PREVIEW_IMAGES_HEIGHT + } + } + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerAudioModeCoverLayoutLand.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerAudioModeCoverLayoutLand.ets new file mode 100644 index 0000000..39fbe02 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerAudioModeCoverLayoutLand.ets @@ -0,0 +1,121 @@ +import { DependScope, MutableObserver, PLVMediaOutputMode, seconds } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { createId, parent, toCenterOf, toMiddleOf, usePadding } from '../../utils/arkts-no-everything' +import { readFileAsPixelMap } from '../ext/ImageKitExts' +import { image } from '@kit.ImageKit' + +@Component +export struct PLVMediaPlayerAudioModeCoverLayoutLand { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + @State isVisible: boolean = false + @State audioImageRotation: number = 0 + @State audioModeCoverImage: string | image.PixelMap | null = null + private observers: MutableObserver[] = [] + // + private readonly plv_media_player_audio_mode_cover_layout_background_land: string = createId() + private readonly plv_media_player_audio_mode_cover_layout_content_land: string = createId() + + // + + aboutToAppear(): void { + let rememberCoverImage: string | null = null + this.mediaViewModel.mediaInfoViewState.observe(async (viewState) => { + this.isVisible = viewState.outputMode === PLVMediaOutputMode.AUDIO_ONLY + if (rememberCoverImage !== viewState.audioModeCoverImage && viewState.audioModeCoverImage !== null) { + rememberCoverImage = viewState.audioModeCoverImage + const coverImage = viewState.audioModeCoverImage + this.audioModeCoverImage = coverImage == null ? null : coverImage.startsWith("http") ? coverImage : await readFileAsPixelMap(coverImage) + } + }).pushTo(this.observers) + } + + build() { + RelativeContainer() { + Stack() { + Rect() + .width('100%') + .height('100%') + .fill('#041938') + Shape() + .width('100%') + .height('100%') + .linearGradient({ + angle: 135, + colors: [['#333F76FC', 0], ['#1A3F76FC', 1]] + }) + } + .id(this.plv_media_player_audio_mode_cover_layout_background_land) + + Row() { + Stack() { + Image($r('app.media.plv_media_player_audio_mode_image_wrap_bg')) + .width(120) + .height(120) + + Image(this.audioModeCoverImage ?? $r('app.media.plv_media_player_audio_mode_cover_placeholder')) + .width(71) + .height(71) + .clipShape(new Circle().width(71).height(71)) + .rotate({ + angle: this.audioImageRotation + }) + .animation({ + duration: seconds(30).toMillis(), + curve: Curve.Linear, + iterations: -1 + }) + .onAppear(() => { + this.audioImageRotation = 360 + }) + } + + Column() { + Text($r('app.string.plv_media_player_ui_component_audio_mode_hint_desc_text')) + .fontColor('#FFFFFF') + .fontSize(14) + Image($r('app.media.plv_media_player_audio_mode_audio_volume_visualize_port')) + .width(228) + .height(18) + Row() { + Image($r('app.media.plv_media_player_audio_mode_switch_video_icon')) + .width(14) + .height(14) + Text($r('app.string.plv_media_player_ui_component_switch_video_hint_text')) + .fontColor('#FFFFFF') + .fontSize(14) + .margin({ + left: 4 + }) + } + .padding(usePadding({ + horizontal: 14, + vertical: 4 + })) + .backgroundColor('#4D000000') + .borderRadius(22) + .onClick(() => { + this.mediaViewModel.changeMediaOutputMode(PLVMediaOutputMode.AUDIO_VIDEO) + }) + + } + .height(120) + .justifyContent(FlexAlign.SpaceBetween) + .margin({ + left: 24 + }) + } + .id(this.plv_media_player_audio_mode_cover_layout_content_land) + .alignRules({ + center: toCenterOf(parent), + middle: toMiddleOf(parent) + }) + } + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerAudioModeCoverLayoutPort.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerAudioModeCoverLayoutPort.ets new file mode 100644 index 0000000..3094069 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerAudioModeCoverLayoutPort.ets @@ -0,0 +1,121 @@ +import { DependScope, MutableObserver, PLVMediaOutputMode, seconds } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { createId, parent, toCenterOf, toMiddleOf, usePadding } from '../../utils/arkts-no-everything' +import { image } from '@kit.ImageKit' +import { readFileAsPixelMap } from '../ext/ImageKitExts' + +@Component +export struct PLVMediaPlayerAudioModeCoverLayoutPort { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + @State isVisible: boolean = false + @State audioImageRotation: number = 0 + @State audioModeCoverImage: string | image.PixelMap | null = null + private observers: MutableObserver[] = [] + // + private readonly plv_media_player_audio_mode_cover_layout_background: string = createId() + private readonly plv_media_player_audio_mode_cover_layout_content: string = createId() + + // + + aboutToAppear(): void { + let rememberCoverImage: string | null = null + this.mediaViewModel.mediaInfoViewState.observe(async (viewState) => { + this.isVisible = viewState.outputMode === PLVMediaOutputMode.AUDIO_ONLY + if (rememberCoverImage !== viewState.audioModeCoverImage && viewState.audioModeCoverImage !== null) { + rememberCoverImage = viewState.audioModeCoverImage + const coverImage = viewState.audioModeCoverImage + this.audioModeCoverImage = coverImage == null ? null : coverImage.startsWith("http") ? coverImage : await readFileAsPixelMap(coverImage) + } + }).pushTo(this.observers) + } + + build() { + RelativeContainer() { + Stack() { + Rect() + .width('100%') + .height('100%') + .fill('#041938') + Shape() + .width('100%') + .height('100%') + .linearGradient({ + angle: 135, + colors: [['#333F76FC', 0], ['#1A3F76FC', 1]] + }) + } + .id(this.plv_media_player_audio_mode_cover_layout_background) + + Row() { + Stack() { + Image($r('app.media.plv_media_player_audio_mode_image_wrap_bg')) + .width(88) + .height(88) + + Image(this.audioModeCoverImage ?? $r('app.media.plv_media_player_audio_mode_cover_placeholder')) + .width(52) + .height(52) + .clipShape(new Circle().width(52).height(52)) + .rotate({ + angle: this.audioImageRotation + }) + .animation({ + duration: seconds(30).toMillis(), + curve: Curve.Linear, + iterations: -1 + }) + .onAppear(() => { + this.audioImageRotation = 360 + }) + } + + Column() { + Text($r('app.string.plv_media_player_ui_component_audio_mode_hint_desc_text')) + .fontColor('#FFFFFF') + .fontSize(12) + Image($r('app.media.plv_media_player_audio_mode_audio_volume_visualize_port')) + .width(178) + .height(14) + Row() { + Image($r('app.media.plv_media_player_audio_mode_switch_video_icon')) + .width(11) + .height(11) + Text($r('app.string.plv_media_player_ui_component_switch_video_hint_text')) + .fontColor('#FFFFFF') + .fontSize(12) + .margin({ + left: 4 + }) + } + .padding(usePadding({ + horizontal: 14, + vertical: 4 + })) + .backgroundColor('#4D000000') + .borderRadius(20) + .onClick(() => { + this.mediaViewModel.changeMediaOutputMode(PLVMediaOutputMode.AUDIO_VIDEO) + }) + + } + .height(88) + .justifyContent(FlexAlign.SpaceBetween) + .margin({ + left: 20 + }) + } + .id(this.plv_media_player_audio_mode_cover_layout_content) + .alignRules({ + center: toCenterOf(parent), + middle: toMiddleOf(parent) + }) + } + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerAutoContinueHintLayout.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerAutoContinueHintLayout.ets new file mode 100644 index 0000000..dc386a9 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerAutoContinueHintLayout.ets @@ -0,0 +1,45 @@ +import { DependScope, MutableObserver, seconds } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { usePadding } from '../../utils/arkts-no-everything' +import { formatDuration } from '../../utils/PLVTimeUtils' + +@Component +export struct PLVMediaPlayerAutoContinueHintLayout { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + @State isVisible: boolean = false + @State autoContinueTimestamp: number = 0 + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.mediaViewModel.onAutoContinueEvent.observe((autoContinueEvent) => { + this.autoContinueTimestamp = autoContinueEvent.startPosition + this.isVisible = true + setTimeout(() => this.isVisible = false, seconds(3).toMillis()) + }).pushTo(this.observers) + } + + build() { + if (this.isVisible) { + Text() { + Span($r('app.string.plv_media_player_ui_component_auto_continue_hint_pre')) + Span(formatDuration(this.autoContinueTimestamp)) + .fontColor('#3F76FC') + Span($r('app.string.plv_media_player_ui_component_auto_continue_hint_post')) + } + .fontSize(12) + .fontColor('#FFFFFF') + .backgroundColor('#99000000') + .borderRadius(8) + .padding(usePadding({ + vertical: 7, + horizontal: 16 + })) + } + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerAuxiliaryCountDownTextView.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerAuxiliaryCountDownTextView.ets new file mode 100644 index 0000000..6375095 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerAuxiliaryCountDownTextView.ets @@ -0,0 +1,35 @@ +import { DependScope, MutableObserver, PLVMediaPlayStage } from '@polyvharmony/media-player-sdk' +import { PLVMPAuxiliaryViewModel } from '../../modules/auxiliary/viewmodel/PLVMPAuxiliaryViewModel' + +@Component +export struct PLVMediaPlayerAuxiliaryCountDownTextView { + @Consume dependScope: DependScope + private auxiliaryViewModel: PLVMPAuxiliaryViewModel = this.dependScope.get(PLVMPAuxiliaryViewModel) + @State isVisible: boolean = false + @State timeLeftInSeconds: number = 0 + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.auxiliaryViewModel.auxiliaryPlayViewState.observe((viewState) => { + this.timeLeftInSeconds = viewState.timeLeftInSeconds + }).pushTo(this.observers) + + this.auxiliaryViewModel.auxiliaryInfoViewState.observe((viewState) => { + this.timeLeftInSeconds = viewState?.showDuration.toSeconds() ?? 0 + this.isVisible = viewState !== null + && [PLVMediaPlayStage.HEAD_ADVERT, PLVMediaPlayStage.TAIL_ADVERT].includes(viewState.stage) + }).pushTo(this.observers) + } + + build() { + Text($r('app.string.plv_media_player_ui_component_auxiliary_time_left_text', this.timeLeftInSeconds.toString())) + .fontSize(14) + .fontColor('#FFFFFF') + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerBackImageView.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerBackImageView.ets new file mode 100644 index 0000000..967d521 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerBackImageView.ets @@ -0,0 +1,44 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { PLVMPPageControlViewModel } from '../../modules/pagecontrol/viewmodel/PLVMPPageControlViewModel' +import { isLandscape, isPortrait } from '../../utils/PLVDisplayUtils' +import { PLVOrientationManager } from '../../utils/PLVOrientationManager' + +@Component +export struct PLVMediaPlayerBackImageView { + @Consume dependScope: DependScope + @Consume pageDependScope: DependScope + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + private pageControlViewModel: PLVMPPageControlViewModel = this.pageDependScope.get(PLVMPPageControlViewModel) + @State isVisible: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + let visible = viewState.controllerVisible + && !(viewState.isFloatActionLayoutVisible() && isLandscape()) + && !viewState.progressSliderDragging + && !viewState.controllerLocking + visible = visible || viewState.isMediaStopOverlayVisible() + this.isVisible = visible + }).pushTo(this.observers) + } + + build() { + Image($r('app.media.plv_media_player_back_icon')) + .onClick(() => { + if (isPortrait()) { + const navPathStack: NavPathStack | undefined = this.pageControlViewModel.navPathStack + navPathStack?.pop() + } else { + PLVOrientationManager.getInstance().requestOrientation('port'); + } + }) + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerBitRateSelectLayoutLand.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerBitRateSelectLayoutLand.ets new file mode 100644 index 0000000..2b56f79 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerBitRateSelectLayoutLand.ets @@ -0,0 +1,102 @@ +import { DependScope, extendArray, MutableObserver, PLVMediaBitRate } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { createId, parent, toCenterOf, toEndOf, toTopOf, usePadding } from '../../utils/arkts-no-everything' + +@Component +export struct PLVMediaPlayerBitRateSelectLayoutLand { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + @State currentBitRate: PLVMediaBitRate | null = null + @State supportBitRates: PLVMediaBitRate[] = [] + private observers: MutableObserver[] = [] + // + private readonly plv_media_player_bit_rate_select_layout_close_layout_mask_land: string = createId() + private readonly plv_media_player_bit_rate_select_content: string = createId() + private readonly plv_media_player_bit_rate_select_close_iv_land: string = createId() + + // + + aboutToAppear(): void { + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isVisible = extendArray(viewState.floatActionLayouts).lastOrNull_ext() === 'bitRate' + }).pushTo(this.observers) + + this.mediaViewModel.mediaInfoViewState.observe((viewState) => { + this.currentBitRate = viewState.bitRate + this.supportBitRates = viewState.supportBitRates + }) + } + + build() { + RelativeContainer() { + Column() { + } + .id(this.plv_media_player_bit_rate_select_layout_close_layout_mask_land) + .width('100%') + .height('100%') + .linearGradient({ + angle: 90, + colors: [['#00000000', 0], ['#cc000000', 0.5], ['#cc000000', 1]] + }) + .responseRegion({ + width: '50%' + }) + .onClick(() => { + this.controllerViewModel.popFloatActionLayout() + }) + + Column() { + ForEach(this.supportBitRates, (bitRate: PLVMediaBitRate) => { + Text(bitRate.name) + .fontSize(14) + .fontColor(this.currentBitRate?.name === bitRate.name ? '#3F76FC' : '#FFFFFF') + .margin(usePadding({ + vertical: 24 + })) + .onClick(() => { + if (this.currentBitRate?.name !== bitRate.name) { + this.mediaViewModel.changeBitRate(bitRate) + this.controllerViewModel.popFloatActionLayout() + } + }) + }, (bitRate: PLVMediaBitRate) => bitRate.name) + } + .id(this.plv_media_player_bit_rate_select_content) + .alignRules({ + center: toCenterOf(parent), + right: toEndOf(parent) + }) + .margin({ + right: 167 + }) + + Image($r('app.media.plv_media_player_float_menu_close_icon')) + .id(this.plv_media_player_bit_rate_select_close_iv_land) + .width(48) + .height(48) + .padding(12) + .margin({ + top: 8, + right: 8 + }) + .alignRules({ + top: toTopOf(parent), + right: toEndOf(parent) + }) + .fillColor('#99FFFFFF') + .onClick(() => { + this.controllerViewModel.popFloatActionLayout() + }) + + } + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerBitRateTextView.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerBitRateTextView.ets new file mode 100644 index 0000000..a15e37a --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerBitRateTextView.ets @@ -0,0 +1,58 @@ +import { DependScope, MutableObserver, PLVMediaBitRate, PLVMediaOutputMode } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { + PLVMPMediaControllerViewState +} from '../../modules/mediacontroller/viewmodel/viewstate/PLVMPMediaControllerViewState' +import { isLandscape } from '../../utils/PLVDisplayUtils' + +@Component +export struct PLVMediaPlayerBitRateTextView { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + @State currentBitRate: PLVMediaBitRate | null = null + private outputMode: PLVMediaOutputMode = PLVMediaOutputMode.AUDIO_VIDEO + private controllerViewState: PLVMPMediaControllerViewState | null = null + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.mediaViewModel.mediaInfoViewState.observe((viewState) => { + this.currentBitRate = viewState.bitRate + this.outputMode = viewState.outputMode + this.updateVisible() + }).pushTo(this.observers) + + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.controllerViewState = viewState + this.updateVisible() + }).pushTo(this.observers) + } + + build() { + Text(this.currentBitRate?.name ?? "") + .fontSize(14) + .fontColor('#FFFFFF') + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + .onClick(() => { + this.controllerViewModel.pushFloatActionLayout('bitRate') + }) + } + + private updateVisible() { + this.isVisible = this.controllerViewState !== null + && this.controllerViewState.controllerVisible + && !this.controllerViewState.isMediaStopOverlayVisible() + && !this.controllerViewState.progressSliderDragging + && !this.controllerViewState.controllerLocking + && !(this.controllerViewState.isFloatActionLayoutVisible() && isLandscape()) + && this.currentBitRate !== null + && this.outputMode !== PLVMediaOutputMode.AUDIO_ONLY + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerBrightnessVolumeUpdateHintLayout.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerBrightnessVolumeUpdateHintLayout.ets new file mode 100644 index 0000000..4b434db --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerBrightnessVolumeUpdateHintLayout.ets @@ -0,0 +1,79 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { usePadding } from '../../utils/arkts-no-everything' + +@Component +export struct PLVMediaPlayerBrightnessVolumeUpdateHintLayout { + @Consume dependScope: DependScope + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + @State currentBrightness: number = 50 + @State currentVolume: number = 100 + @State updateType: 'brightness' | 'volume' = 'brightness' + private hideTimeoutId: number | null = null + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.controllerViewModel.brightnessUpdateEvent.observe((brightness) => { + this.update('brightness', brightness * 100) + }).pushTo(this.observers) + + this.controllerViewModel.volumeUpdateEvent.observe((volume) => { + this.update('volume', volume) + }).pushTo(this.observers) + } + + build() { + Row() { + Image(this.updateType === 'brightness' ? $r('app.media.plv_media_player_brightness_hint_icon') : $r('app.media.plv_media_player_volume_hint_icon')) + .width(24) + .height(24) + + Progress({ + value: this.updateType === 'brightness' ? this.currentBrightness : this.currentVolume, + total: 100, + type: ProgressType.Linear + }) + .width(100) + .height(2) + .borderRadius(1) + .color('#3F76FC') + .backgroundColor('#66FFFFFF') + .margin({ + left: 12, + right: 8 + }) + } + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + .padding(usePadding({ + horizontal: 16, + vertical: 8 + })) + .backgroundColor('#99000000') + .borderRadius(20) + } + + private update(type: 'brightness' | 'volume', value: number) { + this.updateType = type + if (type === 'brightness') { + this.currentBrightness = value + } else { + this.currentVolume = value + } + + this.isVisible = true + if (this.hideTimeoutId) { + clearTimeout(this.hideTimeoutId) + } + this.hideTimeoutId = setTimeout(() => this.isVisible = false, 2000) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + if (this.hideTimeoutId) { + clearTimeout(this.hideTimeoutId) + } + this.hideTimeoutId = null + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerBufferingSpeedLayout.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerBufferingSpeedLayout.ets new file mode 100644 index 0000000..fa4e81b --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerBufferingSpeedLayout.ets @@ -0,0 +1,83 @@ +import { + createDebounce, + Debounce, + delay, + DependScope, + MutableObserver, + PLVMediaPlayerState +} from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' + +@Component +export struct PLVMediaPlayerBufferingSpeedLayout { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + @State isVisible: boolean = false + @State speedText: string = "" + private isBuffering: boolean = false + private isPreparing: boolean = false + private debounce: Debounce = createDebounce({ timeout: 500 }) + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.mediaViewModel.mediaPlayViewState.observe((viewState) => { + this.isBuffering = viewState.isBuffering + this.updateVisible() + this.speedText = this.updateSpeedText(viewState.bufferingSpeed) + }).pushTo(this.observers) + + this.mediaViewModel.playerState.observe((playerState: PLVMediaPlayerState) => { + this.isPreparing = playerState === PLVMediaPlayerState.STATE_PREPARING || playerState === PLVMediaPlayerState.STATE_PREPARED + this.updateVisible() + }).pushTo(this.observers) + } + + build() { + if (this.isVisible) { + Stack() { + Column() { + Text(this.speedText) + .fontSize(14) + .fontColor('#FFFFFF') + Text($r('app.string.plv_media_player_ui_component_buffering_speed_hint_text')) + .fontSize(14) + .fontColor('#FFFFFF') + .margin({ + top: 12 + }) + } + } + .width('100%') + .height('100%') + .backgroundColor('#33000000') + } + } + + private async updateVisible() { + if (!this.isBuffering && !this.isPreparing) { + this.isVisible = false + return + } + if (!this.isVisible) { + await delay(500) + } + this.debounce.run(() => { + this.isVisible = this.isBuffering || this.isPreparing + }) + } + + private updateSpeedText(speed: number): string { + if (speed < 1 << 10) { + return `${speed.toFixed(0)}B/S` + } else if (speed < 1 << 20) { + return `${(speed / (1 << 10)).toFixed(1)}KB/S` + } else { + return `${(speed / (1 << 20)).toFixed(1)}MB/S` + } + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerControllerGradientMaskLayout.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerControllerGradientMaskLayout.ets new file mode 100644 index 0000000..22fe7bf --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerControllerGradientMaskLayout.ets @@ -0,0 +1,62 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { createId, parent, toBottomOf, toTopOf } from '../../utils/arkts-no-everything' +import { isLandscape } from '../../utils/PLVDisplayUtils' + +@Component +export struct PLVMediaPlayerControllerGradientMaskLayout { + maskHeight: number = 100 + @Consume dependScope: DependScope + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + private observers: MutableObserver[] = [] + // + private readonly plv_media_player_controller_gradient_mask_top: string = createId() + private readonly plv_media_player_controller_gradient_mask_bottom: string = createId() + + // + + aboutToAppear(): void { + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + let visible = viewState.controllerVisible + && !viewState.isMediaStopOverlayVisible() + && !(viewState.isFloatActionLayoutVisible() && isLandscape()) + && !viewState.controllerLocking + this.isVisible = visible + }).pushTo(this.observers) + } + + build() { + RelativeContainer() { + Shape() + .id(this.plv_media_player_controller_gradient_mask_top) + .width('100%') + .height(this.maskHeight) + .linearGradient({ + direction: GradientDirection.Bottom, + colors: [['#66000000', 0], ['#00000000', 1]] + }) + .alignRules({ + top: toTopOf(parent) + }) + + Shape() + .id(this.plv_media_player_controller_gradient_mask_bottom) + .width('100%') + .height(this.maskHeight) + .linearGradient({ + direction: GradientDirection.Top, + colors: [['#66000000', 0], ['#00000000', 1]] + }) + .alignRules({ + bottom: toBottomOf(parent) + }) + } + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerGestureHandleLayout.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerGestureHandleLayout.ets new file mode 100644 index 0000000..004b71f --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerGestureHandleLayout.ets @@ -0,0 +1,237 @@ +import { DependScope, extendNumber, minutes, MutableObserver, repeat } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaPlayViewState } from '../../modules/media/viewmodel/viewstate/PLVMPMediaPlayViewState' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { + PLVMPMediaControllerViewState +} from '../../modules/mediacontroller/viewmodel/viewstate/PLVMPMediaControllerViewState' +import { getDisplayWindowWidth } from '../../utils/PLVDisplayUtils' + +@Component +export struct PLVMediaPlayerGestureHandleLayout { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + private mediaPlayViewState: PLVMPMediaPlayViewState | undefined = this.mediaViewModel.mediaPlayViewState.value + private controllerViewState: PLVMPMediaControllerViewState | undefined = this.controllerViewModel.mediaControllerViewState.value + private countClicksInDuration: number = 0 + private countClicksTimeoutId: number | null = null + private readonly horizontalPanGestureState: PanGestureState = new PanGestureState() + private readonly verticalPanGestureState: PanGestureState = new PanGestureState() + private videoProgressOnStartDrag: number = 0 + private videoDuration: number = 0 + private observers: MutableObserver[] = [] + // 单击切换显示隐藏始终响应,其他事件的响应跟随状态 + private isHandleGesture: boolean = false + + aboutToAppear() { + this.mediaViewModel.mediaPlayViewState.observe((viewState) => { + this.mediaPlayViewState = viewState + this.updateIsHandleGesture() + }).pushTo(this.observers) + + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.controllerViewState = viewState + this.updateIsHandleGesture() + }).pushTo(this.observers) + } + + build() { + Column() { + } + .width('100%') + .height('100%') + .gesture( + GestureGroup(GestureMode.Exclusive, + TapGesture({ count: 1 }) + .onAction(() => { + this.countClicksInDuration++ + if (this.countClicksTimeoutId !== null) { + clearTimeout(this.countClicksTimeoutId) + } + this.countClicksTimeoutId = setTimeout((): void => this.handleClicks(), 200) + }), + LongPressGesture() + .onAction((event: GestureEvent) => { + this.controllerViewModel.handleLongPressSpeeding('start') + }) + .onActionEnd((event: GestureEvent) => { + this.controllerViewModel.handleLongPressSpeeding('end') + }) + .onActionCancel(() => { + this.controllerViewModel.handleLongPressSpeeding('end') + }), + PanGesture({ + direction: PanDirection.Horizontal + }) + .onActionStart((event: GestureEvent) => { + this.handleHorizontalPanGesture(event, 'start') + }) + .onActionUpdate((event: GestureEvent) => { + this.handleHorizontalPanGesture(event, 'update') + }) + .onActionEnd((event: GestureEvent) => { + this.handleHorizontalPanGesture(event, 'end') + }) + .onActionCancel(() => { + this.handleHorizontalPanGesture(null, 'end') + }), + PanGesture({ + direction: PanDirection.Vertical + }) + .onActionStart((event: GestureEvent) => { + this.handleVerticalPanGesture(event, 'start') + }) + .onActionUpdate((event: GestureEvent) => { + this.handleVerticalPanGesture(event, 'update') + }) + .onActionEnd((event: GestureEvent) => { + this.handleVerticalPanGesture(event, 'end') + }) + .onActionCancel(() => { + this.handleVerticalPanGesture(null, 'end') + }) + ) + ) + } + + private handleClicks() { + if (this.countClicksInDuration > 1) { + this.onDoubleClick() + } else { + this.onSingleClick() + } + this.countClicksInDuration = 0 + if (this.countClicksTimeoutId !== null) { + clearTimeout(this.countClicksTimeoutId) + } + this.countClicksTimeoutId = null + } + + private onSingleClick() { + this.controllerViewModel.changeControllerVisible() + } + + private onDoubleClick() { + if (!this.isHandleGesture) { + return + } + if (this.mediaViewModel.mediaPlayViewState.value?.isPlaying) { + this.mediaViewModel.pause() + } else { + this.mediaViewModel.start() + } + } + + private handleHorizontalPanGesture(event: GestureEvent | null, type: 'start' | 'update' | 'end') { + if (!this.isHandleGesture) { + return + } + const state = this.horizontalPanGestureState + switch (type) { + case 'start': + if (event === null) { + return; + } + state.startX = event.fingerList[0]?.globalX ?? null + this.videoProgressOnStartDrag = this.mediaViewModel.mediaPlayViewState.value?.currentProgress ?? 0 + this.videoDuration = this.mediaViewModel.mediaPlayViewState.value?.duration ?? 0 + this.controllerViewModel.handleDragGestureSeek('update', this.videoProgressOnStartDrag) + break; + case 'update': + if (event === null || state.startX === null) { + return; + } + const dx = event.offsetX + const percent = dx / getDisplayWindowWidth().vp + const dprogress = percent * Math.min(this.videoDuration, minutes(3).toMillis()) + const targetProgress = extendNumber(this.videoProgressOnStartDrag + dprogress) + .coerceIn_ext(0, this.videoDuration) + this.controllerViewModel.handleDragGestureSeek('update', targetProgress) + break; + case 'end': + state.startX = null + state.startY = null + state.lastConsumedX = 0 + state.lastConsumedY = 0 + state.lastX = 0 + state.lastY = 0 + this.controllerViewModel.handleDragGestureSeek('end', 0) + break; + } + } + + private handleVerticalPanGesture(event: GestureEvent | null, type: 'start' | 'update' | 'end') { + if (!this.isHandleGesture) { + return + } + const state = this.verticalPanGestureState + switch (type) { + case 'start': + if (event === null) { + break; + } + state.startX = event.fingerList[0]?.globalX ?? null + state.startY = event.fingerList[0]?.globalY ?? null + state.lastY = state.startY ?? 0 + state.lastConsumedY = state.lastY + break; + case 'update': + if (event === null || state.startX === null || state.startY === null) { + return; + } + const sensitivity = 5 + state.lastY = state.startY + event.offsetY + const dy = state.lastY - state.lastConsumedY + if (Math.abs(dy) < sensitivity) { + break; + } + const consumeCount = Math.floor(Math.abs(dy) / sensitivity) + const direction = dy / Math.abs(dy) + repeat(consumeCount, () => { + if (state.isStartAtLeftSide()) { + this.controllerViewModel.changeBrightness(direction > 0 ? 'down' : 'up') + } else { + this.controllerViewModel.changeVolume(direction > 0 ? 'down' : 'up') + } + }) + state.lastConsumedY = state.lastConsumedY + consumeCount * sensitivity * direction + break; + case 'end': + state.startX = null + state.startY = null + state.lastConsumedX = 0 + state.lastConsumedY = 0 + state.lastX = 0 + state.lastY = 0 + break; + } + } + + private updateIsHandleGesture() { + this.isHandleGesture = this.mediaPlayViewState !== undefined + && this.controllerViewState !== undefined + && !this.controllerViewState.controllerLocking + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} + +class PanGestureState { + startX: number | null = null + startY: number | null = null + lastConsumedX: number = 0 + lastConsumedY: number = 0 + lastX: number = 0 + lastY: number = 0 + + isStartAtLeftSide(): boolean | undefined { + if (this.startX === null) { + return undefined + } + return this.startX < getDisplayWindowWidth().vp / 2 + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerHandleOnEnterBackgroundComponent.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerHandleOnEnterBackgroundComponent.ets new file mode 100644 index 0000000..5b6d19c --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerHandleOnEnterBackgroundComponent.ets @@ -0,0 +1,70 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import common from '@ohos.app.ability.common' +import window from '@ohos.window' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' + +/** + * 进入后台是否自动暂停播放 + */ +const AUTO_PAUSE_ON_BACKGROUND = true + +@Component +export struct PLVMediaPlayerHandleOnEnterBackgroundComponent { + @Consume dependScope: DependScope + private context = getContext(this) as common.UIAbilityContext + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + private isPausedByPageHide = false + private windowEventObserver?: (event: window.WindowEventType) => void = undefined + private observers: MutableObserver[] = [] + + async aboutToAppear(): Promise { + const lastWindow = await window.getLastWindow(this.context) + this.windowEventObserver = (event: window.WindowEventType) => { + if (event === window.WindowEventType.WINDOW_SHOWN) { + this.onResumeFromBackground() + } + if (event === window.WindowEventType.WINDOW_HIDDEN) { + this.onEnterBackground() + } + } + lastWindow.on('windowEvent', this.windowEventObserver) + } + + build() { + } + + private onEnterBackground() { + if (this.isHandleAutoPause()) { + this.isPausedByPageHide ||= this.mediaViewModel.mediaPlayViewState.value?.isPlaying === true + if (this.isPausedByPageHide) { + this.mediaViewModel.pause() + } + } + } + + private onResumeFromBackground() { + if (this.isHandleAutoPause()) { + if (this.isPausedByPageHide) { + this.mediaViewModel.start() + } + this.isPausedByPageHide = false + } + } + + private isHandleAutoPause(): boolean { + if (!AUTO_PAUSE_ON_BACKGROUND) { + return false; + } + return true; + } + + async aboutToDisappear(): Promise { + const lastWindow = await window.getLastWindow(this.context) + lastWindow.off('windowEvent', this.windowEventObserver) + + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerLockControllerImageView.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerLockControllerImageView.ets new file mode 100644 index 0000000..28d5f8d --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerLockControllerImageView.ets @@ -0,0 +1,44 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { isLandscape } from '../../utils/PLVDisplayUtils' + +@Component +export struct PLVMediaPlayerLockControllerImageView { + @Consume dependScope: DependScope + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + @State isLocking: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isVisible = viewState.controllerVisible + && !viewState.isMediaStopOverlayVisible() + && !viewState.progressSliderDragging + && !(viewState.isFloatActionLayoutVisible() && isLandscape()) + }).pushTo(this.observers) + + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isLocking = viewState.controllerLocking + }).pushTo(this.observers) + } + + build() { + Image(this.isLocking ? $r('app.media.plv_media_player_lock_orientation_icon_locking') : $r('app.media.plv_media_player_lock_orientation_icon_no_lock')) + .width(40) + .height(40) + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + .onClick(() => { + if (this.isLocking) { + this.controllerViewModel.lockMediaController('unlock') + } else { + this.controllerViewModel.lockMediaController('lock') + } + }) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerLongPressSpeedHintLayout.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerLongPressSpeedHintLayout.ets new file mode 100644 index 0000000..dba9aec --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerLongPressSpeedHintLayout.ets @@ -0,0 +1,54 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' + +@Component +export struct PLVMediaPlayerLongPressSpeedHintLayout { + @Consume dependScope: DependScope + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isVisible = viewState.longPressSpeeding + }).pushTo(this.observers) + } + + build() { + Row() { + Text() { + Span('2x') + .fontSize(20) + Span(' ') + .fontSize(10) + Span($r('app.string.plv_media_player_ui_component_long_press_speed_control_hint_text')) + .fontSize(14) + } + .fontColor('#FFFFFF') + .margin({ + top: 5, + bottom: 9 + }) + + Image($r("app.media.plv_media_player_long_press_speed_control_anim")) + .width(28) + .height(28) + .margin({ + top: 7, + bottom: 5 + }) + } + .backgroundColor('#99000000') + .borderRadius(20) + .padding({ + left: 16, + right: 12 + }) + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMarqueeLayout.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMarqueeLayout.ets new file mode 100644 index 0000000..c3fac1c --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMarqueeLayout.ets @@ -0,0 +1,62 @@ +import { DependScope, MutableObserver, seconds } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMarqueeAnimateSettingVO, PLVMarqueeAnimateType } from './marquee/model/PLVMarqueeAnimateSettingVO' +import { PLVMarqueeModel, PLVMarqueePlayingState } from './marquee/model/PLVMarqueeModel' +import { PLVMarqueeTextSettingVO } from './marquee/model/PLVMarqueeTextSettingVO' +import { PLVMarqueeView } from './marquee/PLVMarqueeView' + +@Component +export struct PLVMediaPlayerMarqueeLayout { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private marqueeModel: PLVMarqueeModel = this.createMarqueeModel() + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.mediaViewModel.mediaPlayViewState.observe((viewState) => { + this.marqueeModel.setPlayingState(viewState.isPlaying ? PLVMarqueePlayingState.PLAY : PLVMarqueePlayingState.PAUSE) + }).pushTo(this.observers) + } + + private createMarqueeModel(): PLVMarqueeModel { + return new PLVMarqueeModel( + new PLVMarqueeAnimateSettingVO() + .setAnimateType(PLVMarqueeAnimateType.ROLL_DOUBLE_MARQUEE) + .setRollTime(seconds(20)) + .setRollInterval(seconds(3)) + .setTweenTime(seconds(2)) + .setTweenInterval(seconds(2)) + .setHiddenWhenPause(false) + .setAlwaysShowWhenRun(false), + new PLVMarqueeTextSettingVO() + .setContent("播放器跑马灯 MediaPlayerMarquee") + .setFontColor('#FF0000') + .setFontSize(40) + .setShadow(true) + .setShadowColor('#000000') + .setShadowOffsetX(2) + .setShadowOffsetY(2) + .setShadowRadius(4), + new PLVMarqueeTextSettingVO() + .setContent("MediaPlayerMarquee") + .setFontColor('#05000000') + .setFontSize(24) + .setShadow(true) + .setShadowColor('#05000000') + .setShadowOffsetX(2) + .setShadowOffsetY(2) + .setShadowRadius(4) + ) + } + + build() { + PLVMarqueeView({ + model: this.marqueeModel + }) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreActionImageView.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreActionImageView.ets new file mode 100644 index 0000000..5bba34a --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreActionImageView.ets @@ -0,0 +1,36 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { isLandscape } from '../../utils/PLVDisplayUtils' + +@Component +export struct PLVMediaPlayerMoreActionImageView { + @Consume dependScope: DependScope + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + let visible = viewState.controllerVisible + && !viewState.isMediaStopOverlayVisible() + && !viewState.progressSliderDragging + && !viewState.controllerLocking + && !(viewState.isFloatActionLayoutVisible() && isLandscape()) + visible = visible || viewState.isMediaStopOverlayVisible() + this.isVisible = visible + }).pushTo(this.observers) + } + + build() { + Image($r('app.media.plv_media_player_more_action_icon')) + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + .onClick(() => { + this.controllerViewModel.pushFloatActionLayout('more') + }) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutAudioModeActionView.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutAudioModeActionView.ets new file mode 100644 index 0000000..0032f3b --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutAudioModeActionView.ets @@ -0,0 +1,49 @@ +import { DependScope, MutableObserver, PLVMediaOutputMode } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' + +@Component +export struct PLVMediaPlayerMoreLayoutAudioModeActionView { + @Consume dependScope: DependScope + iconResourceActive: Resource = $r("app.media.plv_media_player_more_audio_mode_action_icon_active") + iconResourceInactive: Resource = $r("app.media.plv_media_player_more_audio_mode_action_icon_inactive_port") + textFontColorActive: string = '#CC3F76FC' + textFontColorInactive: string = '#CC333333' + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controlViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + @State isCurrentAudioOnly: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.mediaViewModel.mediaInfoViewState.observe((viewState) => { + this.isVisible = viewState.supportOutputModes.includes(PLVMediaOutputMode.AUDIO_ONLY) + this.isCurrentAudioOnly = viewState.outputMode === PLVMediaOutputMode.AUDIO_ONLY + }).pushTo(this.observers) + } + + build() { + Column() { + Image(this.isCurrentAudioOnly ? this.iconResourceActive : this.iconResourceInactive) + .width(36) + .height(36) + + Text($r('app.string.plv_media_player_ui_component_audio_mode_hint_text')) + .margin({ + top: 4 + }) + .fontColor(this.isCurrentAudioOnly ? this.textFontColorActive : this.textFontColorInactive) + .fontSize(12) + } + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + .onClick(() => { + this.mediaViewModel.changeMediaOutputMode(this.isCurrentAudioOnly ? PLVMediaOutputMode.AUDIO_VIDEO : PLVMediaOutputMode.AUDIO_ONLY) + this.controlViewModel.popFloatActionLayout() + }) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutDownloadActionView.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutDownloadActionView.ets new file mode 100644 index 0000000..f35f474 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutDownloadActionView.ets @@ -0,0 +1,141 @@ +import { DependScope, MutableObserver, PLVMediaPlayerAppContext } from '@polyvharmony/media-player-sdk' +import { + PLVMediaDownloadStatus, + PLVMediaDownloadStatusCompleted, + PLVMediaDownloadStatusDownloading, + PLVMediaDownloadStatusError, + PLVMediaDownloadStatusNotStarted, + PLVMediaDownloadStatusPaused, + PLVMediaDownloadStatusWaiting +} from '@polyvharmony/media-player-sdk-addon-cache-down' +import { PLVMPDownloadItemViewModel } from '../../modules/download/single/viewmodel/PLVMPDownloadItemViewModel' +import { PLVMPPageControlViewModel } from '../../modules/pagecontrol/viewmodel/PLVMPPageControlViewModel' +import { PLVMediaPlayerDownloadCenterPageParam, PLVMediaPlayerScenes } from '../enums/PLVMediaPlayerScenes' + +@Component +export struct PLVMediaPlayerMoreLayoutDownloadActionView { + @Consume dependScope: DependScope + @Consume pageDependScope: DependScope + iconResource: Resource = $r("app.media.plv_media_player_more_download_action_icon_port") + textFontColor: string = '#FFFFFF' + private readonly downloadViewModel = this.dependScope.get(PLVMPDownloadItemViewModel) + private readonly pageControlViewModel = this.pageDependScope.get(PLVMPPageControlViewModel) + @State downloadStatusText: string = PLVMediaPlayerAppContext.getString($r("app.string.plv_media_player_ui_component_download_text")) + @State isShowDownloadProgress: boolean = false + @State downloadProgress: number = 0 + @State downloadProgressText: string = "0%" + @State isVisible: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.downloadViewModel.downloadItem.observe((viewState) => { + if (viewState === null) { + return + } + this.downloadStatusText = downloadStatusText(viewState.status) + this.isShowDownloadProgress = viewState.status instanceof PLVMediaDownloadStatusDownloading + || viewState.status instanceof PLVMediaDownloadStatusWaiting; + this.downloadProgress = Math.floor(viewState.progress * 100) + this.downloadProgressText = `${this.downloadProgress}%` + this.isVisible = viewState.isVisible + }).pushTo(this.observers) + } + + build() { + Column() { + if (!this.isShowDownloadProgress) { + Image(this.iconResource) + .width(36) + .height(36) + .draggable(false) + } + + if (this.isShowDownloadProgress) { + Stack() { + Progress({ + value: this.downloadProgress, + total: 100, + type: ProgressType.Ring + }) + .width(36) + .height(36) + .color('#3F76FC') + .backgroundColor('#333F76FC') + .style({ + strokeWidth: 3 + }) + + Text(this.downloadProgressText) + .fontColor('#3F76FC') + .fontSize(12) + } + } + + Text(this.downloadStatusText) + .fontColor(this.textFontColor) + .fontSize(12) + .margin({ + top: 4 + }) + } + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + .gesture( + GestureGroup(GestureMode.Exclusive, + TapGesture() + .onAction(() => { + const status = this.downloadViewModel.downloadItem.value?.status ?? PLVMediaDownloadStatusNotStarted.instance + if (status instanceof PLVMediaDownloadStatusNotStarted + || status instanceof PLVMediaDownloadStatusPaused + || status instanceof PLVMediaDownloadStatusError) { + this.downloadViewModel.startDownload() + } else { + this.gotoDownloadCenter() + } + }), + LongPressGesture() + .onAction(() => this.gotoDownloadCenter()) + ) + ) + } + + private gotoDownloadCenter() { + const navPathStack: NavPathStack | undefined = this.pageControlViewModel.navPathStack + if (navPathStack === undefined) { + return + } + const status = this.downloadViewModel.downloadItem.value?.status ?? PLVMediaDownloadStatusNotStarted.instance + let gotoDownloadingTab = false + if (status instanceof PLVMediaDownloadStatusPaused + || status instanceof PLVMediaDownloadStatusWaiting + || status instanceof PLVMediaDownloadStatusDownloading + || status instanceof PLVMediaDownloadStatusError) { + gotoDownloadingTab = true + } + navPathStack.removeByName(PLVMediaPlayerScenes.DOWNLOAD_CENTER.name) + navPathStack.pushPath(new NavPathInfo( + PLVMediaPlayerScenes.DOWNLOAD_CENTER.name, + new PLVMediaPlayerDownloadCenterPageParam(gotoDownloadingTab ? 1 : 0) + )) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} + +export function downloadStatusText(status: PLVMediaDownloadStatus): string { + if (status instanceof PLVMediaDownloadStatusPaused) { + return PLVMediaPlayerAppContext.getString($r("app.string.plv_media_player_ui_component_download_text_paused")) + } else if (status instanceof PLVMediaDownloadStatusDownloading) { + return PLVMediaPlayerAppContext.getString($r("app.string.plv_media_player_ui_component_download_text_downloading")) + } else if (status instanceof PLVMediaDownloadStatusWaiting) { + return PLVMediaPlayerAppContext.getString($r("app.string.plv_media_player_ui_component_download_text_waiting")) + } else if (status instanceof PLVMediaDownloadStatusCompleted) { + return PLVMediaPlayerAppContext.getString($r("app.string.plv_media_player_ui_component_download_text_completed")) + } else if (status instanceof PLVMediaDownloadStatusError) { + return PLVMediaPlayerAppContext.getString($r("app.string.plv_media_player_ui_component_download_text_failed")) + } else { + return PLVMediaPlayerAppContext.getString($r("app.string.plv_media_player_ui_component_download_text")) + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutLand.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutLand.ets new file mode 100644 index 0000000..2a2cf2a --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutLand.ets @@ -0,0 +1,127 @@ +import { DependScope, extendArray, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { createId, parent, toEndOf, toStartOf, toTopOf } from '../../utils/arkts-no-everything' +import { PLVMediaPlayerMoreLayoutAudioModeActionView } from './PLVMediaPlayerMoreLayoutAudioModeActionView' +import { PLVMediaPlayerMoreLayoutDownloadActionView } from './PLVMediaPlayerMoreLayoutDownloadActionView' +import { PLVMediaPlayerMoreLayoutSubtitleActionView } from './PLVMediaPlayerMoreLayoutSubtitleActionView' + +@Component +export struct PLVMediaPlayerMoreLayoutLand { + @Consume dependScope: DependScope + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + private observers: MutableObserver[] = [] + // + private readonly plv_media_player_more_action_start_guide_line: string = createId() + private readonly plv_media_player_more_action_layout_close_layout_mask: string = createId() + private readonly plv_media_player_more_action_audio_mode_action_view: string = createId() + private readonly plv_media_player_more_action_subtitle_action_view: string = createId() + private readonly plv_media_player_more_action_download_action_view: string = createId() + private readonly plv_media_player_more_action_close_iv: string = createId() + + // + + aboutToAppear(): void { + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isVisible = extendArray(viewState.floatActionLayouts).lastOrNull_ext() === 'more' + }).pushTo(this.observers) + } + + build() { + RelativeContainer() { + Column() { + } + .id(this.plv_media_player_more_action_layout_close_layout_mask) + .width('100%') + .height('100%') + .linearGradient({ + angle: 90, + colors: [['#00000000', 0], ['#cc000000', 0.5], ['#cc000000', 1]] + }) + .responseRegion({ + width: '50%' + }) + .onClick(() => { + this.controllerViewModel.popFloatActionLayout() + }) + + Shape() { + } + .id(this.plv_media_player_more_action_start_guide_line) + .width('56%') + .height('100%') + .alignRules({ + left: toStartOf(parent) + }) + .hitTestBehavior(HitTestMode.None) + + PLVMediaPlayerMoreLayoutAudioModeActionView({ + iconResourceInactive: $r('app.media.plv_media_player_more_audio_mode_action_icon_inactive_land'), + textFontColorInactive: '#CCFFFFFF' + }) + .id(this.plv_media_player_more_action_audio_mode_action_view) + .alignRules({ + left: toEndOf(this.plv_media_player_more_action_start_guide_line), + top: toTopOf(parent) + }) + .margin({ + top: 48 + }) + + PLVMediaPlayerMoreLayoutSubtitleActionView({ + iconResourceActive: $r('app.media.plv_media_player_more_subtitle_action_icon_active_land'), + iconResourceInactive: $r('app.media.plv_media_player_more_subtitle_action_icon_inactive_land'), + textFontColorActive: '#CCFFFFFF', + textFontColorInactive: '#CCFFFFFF' + }) + .id(this.plv_media_player_more_action_subtitle_action_view) + .alignRules({ + left: toEndOf(this.plv_media_player_more_action_audio_mode_action_view), + top: toTopOf(parent) + }) + .margin({ + top: 48, + left: 28 + }) + + PLVMediaPlayerMoreLayoutDownloadActionView({ + iconResource: $r("app.media.plv_media_player_more_download_action_icon_land"), + textFontColor: "#CCFFFFFF" + }) + .id(this.plv_media_player_more_action_download_action_view) + .alignRules({ + left: toEndOf(this.plv_media_player_more_action_subtitle_action_view), + top: toTopOf(parent) + }) + .margin({ + top: 48, + left: 28 + }) + + Image($r('app.media.plv_media_player_float_menu_close_icon')) + .id(this.plv_media_player_more_action_close_iv) + .width(48) + .height(48) + .padding(12) + .margin({ + top: 8, + right: 8 + }) + .alignRules({ + top: toTopOf(parent), + right: toEndOf(parent) + }) + .fillColor('#99FFFFFF') + .onClick(() => { + this.controllerViewModel.popFloatActionLayout() + }) + + } + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutPort.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutPort.ets new file mode 100644 index 0000000..e95ae86 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutPort.ets @@ -0,0 +1,170 @@ +import { + DependScope, + extendArray, + MutableObserver, + PLVMediaBitRate, + PLVMediaOutputMode +} from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { createId, parent, toBottomOf, toEndOf, toTopOf, usePadding } from '../../utils/arkts-no-everything' +import { PLVMediaPlayerMoreLayoutAudioModeActionView } from './PLVMediaPlayerMoreLayoutAudioModeActionView' +import { PLVMediaPlayerMoreLayoutDownloadActionView } from './PLVMediaPlayerMoreLayoutDownloadActionView' +import { PLVMediaPlayerMoreLayoutSubtitleActionView } from './PLVMediaPlayerMoreLayoutSubtitleActionView' + +@Component +export struct PLVMediaPlayerMoreLayoutPort { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + @State currentSpeed: number = 1 + private supportSpeeds: number[] = [0.5, 1.0, 1.5, 2.0] + @State currentBitRate: PLVMediaBitRate | null = null + @State supportBitRates: PLVMediaBitRate[] = [] + @State currentMediaOutputMode: PLVMediaOutputMode = PLVMediaOutputMode.AUDIO_VIDEO + private observers: MutableObserver[] = [] + // + private readonly plv_media_player_more_action_layout_close_layout_mask_port: string = createId() + private readonly plv_media_player_more_action_layout_container: string = createId() + private readonly plv_media_player_more_action_close_iv_port: string = createId() + + // + + aboutToAppear(): void { + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isVisible = extendArray(viewState.floatActionLayouts).lastOrNull_ext() === 'more' + }).pushTo(this.observers) + + this.mediaViewModel.mediaPlayViewState.observe((viewState) => { + this.currentSpeed = viewState.speed + }).pushTo(this.observers) + + this.mediaViewModel.mediaInfoViewState.observe((viewState) => { + this.currentBitRate = viewState.bitRate + this.supportBitRates = viewState.supportBitRates + this.currentMediaOutputMode = viewState.outputMode + }).pushTo(this.observers) + } + + build() { + RelativeContainer() { + Column() { + } + .id(this.plv_media_player_more_action_layout_close_layout_mask_port) + .width('100%') + .height('100%') + .onClick(() => { + this.controllerViewModel.popFloatActionLayout() + }) + + Column() { + Row({ space: 28 }) { + PLVMediaPlayerMoreLayoutAudioModeActionView() + PLVMediaPlayerMoreLayoutSubtitleActionView() + PLVMediaPlayerMoreLayoutDownloadActionView({ + iconResource: $r("app.media.plv_media_player_more_download_action_icon_port"), + textFontColor: "#CC333333" + }) + } + + if (this.currentBitRate !== null && this.supportBitRates.length > 0 && this.currentMediaOutputMode !== PLVMediaOutputMode.AUDIO_ONLY) { + Row() { + Text($r('app.string.plv_media_player_ui_component_bit_rate_hint_text_portrait')) + .fontColor('#CC333333') + .fontSize(12) + .margin({ + right: 35 + }) + ForEach(this.supportBitRates, (bitRate: PLVMediaBitRate) => { + Text(bitRate.name) + .width(40) + .height(17) + .fontSize(12) + .fontColor(this.currentBitRate?.name === bitRate.name ? '#3F76FC' : '#333333') + .margin({ + right: 28 + }) + .onClick(() => { + if (this.currentBitRate?.name !== bitRate.name) { + this.mediaViewModel.changeBitRate(bitRate) + this.controllerViewModel.popFloatActionLayout() + } + }) + }, (bitRate: PLVMediaBitRate) => bitRate.name) + } + .margin({ + top: 20 + }) + .alignItems(VerticalAlign.Center) + } + + Row() { + Text($r('app.string.plv_media_player_ui_component_speed_hint_text_portrait')) + .fontColor('#CC333333') + .fontSize(12) + .margin({ + right: 47 + }) + ForEach(this.supportSpeeds, (speed: number) => { + Text(`${speed.toFixed(1)}x`) + .width(40) + .height(17) + .fontSize(12) + .fontColor(this.currentSpeed === speed ? '#3F76FC' : '#333333') + .margin({ + right: 28 + }) + .onClick(() => { + this.mediaViewModel.setSpeed(speed) + this.controllerViewModel.popFloatActionLayout() + }) + }, (speed: number) => speed.toString()) + } + .margin({ + top: 20 + }) + .alignItems(VerticalAlign.Center) + + } + .id(this.plv_media_player_more_action_layout_container) + .width('100%') + .alignItems(HorizontalAlign.Start) + .padding(usePadding({ + horizontal: 24, + vertical: 48 + })) + .backgroundColor('#F7F8FA') + .borderRadius({ + topLeft: 16, + topRight: 16 + }) + .alignRules({ + bottom: toBottomOf(parent) + }) + + Image($r('app.media.plv_media_player_float_menu_close_icon')) + .id(this.plv_media_player_more_action_close_iv_port) + .width(48) + .height(48) + .padding(12) + .margin({ + top: 8, + right: 8 + }) + .alignRules({ + top: toTopOf(this.plv_media_player_more_action_layout_container), + right: toEndOf(this.plv_media_player_more_action_layout_container) + }) + .onClick(() => { + this.controllerViewModel.popFloatActionLayout() + }) + } + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutSubtitleActionView.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutSubtitleActionView.ets new file mode 100644 index 0000000..7cfe2a3 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreLayoutSubtitleActionView.ets @@ -0,0 +1,49 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' + +@Component +export struct PLVMediaPlayerMoreLayoutSubtitleActionView { + @Consume dependScope: DependScope + iconResourceActive: Resource = $r("app.media.plv_media_player_more_subtitle_action_icon_active_port") + iconResourceInactive: Resource = $r("app.media.plv_media_player_more_subtitle_action_icon_inactive_port") + textFontColorActive: string = '#CC333333' + textFontColorInactive: string = '#CC333333' + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controlViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + @State isCurrentSubtitleEnable: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.mediaViewModel.mediaInfoViewState.observe((viewState) => { + this.isVisible = viewState.supportSubtitles.length > 0 + this.isCurrentSubtitleEnable = viewState.currentSubtitle !== null && viewState.currentSubtitle.length > 0 + }).pushTo(this.observers) + } + + build() { + Column() { + Image(this.isCurrentSubtitleEnable ? this.iconResourceActive : this.iconResourceInactive) + .width(36) + .height(36) + + Text($r('app.string.plv_media_player_ui_component_subtitle_setting_text')) + .margin({ + top: 4 + }) + .fontColor(this.isCurrentSubtitleEnable ? this.textFontColorActive : this.textFontColorInactive) + .fontSize(12) + } + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + .onClick(() => { + this.controlViewModel.popFloatActionLayout() + this.controlViewModel.pushFloatActionLayout('subtitle') + }) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreSubtitleSettingLayoutLand.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreSubtitleSettingLayoutLand.ets new file mode 100644 index 0000000..31ce01d --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreSubtitleSettingLayoutLand.ets @@ -0,0 +1,202 @@ +import { DependScope, extendArray, MutableObserver, PLVMediaSubtitle } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { + createId, + parent, + toBottomOf, + toCenterOf, + toEndOf, + toStartOf, + toTopOf, + usePadding +} from '../../utils/arkts-no-everything' + +@Component +export struct PLVMediaPlayerMoreSubtitleSettingLayoutLand { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + @State isSubtitleEnable: boolean = false + @State supportSubtitles: PLVMediaSubtitle[][] = [] + @State currentSubtitle: PLVMediaSubtitle[] | null = null + private lastSelectedSubtitle: PLVMediaSubtitle[] | null = null + private observers: MutableObserver[] = [] + // + private readonly plv_media_player_more_action_start_guide_line: string = createId() + private readonly plv_media_player_more_action_layout_close_layout_mask: string = createId() + private readonly plv_media_player_more_action_close_iv: string = createId() + private readonly plv_media_player_more_action_show_subtitle_toggle_label: string = createId() + private readonly plv_media_player_more_action_show_subtitle_toggle: string = createId() + private readonly plv_media_player_more_action_subtitle_container: string = createId() + + // + + aboutToAppear(): void { + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isVisible = extendArray(viewState.floatActionLayouts).lastOrNull_ext() === 'subtitle' + && !viewState.isMediaStopOverlayVisible() + }).pushTo(this.observers) + + this.mediaViewModel.mediaInfoViewState.observe((viewState) => { + this.isSubtitleEnable = viewState.currentSubtitle != null && viewState.currentSubtitle.length > 0 + this.supportSubtitles = viewState.supportSubtitles + this.currentSubtitle = viewState.currentSubtitle + if (this.currentSubtitle != null && this.currentSubtitle.length > 0) { + this.lastSelectedSubtitle = this.currentSubtitle + } + }).pushTo(this.observers) + } + + build() { + RelativeContainer() { + Column() { + } + .id(this.plv_media_player_more_action_layout_close_layout_mask) + .width('100%') + .height('100%') + .linearGradient({ + angle: 90, + colors: [['#00000000', 0], ['#cc000000', 0.5], ['#cc000000', 1]] + }) + .responseRegion({ + width: '50%' + }) + .onClick(() => { + this.controllerViewModel.popFloatActionLayout() + }) + + Shape() { + } + .id(this.plv_media_player_more_action_start_guide_line) + .width('60%') + .height('100%') + .alignRules({ + left: toStartOf(parent) + }) + .hitTestBehavior(HitTestMode.None) + + Image($r('app.media.plv_media_player_float_menu_close_icon')) + .id(this.plv_media_player_more_action_close_iv) + .width(48) + .height(48) + .padding(12) + .margin({ + top: 8, + right: 8 + }) + .alignRules({ + top: toTopOf(parent), + right: toEndOf(parent) + }) + .fillColor('#99FFFFFF') + .onClick(() => { + this.controllerViewModel.popFloatActionLayout() + }) + + Text($r("app.string.plv_media_player_ui_component_subtitle_setting_show_switch_label")) + .id(this.plv_media_player_more_action_show_subtitle_toggle_label) + .fontSize(14) + .fontColor("#FFFFFF") + .alignRules({ + top: toTopOf(parent), + left: toEndOf(this.plv_media_player_more_action_start_guide_line) + }) + .margin({ + top: 74 + }) + + Toggle({ type: ToggleType.Switch, isOn: this.isSubtitleEnable }) + .id(this.plv_media_player_more_action_show_subtitle_toggle) + .width(38) + .height(24) + .selectedColor('#3F76FC') + .switchPointColor('#FFFFFF') + .alignRules({ + center: toCenterOf(this.plv_media_player_more_action_show_subtitle_toggle_label), + left: toEndOf(this.plv_media_player_more_action_show_subtitle_toggle_label) + }) + .margin({ + left: 20 + }) + .onChange((isOn: boolean) => { + if (isOn) { + this.mediaViewModel.setShowSubtitles(this.lastSelectedSubtitle ?? this.supportSubtitles[0] ?? []) + } else { + this.mediaViewModel.setShowSubtitles([]) + } + }) + + Scroll() { + Column() { + ForEach( + this.supportSubtitles, + (subtitle: PLVMediaSubtitle[]) => { + Column() { + Row() { + Text(this.getSubtitleShowText(subtitle)) + .fontSize(14) + .fontColor(this.equals(this.currentSubtitle, subtitle) ? "#3F76FC" : "#FFFFFF") + Blank() + } + .width('100%') + .padding(usePadding({ + vertical: 12 + })) + .onClick(() => { + this.mediaViewModel.setShowSubtitles(subtitle) + }) + } + .width('100%') + .padding(usePadding({ + vertical: 12 + })) + + }, + (subtitle: PLVMediaSubtitle[]) => { + return subtitle.map((it) => it.name).join() + } + ) + } + .visibility(this.isSubtitleEnable ? Visibility.Visible : Visibility.None) + } + .id(this.plv_media_player_more_action_subtitle_container) + .width('40%') + .height(240) + .scrollBar(BarState.Off) + .alignRules({ + top: toBottomOf(this.plv_media_player_more_action_show_subtitle_toggle_label), + left: toStartOf(this.plv_media_player_more_action_show_subtitle_toggle_label) + }) + .margin(usePadding({ + top: 24 + })) + + } + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + private getSubtitleShowText(subtitle: PLVMediaSubtitle[]): string | Resource { + if (subtitle.length == 0) { + return "" + } + if (subtitle.length == 1) { + return subtitle[0].name + } + return $r("app.string.plv_media_player_ui_component_subtitle_setting_double_subtitle_prefix", subtitle.map(it => it.name) + .join("/")) + } + + private equals(subtitle: PLVMediaSubtitle[] | null, other: PLVMediaSubtitle[] | null): boolean { + if (subtitle == null || other == null) { + return subtitle == other + } + return subtitle.length == other.length && subtitle.map(it => it.name).join() == other.map(it => it.name).join() + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreSubtitleSettingLayoutPort.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreSubtitleSettingLayoutPort.ets new file mode 100644 index 0000000..5706a11 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerMoreSubtitleSettingLayoutPort.ets @@ -0,0 +1,222 @@ +import { DependScope, extendArray, MutableObserver, PLVMediaSubtitle } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { + createId, + parent, + toBottomOf, + toCenterOf, + toEndOf, + toMiddleOf, + toStartOf, + toTopOf, + usePadding +} from '../../utils/arkts-no-everything' + +@Component +export struct PLVMediaPlayerMoreSubtitleSettingLayoutPort { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + @State isSubtitleEnable: boolean = false + @State supportSubtitles: PLVMediaSubtitle[][] = [] + @State currentSubtitle: PLVMediaSubtitle[] | null = null + private lastSelectedSubtitle: PLVMediaSubtitle[] | null = null + private observers: MutableObserver[] = [] + // + private readonly plv_media_player_more_action_layout_close_layout_mask_port: string = createId() + private readonly plv_media_player_more_action_layout_container: string = createId() + private readonly plv_media_player_more_action_back_iv_port: string = createId() + private readonly plv_media_player_more_action_subtitle_action_title: string = createId() + private readonly plv_media_player_more_action_show_subtitle_toggle_label: string = createId() + private readonly plv_media_player_more_action_show_subtitle_toggle: string = createId() + private readonly plv_media_player_more_action_subtitle_container: string = createId() + + // + + aboutToAppear(): void { + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isVisible = extendArray(viewState.floatActionLayouts).lastOrNull_ext() === 'subtitle' + && !viewState.isMediaStopOverlayVisible() + }).pushTo(this.observers) + + this.mediaViewModel.mediaInfoViewState.observe((viewState) => { + this.isSubtitleEnable = viewState.currentSubtitle != null && viewState.currentSubtitle.length > 0 + this.supportSubtitles = viewState.supportSubtitles + this.currentSubtitle = viewState.currentSubtitle + if (this.currentSubtitle != null && this.currentSubtitle.length > 0) { + this.lastSelectedSubtitle = this.currentSubtitle + } + }).pushTo(this.observers) + } + + build() { + RelativeContainer() { + Column() { + } + .id(this.plv_media_player_more_action_layout_close_layout_mask_port) + .width('100%') + .height('100%') + .onClick(() => { + this.controllerViewModel.popFloatActionLayout() + }) + + RelativeContainer() { + + Image($r("app.media.plv_media_player_back_icon")) + .id(this.plv_media_player_more_action_back_iv_port) + .width(20) + .height(20) + .fillColor('#CC000000') + .margin({ + left: 20, + top: 16 + }) + .alignRules({ + top: toTopOf(parent), + left: toStartOf(parent) + }) + .onClick(() => { + this.controllerViewModel.popFloatActionLayout() + this.controllerViewModel.pushFloatActionLayout('more') + }) + + Text($r("app.string.plv_media_player_ui_component_subtitle_setting_text")) + .id(this.plv_media_player_more_action_subtitle_action_title) + .fontSize(16) + .fontColor("#CC000000") + .alignRules({ + top: toTopOf(parent), + middle: toMiddleOf(parent) + }) + .margin({ + top: 15 + }) + + Text($r("app.string.plv_media_player_ui_component_subtitle_setting_show_switch_label")) + .id(this.plv_media_player_more_action_show_subtitle_toggle_label) + .fontSize(14) + .fontColor("#CC000000") + .alignRules({ + top: toBottomOf(this.plv_media_player_more_action_back_iv_port), + left: toStartOf(parent) + }) + .margin({ + left: 36, + top: 30 + }) + + Toggle({ type: ToggleType.Switch, isOn: this.isSubtitleEnable }) + .id(this.plv_media_player_more_action_show_subtitle_toggle) + .width(38) + .height(24) + .selectedColor('#3F76FC') + .switchPointColor('#FFFFFF') + .alignRules({ + center: toCenterOf(this.plv_media_player_more_action_show_subtitle_toggle_label), + right: toEndOf(parent) + }) + .margin({ + right: 16 + }) + .onChange((isOn: boolean) => { + if (isOn) { + this.mediaViewModel.setShowSubtitles(this.lastSelectedSubtitle ?? this.supportSubtitles[0] ?? []) + } else { + this.mediaViewModel.setShowSubtitles([]) + } + }) + + Scroll() { + Column() { + ForEach( + this.supportSubtitles, + (subtitle: PLVMediaSubtitle[]) => { + Column() { + Row() { + Text(this.getSubtitleShowText(subtitle)) + .fontSize(14) + .fontColor(this.equals(this.currentSubtitle, subtitle) ? "#3F76FC" : "#CC000000") + Blank() + } + .width('100%') + .padding(usePadding({ + vertical: 16, + horizontal: 20 + })) + .backgroundColor(this.equals(this.currentSubtitle, subtitle) ? "#1A3F76FC" : "#FFFFFF") + .borderRadius(8) + .borderWidth(1) + .borderColor(this.equals(this.currentSubtitle, subtitle) ? "#3F76FC" : "#FFFFFF") + .onClick(() => { + this.mediaViewModel.setShowSubtitles(subtitle) + }) + } + .width('100%') + .padding(usePadding({ + vertical: 4, + horizontal: 16 + })) + + }, + (subtitle: PLVMediaSubtitle[]) => { + return subtitle.map((it) => it.name).join() + } + ) + } + .width('100%') + .height('100%') + .visibility(this.isSubtitleEnable ? Visibility.Visible : Visibility.None) + } + .id(this.plv_media_player_more_action_subtitle_container) + .width('100%') + .height(318) + .scrollBar(BarState.Off) + .alignRules({ + top: toBottomOf(this.plv_media_player_more_action_show_subtitle_toggle_label) + }) + .margin(usePadding({ + top: 18 + })) + + } + .id(this.plv_media_player_more_action_layout_container) + .width('100%') + .height(422) + .backgroundColor('#F7F8FA') + .borderRadius({ + topLeft: 16, + topRight: 16 + }) + .alignRules({ + bottom: toBottomOf(parent) + }) + + } + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + private getSubtitleShowText(subtitle: PLVMediaSubtitle[]): string | Resource { + if (subtitle.length == 0) { + return "" + } + if (subtitle.length == 1) { + return subtitle[0].name + } + return $r("app.string.plv_media_player_ui_component_subtitle_setting_double_subtitle_prefix", subtitle.map(it => it.name) + .join("/")) + } + + private equals(subtitle: PLVMediaSubtitle[] | null, other: PLVMediaSubtitle[] | null): boolean { + if (subtitle == null || other == null) { + return subtitle == other + } + return subtitle.length == other.length && subtitle.map(it => it.name).join() == other.map(it => it.name).join() + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerNetworkPoorIndicateLayout.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerNetworkPoorIndicateLayout.ets new file mode 100644 index 0000000..77ffb0a --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerNetworkPoorIndicateLayout.ets @@ -0,0 +1,103 @@ +import { + DependScope, + extendArray, + MutableObserver, + PLVMediaBitRate, + PLVMediaOutputMode +} from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { usePadding } from '../../utils/arkts-no-everything' +import { isLandscape, isPortrait } from '../../utils/PLVDisplayUtils' + +@Component +export struct PLVMediaPlayerNetworkPoorIndicateLayout { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + @State alternativeBitRate: PLVMediaBitRate | null = null + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.mediaViewModel.networkPoorEvent.observe(() => { + const supportBitRates = this.mediaViewModel.mediaInfoViewState.value?.supportBitRates + const currentBitRate = this.mediaViewModel.mediaInfoViewState.value?.bitRate + const outputMode = this.mediaViewModel.mediaInfoViewState.value?.outputMode + if (!supportBitRates || !currentBitRate || outputMode === PLVMediaOutputMode.AUDIO_ONLY) { + return + } + const nextDowngradeBitRate: PLVMediaBitRate | undefined = extendArray(supportBitRates) + .filter((bitRate: PLVMediaBitRate) => bitRate.index < currentBitRate.index) + .maxBy_ext((bitRate: PLVMediaBitRate) => bitRate.index) + if (!nextDowngradeBitRate) { + return + } + this.alternativeBitRate = nextDowngradeBitRate + this.isVisible = true + }).pushTo(this.observers) + + this.mediaViewModel.onChangeBitRateEvent.observe(() => { + this.isVisible = false + }).pushTo(this.observers) + + this.mediaViewModel.onPreparedEvent.observe(() => { + this.isVisible = false + }).pushTo(this.observers) + + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + const lastFloatActionLayout = extendArray(viewState.floatActionLayouts).lastOrNull_ext() + if ((isPortrait() && lastFloatActionLayout === 'more') + || (isLandscape() && lastFloatActionLayout === 'bitRate')) { + this.isVisible = false + } + }).pushTo(this.observers) + } + + build() { + if (this.isVisible) { + Row() { + Text() { + Span($r('app.string.plv_media_player_ui_component_network_poor_hint_text')) + Span($r('app.string.plv_media_player_ui_component_network_poor_switch_bitrate_action_text_prefix')) + .fontColor('#3F76FC') + .onClick(() => { + this.mediaViewModel.changeBitRate(this.alternativeBitRate) + }) + Span(this.alternativeBitRate?.name ?? "") + .fontColor('#3F76FC') + .onClick(() => { + this.mediaViewModel.changeBitRate(this.alternativeBitRate) + }) + } + .fontColor('#FFFFFF') + .fontSize(14) + .margin({ + left: 8 + }) + + Image($r('app.media.plv_media_player_float_menu_close_icon')) + .width(16) + .height(16) + .fillColor('#CCCCCC') + .margin({ + left: 8 + }) + .onClick(() => { + this.isVisible = false + }) + } + .backgroundColor('#CC000000') + .borderRadius(8) + .padding(usePadding({ + vertical: 10, + horizontal: 8 + })) + } + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerPlayButton.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerPlayButton.ets new file mode 100644 index 0000000..30f9b26 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerPlayButton.ets @@ -0,0 +1,44 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { isLandscape } from '../../utils/PLVDisplayUtils' + +@Component +export struct PLVMediaPlayerPlayButton { + @Consume dependScope: DependScope + private viewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isPlaying: boolean = false + @State isVisible: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.viewModel.mediaPlayViewState.observe((viewState) => { + this.isPlaying = viewState.isPlaying + }).pushTo(this.observers) + + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isVisible = viewState.controllerVisible + && !viewState.isMediaStopOverlayVisible() + && !viewState.controllerLocking + && !(viewState.isFloatActionLayoutVisible() && isLandscape()) + }).pushTo(this.observers) + } + + build() { + Image(this.isPlaying ? $r('app.media.plv_media_player_play_button_icon_to_pause') : $r('app.media.plv_media_player_play_button_icon_to_play')) + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + .onClick(() => { + if (this.isPlaying) { + this.viewModel.pause() + } else { + this.viewModel.start() + } + }) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerPlayCompleteAutoRestartComponent.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerPlayCompleteAutoRestartComponent.ets new file mode 100644 index 0000000..c3412e8 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerPlayCompleteAutoRestartComponent.ets @@ -0,0 +1,24 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' + +@Component +export struct PLVMediaPlayerPlayCompleteAutoRestartComponent { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.mediaViewModel.onCompleteEvent.observe((event) => [ + this.mediaViewModel.restart() + ]).pushTo(this.observers) + } + + build() { + + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerPlayCompleteManualRestartOverlayLayout.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerPlayCompleteManualRestartOverlayLayout.ets new file mode 100644 index 0000000..6c3487f --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerPlayCompleteManualRestartOverlayLayout.ets @@ -0,0 +1,49 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' + +@Component +export struct PLVMediaPlayerPlayCompleteManualRestartOverlayLayout { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.controllerViewModel.mediaControllerViewState.observe((viewState) => [ + this.isVisible = viewState.completeOverlayLayoutVisible + ]).pushTo(this.observers) + } + + build() { + Stack() { + Stack() { + Column() { + Image($r('app.media.plv_media_player_restart_icon')) + .width(32) + .height(32) + + Text($r('app.string.plv_media_player_ui_component_complete_hint_restart_text')) + .fontSize(14) + .fontColor('#99FFFFFF') + .margin({ + top: 8 + }) + } + .onClick(() => { + this.mediaViewModel.restart() + }) + } + .width('100%') + .height('100%') + .backgroundColor('#CC000000') + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerPlayErrorOverlayLayout.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerPlayErrorOverlayLayout.ets new file mode 100644 index 0000000..e2bf73d --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerPlayErrorOverlayLayout.ets @@ -0,0 +1,63 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { usePadding } from '../../utils/arkts-no-everything' + +@Component +export struct PLVMediaPlayerPlayErrorOverlayLayout { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.controllerViewModel.mediaControllerViewState.observe((viewState) => [ + this.isVisible = viewState.errorOverlayLayoutVisible + ]).pushTo(this.observers) + } + + build() { + Stack() { + Column() { + Text($r('app.string.plv_media_player_ui_component_error_hint_text')) + .fontSize(14) + .fontColor('#FFFFFF') + + Row() { + Image($r('app.media.plv_media_player_restart_icon')) + .width(16) + .height(16) + + Text($r('app.string.plv_media_player_ui_component_error_hint_restart_text')) + .fontSize(14) + .fontColor('#FFFFFF') + .margin({ + left: 5 + }) + } + .backgroundColor('#1AFFFFFF') + .borderRadius(16) + .margin({ + top: 22 + }) + .padding(usePadding({ + horizontal: 16, + vertical: 6 + })) + .onClick(() => { + this.mediaViewModel.restart() + }) + } + } + .width('100%') + .height('100%') + .backgroundColor('#CC000000') + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerProgressTextView.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerProgressTextView.ets new file mode 100644 index 0000000..0d0fcbf --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerProgressTextView.ets @@ -0,0 +1,56 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { usePadding } from '../../utils/arkts-no-everything' +import { isLandscape } from '../../utils/PLVDisplayUtils' +import { formatDuration } from '../../utils/PLVTimeUtils' + +@Component +export struct PLVMediaPlayerProgressTextView { + @Consume dependScope: DependScope + private viewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State progress: number = 0 + @State duration: number = 0 + @State isVisible: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear() { + this.viewModel.mediaPlayViewState.observe((viewState) => { + this.progress = viewState.currentProgress + this.duration = viewState.duration + }).pushTo(this.observers) + + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isVisible = viewState.controllerVisible + && !viewState.isMediaStopOverlayVisible() + && !viewState.controllerLocking + && !(viewState.isFloatActionLayoutVisible() && isLandscape()) + }).pushTo(this.observers) + } + + build() { + Row() { + Text(`${formatDuration(this.progress)}`) + .fontColor('#FFFFFF') + .fontSize(14) + + Text("/") + .fontColor('#99FFFFFF') + .fontSize(12) + .padding(usePadding({ + horizontal: 4 + })) + + Text(`${formatDuration(this.duration)}`) + .fontColor('#99FFFFFF') + .fontSize(14) + } + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerScreenshotImageView.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerScreenshotImageView.ets new file mode 100644 index 0000000..4cbfd19 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerScreenshotImageView.ets @@ -0,0 +1,119 @@ +import { DependScope, lateInit, logger, MutableObserver, runCatching } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { isLandscape } from '../../utils/PLVDisplayUtils' +import { image } from '@kit.ImageKit' +import { photoAccessHelper } from '@kit.MediaLibraryKit' +import fs from '@ohos.file.fs' +import { promptAction } from '@kit.ArkUI' +import { any, usePadding } from '../../utils/arkts-no-everything' + +const TAG = "PLVMediaPlayerScreenshotImageView" + +@Component +export struct PLVMediaPlayerScreenshotImageView { + @Consume dependScope: DependScope + private viewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isVisible = viewState.controllerVisible + && !viewState.isMediaStopOverlayVisible() + && !viewState.controllerLocking + && !(viewState.isFloatActionLayoutVisible() && isLandscape()) + }).pushTo(this.observers) + } + + build() { + Image($r('app.media.plv_media_player_screenshot_icon')) + .width(40) + .height(40) + .padding(8) + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + .onClick(async () => { + const result = await runCatching(this.screenshot()) + if (!result.success) { + logger.error(TAG, `screenshot failed: ${any(result).error}`) + } + if (!result.success || !result.data) { + promptAction.showToast({ message: $r('app.string.plv_media_player_ui_component_screenshot_save_failed') }) + return + } + const pixelMap = result.data + this.showSaveImageDialog(pixelMap) + }) + } + + private async screenshot(): Promise { + return await this.viewModel.onScreenshot?.() ?? null + } + + private showSaveImageDialog(pixelMap: PixelMap) { + const controller = new CustomDialogController({ + builder: SaveImageDialog({ pixelMap: pixelMap }) + }) + controller.open() + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} + +@CustomDialog +struct SaveImageDialog { + controller: CustomDialogController = new CustomDialogController({ + builder: SaveImageDialog() + }) + pixelMap: PixelMap = lateInit() + + build() { + Column() { + + Text($r('app.string.plv_media_player_ui_component_screenshot_save_to_local')) + .fontSize(16) + + Image(this.pixelMap) + .width(160) + .height(160) + .margin(usePadding({ + vertical: 8 + })) + .objectFit(ImageFit.Contain) + + SaveButton({ + icon: SaveIconStyle.FULL_FILLED, + text: SaveDescription.SAVE_IMAGE + }) + .onClick(async () => { + const result = await runCatching(this.saveImage()) + if (result.success) { + promptAction.showToast({ message: $r('app.string.plv_media_player_ui_component_screenshot_save_success') }) + } else { + logger.error(TAG, `screenshot failed: ${any(result).error}`) + promptAction.showToast({ message: $r('app.string.plv_media_player_ui_component_screenshot_save_failed') }) + } + }) + } + .padding(usePadding({ + vertical: 12 + })) + } + + private async saveImage() { + const photoAccess = photoAccessHelper.getPhotoAccessHelper(getContext(this)) + const uri = await photoAccess.createAsset(photoAccessHelper.PhotoType.IMAGE, "png") + const file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE) + const packer = image.createImagePacker() + await packer.packToFile(this.pixelMap, file.fd, { + format: "image/png", + quality: 100 + }) + await packer.release() + await fs.close(file) + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSeekProgressPreviewLayout.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSeekProgressPreviewLayout.ets new file mode 100644 index 0000000..25606c2 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSeekProgressPreviewLayout.ets @@ -0,0 +1,49 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { PLVMPProgressImageViewModel } from '../../modules/progressImage/viewmodel/PLVMPProgressImageViewModel' +import { PLVMediaPlayerSeekProgressPreviewTextView } from './PLVMediaPlayerSeekProgressPreviewTextView' +import image from '@ohos.multimedia.image' + +@Component +export struct PLVMediaPlayerSeekProgressPreviewLayout { + @Consume dependScope: DependScope + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + private progressImageViewModel: PLVMPProgressImageViewModel = this.dependScope.get(PLVMPProgressImageViewModel) + @State isVisible: boolean = false + @State progressImagePixelMap: image.PixelMap | null = null + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isVisible = viewState.progressSliderDragging + if (viewState.progressSliderDragging) { + this.progressImageViewModel.updateProgressImage(viewState.progressSliderDragPosition) + } + }).pushTo(this.observers) + + this.progressImageViewModel.progressImage.observe((pixelMap: image.PixelMap | null) => { + this.progressImagePixelMap = pixelMap + }).pushTo(this.observers) + } + + build() { + Column() { + if (this.progressImagePixelMap !== null) { + Image(this.progressImagePixelMap) + .width(160) + .height(90) + .margin({ + bottom: 4 + }) + .borderRadius(8) + } + PLVMediaPlayerSeekProgressPreviewTextView() + } + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSeekProgressPreviewTextView.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSeekProgressPreviewTextView.ets new file mode 100644 index 0000000..4932bd7 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSeekProgressPreviewTextView.ets @@ -0,0 +1,57 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { usePadding } from '../../utils/arkts-no-everything' +import { formatDuration } from '../../utils/PLVTimeUtils' + +@Component +export struct PLVMediaPlayerSeekProgressPreviewTextView { + @Consume dependScope: DependScope + private viewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State progress: number = 0 + @State duration: number = 0 + @State hasProgressPreviewImage: boolean = false + @State isVisible: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear() { + this.viewModel.mediaPlayViewState.observe((viewState) => { + this.duration = viewState.duration + }).pushTo(this.observers) + + this.viewModel.mediaInfoViewState.observe((viewState) => { + this.hasProgressPreviewImage = viewState.progressPreviewImage !== null + }).pushTo(this.observers) + + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.progress = viewState.progressSliderDragPosition + this.isVisible = viewState.progressSliderDragging + }).pushTo(this.observers) + } + + build() { + Row() { + Text(`${formatDuration(this.progress)}`) + .fontColor('#FFFFFF') + .fontSize(this.hasProgressPreviewImage ? 20 : 28) + + Text("/") + .fontColor('#99FFFFFF') + .fontSize(14) + .padding(usePadding({ + horizontal: 4 + })) + + Text(`${formatDuration(this.duration)}`) + .fontColor('#99FFFFFF') + .fontSize(this.hasProgressPreviewImage ? 20 : 28) + } + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSpeedSelectLayoutLand.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSpeedSelectLayoutLand.ets new file mode 100644 index 0000000..0ccc018 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSpeedSelectLayoutLand.ets @@ -0,0 +1,99 @@ +import { DependScope, extendArray, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { createId, parent, toCenterOf, toEndOf, toTopOf, usePadding } from '../../utils/arkts-no-everything' + +@Component +export struct PLVMediaPlayerSpeedSelectLayoutLand { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + @State currentSpeed: number = 1 + private supportSpeeds: number[] = [0.5, 1.0, 1.5, 2.0] + private observers: MutableObserver[] = [] + // + private readonly plv_media_player_speed_select_layout_close_layout_mask_land: string = createId() + private readonly plv_media_player_speed_select_content: string = createId() + private readonly plv_media_player_speed_select_close_iv_land: string = createId() + + // + + aboutToAppear(): void { + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isVisible = extendArray(viewState.floatActionLayouts).lastOrNull_ext() === 'speed' + }).pushTo(this.observers) + + this.mediaViewModel.mediaPlayViewState.observe((viewState) => { + this.currentSpeed = viewState.speed + }).pushTo(this.observers) + } + + build() { + RelativeContainer() { + Column() { + } + .id(this.plv_media_player_speed_select_layout_close_layout_mask_land) + .width('100%') + .height('100%') + .linearGradient({ + angle: 90, + colors: [['#00000000', 0], ['#cc000000', 0.5], ['#cc000000', 1]] + }) + .responseRegion({ + width: '50%' + }) + .onClick(() => { + this.controllerViewModel.popFloatActionLayout() + }) + + Column() { + ForEach(this.supportSpeeds, (speed: number) => { + Text(`${speed.toFixed(1)}x`) + .fontSize(14) + .fontColor(this.currentSpeed === speed ? '#3F76FC' : '#FFFFFF') + .margin(usePadding({ + vertical: 24 + })) + .onClick(() => { + this.mediaViewModel.setSpeed(speed) + this.controllerViewModel.popFloatActionLayout() + }) + }, (speed: number) => speed.toString()) + } + .id(this.plv_media_player_speed_select_content) + .alignRules({ + center: toCenterOf(parent), + right: toEndOf(parent) + }) + .margin({ + right: 167 + }) + + Image($r('app.media.plv_media_player_float_menu_close_icon')) + .id(this.plv_media_player_speed_select_close_iv_land) + .width(48) + .height(48) + .padding(12) + .margin({ + top: 8, + right: 8 + }) + .alignRules({ + top: toTopOf(parent), + right: toEndOf(parent) + }) + .fillColor('#99FFFFFF') + .onClick(() => { + this.controllerViewModel.popFloatActionLayout() + }) + + } + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSpeedTextView.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSpeedTextView.ets new file mode 100644 index 0000000..cce5b93 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSpeedTextView.ets @@ -0,0 +1,43 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { isLandscape } from '../../utils/PLVDisplayUtils' + +@Component +export struct PLVMediaPlayerSpeedTextView { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + @State currentSpeed: number = 1.0 + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.mediaViewModel.mediaPlayViewState.observe((viewState) => { + this.currentSpeed = viewState.speed + }).pushTo(this.observers) + + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isVisible = viewState.controllerVisible + && !viewState.isMediaStopOverlayVisible() + && !viewState.progressSliderDragging + && !viewState.controllerLocking + && !(viewState.isFloatActionLayoutVisible() && isLandscape()) + }).pushTo(this.observers) + } + + build() { + Text(`${this.currentSpeed.toFixed(1)}x`) + .fontSize(14) + .fontColor('#FFFFFF') + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + .onClick(() => { + this.controllerViewModel.pushFloatActionLayout('speed') + }) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSubtitleTextLayout.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSubtitleTextLayout.ets new file mode 100644 index 0000000..2ae6d7e --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSubtitleTextLayout.ets @@ -0,0 +1,63 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' + +@Component +export struct PLVMediaPlayerSubtitleTextLayout { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + @State topSubtitleText: string | null = null + @State topSubtitleFontColor: string = "#FFFFFF" + @State topSubtitleBold: boolean = false + @State topSubtitleItalic: boolean = false + @State topSubtitleBackgroundColor: string = "#000000" + @State bottomSubtitleText: string | null = null + @State bottomSubtitleFontColor: string = "#FFFFFF" + @State bottomSubtitleBold: boolean = false + @State bottomSubtitleItalic: boolean = false + @State bottomSubtitleBackgroundColor: string = "#000000" + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.mediaViewModel.mediaInfoViewState.observe((viewState) => { + this.topSubtitleFontColor = viewState.topSubtitleTextStyle.fontColor + this.topSubtitleBold = viewState.topSubtitleTextStyle.isBold + this.topSubtitleItalic = viewState.topSubtitleTextStyle.isItalic + this.topSubtitleBackgroundColor = viewState.topSubtitleTextStyle.backgroundColor + this.bottomSubtitleFontColor = viewState.bottomSubtitleTextStyle.fontColor + this.bottomSubtitleBold = viewState.bottomSubtitleTextStyle.isBold + this.bottomSubtitleItalic = viewState.bottomSubtitleTextStyle.isItalic + this.bottomSubtitleBackgroundColor = viewState.bottomSubtitleTextStyle.backgroundColor + }).pushTo(this.observers) + + this.mediaViewModel.mediaPlayViewState.observe((viewState) => { + this.topSubtitleText = viewState.subtitleTexts[0]?.text ?? null + this.bottomSubtitleText = viewState.subtitleTexts[1]?.text ?? null + }).pushTo(this.observers) + } + + build() { + Column() { + if (this.topSubtitleText !== null) { + Text(this.topSubtitleText) + .fontColor(this.topSubtitleFontColor) + .fontWeight(this.topSubtitleBold ? FontWeight.Bold : FontWeight.Normal) + .fontStyle(this.topSubtitleItalic ? FontStyle.Italic : FontStyle.Normal) + .backgroundColor(this.topSubtitleBackgroundColor) + .textAlign(TextAlign.Center) + } + if (this.bottomSubtitleText !== null) { + Text(this.bottomSubtitleText) + .fontColor(this.bottomSubtitleFontColor) + .fontWeight(this.bottomSubtitleBold ? FontWeight.Bold : FontWeight.Normal) + .fontStyle(this.bottomSubtitleItalic ? FontStyle.Italic : FontStyle.Normal) + .backgroundColor(this.bottomSubtitleBackgroundColor) + .textAlign(TextAlign.Center) + } + } + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSwitchBitRateHintLayout.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSwitchBitRateHintLayout.ets new file mode 100644 index 0000000..f53eabf --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerSwitchBitRateHintLayout.ets @@ -0,0 +1,67 @@ +import { + DependScope, + MutableObserver, + PLVMediaBitRate, + PLVMediaPlayerOnInfoEvent, + seconds +} from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { usePadding } from '../../utils/arkts-no-everything' + +@Component +export struct PLVMediaPlayerSwitchBitRateHintLayout { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + @State isVisible: boolean = false + @State toBitRate: PLVMediaBitRate | null = null + @State switchSuccess: boolean = false + private changeVisibleTimeoutId: number | null = null + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.mediaViewModel.onChangeBitRateEvent.observe((bitRate) => { + this.toBitRate = bitRate + this.switchSuccess = false + this.showHint() + }).pushTo(this.observers) + + this.mediaViewModel.onInfoEvent.observe((onInfo: PLVMediaPlayerOnInfoEvent) => { + if (onInfo.what === PLVMediaPlayerOnInfoEvent.MEDIA_INFO_VIDEO_RENDERING_START) { + this.switchSuccess = true + this.showHint() + } + }).pushTo(this.observers) + } + + build() { + if (this.isVisible && this.toBitRate !== null) { + Text() { + Span(this.switchSuccess ? $r('app.string.plv_media_player_ui_component_switch_bit_rate_finish_text_pre') : $r('app.string.plv_media_player_ui_component_switch_bit_rate_start_text_pre')) + Span(` ${this.toBitRate.name} `) + .fontColor('#3F76FC') + Span(this.switchSuccess ? $r('app.string.plv_media_player_ui_component_switch_bit_rate_finish_text_post') : $r('app.string.plv_media_player_ui_component_switch_bit_rate_start_text_post')) + } + .fontSize(14) + .fontColor('#FFFFFF') + .backgroundColor('#CC000000') + .borderRadius(20) + .padding(usePadding({ + vertical: 8, + horizontal: 16 + })) + } + } + + private showHint() { + this.isVisible = true + if (this.changeVisibleTimeoutId !== null) { + clearTimeout(this.changeVisibleTimeoutId) + } + this.changeVisibleTimeoutId = setTimeout(() => this.isVisible = false, seconds(3).toMillis()) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/PLVMediaPlayerTitleTextView.ets b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerTitleTextView.ets new file mode 100644 index 0000000..de3bcaa --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/PLVMediaPlayerTitleTextView.ets @@ -0,0 +1,44 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaViewModel } from '../../modules/media/viewmodel/PLVMPMediaViewModel' +import { PLVMPMediaControllerViewModel } from '../../modules/mediacontroller/viewmodel/PLVMPMediaControllerViewModel' +import { isLandscape } from '../../utils/PLVDisplayUtils' + +@Component +export struct PLVMediaPlayerTitleTextView { + @Consume dependScope: DependScope + private viewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State title: string = "" + @State isVisible: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.viewModel.mediaInfoViewState.observe((viewState) => { + this.title = viewState.title + }).pushTo(this.observers) + + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + let visible = viewState.controllerVisible + && !viewState.isMediaStopOverlayVisible() + && !(viewState.isFloatActionLayoutVisible() && isLandscape()) + && !viewState.progressSliderDragging + && !viewState.controllerLocking; + this.isVisible = visible + }).pushTo(this.observers) + } + + build() { + Text(this.title) + .fontSize(16) + .fontColor('#FFFFFF') + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .maxLines(1) + .ellipsisMode(EllipsisMode.END) + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/marquee/PLVMarqueeView.ets b/polyv/src/main/ets/common/ui/component/marquee/PLVMarqueeView.ets new file mode 100644 index 0000000..5c3fdef --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/marquee/PLVMarqueeView.ets @@ -0,0 +1,59 @@ +import { MutableObserver, requireNotNull } from '@polyvharmony/media-player-sdk' +import { PLVMarqueeAnimateType } from './model/PLVMarqueeAnimateSettingVO' +import { PLVMarqueeModel, PLVMarqueePlayingState } from './model/PLVMarqueeModel' +import { PLVMarqueeFlicker15PercentView } from './view/PLVMarqueeFlicker15PercentView' +import { PLVMarqueeFlickerDoubleMarqueeView } from './view/PLVMarqueeFlickerDoubleMarqueeView' +import { PLVMarqueeFlickerView } from './view/PLVMarqueeFlickerView' +import { PLVMarqueeRoll15PercentView } from './view/PLVMarqueeRoll15PercentView' +import { PLVMarqueeRollDoubleMarqueeView } from './view/PLVMarqueeRollDoubleMarqueeView' +import { PLVMarqueeRollFlickerView } from './view/PLVMarqueeRollFlickerView' +import { PLVMarqueeRollView } from './view/PLVMarqueeRollView' + +@Component +export struct PLVMarqueeView { + model?: PLVMarqueeModel + @State isVisible: boolean = true + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + requireNotNull(this.model, () => "model is null") + + this.model?.playingState.observe((playingState) => { + if (this.model?.animateSetting.isHiddenWhenPause === true) { + this.isVisible = playingState === PLVMarqueePlayingState.PLAY + } + }).pushTo(this.observers) + } + + build() { + Stack() { + if (this.model?.animateSetting?.animateType === PLVMarqueeAnimateType.ROLL) { + PLVMarqueeRollView({ model: this.model }) + } + if (this.model?.animateSetting?.animateType === PLVMarqueeAnimateType.FLICKER) { + PLVMarqueeFlickerView({ model: this.model }) + } + if (this.model?.animateSetting?.animateType === PLVMarqueeAnimateType.ROLL_FLICKER) { + PLVMarqueeRollFlickerView({ model: this.model }) + } + if (this.model?.animateSetting?.animateType === PLVMarqueeAnimateType.ROLL_15PERCENT) { + PLVMarqueeRoll15PercentView({ model: this.model }) + } + if (this.model?.animateSetting?.animateType === PLVMarqueeAnimateType.FLICKER_15PERCENT) { + PLVMarqueeFlicker15PercentView({ model: this.model }) + } + if (this.model?.animateSetting?.animateType === PLVMarqueeAnimateType.ROLL_DOUBLE_MARQUEE) { + PLVMarqueeRollDoubleMarqueeView({ model: this.model }) + } + if (this.model?.animateSetting?.animateType === PLVMarqueeAnimateType.FLICKER_DOUBLE_MARQUEE) { + PLVMarqueeFlickerDoubleMarqueeView({ model: this.model }) + } + } + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/marquee/model/PLVMarqueeAnimateSettingVO.ts b/polyv/src/main/ets/common/ui/component/marquee/model/PLVMarqueeAnimateSettingVO.ts new file mode 100644 index 0000000..973c739 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/marquee/model/PLVMarqueeAnimateSettingVO.ts @@ -0,0 +1,72 @@ +import {Duration, seconds} from '@polyvharmony/media-player-sdk'; + +export class PLVMarqueeAnimateSettingVO { + + // 动画类型 + animateType: PLVMarqueeAnimateType = PLVMarqueeAnimateType.ROLL + // 滚动类型跑马灯完整显示一次的时长 + rollTime: Duration = seconds(0) + // 滚动类型跑马灯两次显示之间的间隔时长 + rollInterval: Duration = seconds(0) + // 闪烁类型跑马灯一次完整闪烁的时长 + tweenTime: Duration = seconds(0) + // 闪烁类型跑马灯两次显示之间的间隔时长 + tweenInterval: Duration = seconds(0) + // 暂停时是否显示跑马灯 + isHiddenWhenPause: boolean = true + // 是否在屏幕区域内显示完整的跑马灯 + isAlwaysShowWhenRun: boolean = false + + setAnimateType(animateType: PLVMarqueeAnimateType) { + this.animateType = animateType + return this + } + + setRollTime(duration: Duration) { + this.rollTime = duration + return this + } + + setRollInterval(interval: Duration) { + this.rollInterval = interval + return this + } + + setTweenTime(tweenTime: Duration) { + this.tweenTime = tweenTime + return this + } + + setTweenInterval(tweenInterval: Duration) { + this.tweenInterval = tweenInterval + return this + } + + setHiddenWhenPause(isHiddenWhenPause: boolean) { + this.isHiddenWhenPause = isHiddenWhenPause + return this + } + + setAlwaysShowWhenRun(isAlwaysShowWhenRun: boolean) { + this.isAlwaysShowWhenRun = isAlwaysShowWhenRun + return this + } + +} + +export enum PLVMarqueeAnimateType { + // 滚动 + ROLL = 1, + // 闪烁 + FLICKER = 2, + // 滚动+闪烁 + ROLL_FLICKER = 3, + // 滚动,限制在屏幕上下15%高度范围内 + ROLL_15PERCENT = 4, + // 闪烁,限制在屏幕上下15%高度范围内 + FLICKER_15PERCENT = 5, + // 滚动双跑马灯 + ROLL_DOUBLE_MARQUEE = 6, + // 闪烁双跑马灯 + FLICKER_DOUBLE_MARQUEE = 7 +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/marquee/model/PLVMarqueeModel.ts b/polyv/src/main/ets/common/ui/component/marquee/model/PLVMarqueeModel.ts new file mode 100644 index 0000000..11dcee5 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/marquee/model/PLVMarqueeModel.ts @@ -0,0 +1,32 @@ +import {MutableState} from '@polyvharmony/media-player-sdk'; +import {PLVMarqueeAnimateSettingVO} from "./PLVMarqueeAnimateSettingVO"; +import {PLVMarqueeTextSettingVO} from "./PLVMarqueeTextSettingVO"; + +export class PLVMarqueeModel { + + readonly animateSetting: PLVMarqueeAnimateSettingVO + readonly mainTextSetting: PLVMarqueeTextSettingVO + readonly subTextSetting?: PLVMarqueeTextSettingVO + // 控制跑马灯播放暂停 + readonly playingState: MutableState = new MutableState(PLVMarqueePlayingState.PLAY) + + constructor( + animateSetting: PLVMarqueeAnimateSettingVO, + mainTextSetting: PLVMarqueeTextSettingVO, + subTextSetting?: PLVMarqueeTextSettingVO + ) { + this.animateSetting = animateSetting + this.mainTextSetting = mainTextSetting + this.subTextSetting = subTextSetting + } + + setPlayingState(playingState: PLVMarqueePlayingState) { + this.playingState.value = playingState + } + +} + +export enum PLVMarqueePlayingState { + PLAY = 1, + PAUSE = 2 +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/marquee/model/PLVMarqueeTextSettingVO.ts b/polyv/src/main/ets/common/ui/component/marquee/model/PLVMarqueeTextSettingVO.ts new file mode 100644 index 0000000..c5a8b75 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/marquee/model/PLVMarqueeTextSettingVO.ts @@ -0,0 +1,60 @@ +export class PLVMarqueeTextSettingVO { + + // 跑马灯文本内容 + content: string = "" + // 跑马灯文字颜色 + fontColor: string = '#000000' + // 跑马灯文字大小 + fontSize: number = 16 + // 是否显示阴影 + isShadow: boolean = false + // 阴影颜色 + shadowColor: string = '#000000' + // 阴影偏移X + shadowOffsetX: number = 0 + // 阴影偏移Y + shadowOffsetY: number = 0 + // 阴影模糊半径 + shadowRadius: number = 0 + + setContent(content: string) { + this.content = content + return this + } + + setFontColor(fontColor: string) { + this.fontColor = fontColor + return this + } + + setFontSize(fontSize: number) { + this.fontSize = fontSize + return this + } + + setShadow(isShadow: boolean) { + this.isShadow = isShadow + return this + } + + setShadowColor(shadowColor: string) { + this.shadowColor = shadowColor + return this + } + + setShadowOffsetX(shadowOffsetX: number) { + this.shadowOffsetX = shadowOffsetX + return this + } + + setShadowOffsetY(shadowOffsetY: number) { + this.shadowOffsetY = shadowOffsetY + return this + } + + setShadowRadius(shadowRadius: number) { + this.shadowRadius = shadowRadius + return this + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeDoubleMarqueeSubTextView.ets b/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeDoubleMarqueeSubTextView.ets new file mode 100644 index 0000000..b767e0e --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeDoubleMarqueeSubTextView.ets @@ -0,0 +1,114 @@ +import { + extendNumber, + lateInit, + Lazy, + lazy, + MutableObserver, + random, + requireNotNull, + seconds +} from '@polyvharmony/media-player-sdk' +import { PLVMarqueeModel, PLVMarqueePlayingState } from '../model/PLVMarqueeModel' +import MeasureText from '@ohos.measure' +import { PLVMarqueeTextSettingVO } from '../model/PLVMarqueeTextSettingVO' +import Animator, { AnimatorOptions, AnimatorResult } from '@ohos.animator' + +@Component +export struct PLVMarqueeDoubleMarqueeSubTextView { + model?: PLVMarqueeModel + @State textOpacity: number = 0 + @State textOffsetX: number = 0 + @State textOffsetY: number = 0 + private textSetting: PLVMarqueeTextSettingVO = lateInit() + private textSizeOption: Lazy = lazy(() => MeasureText.measureTextSize({ + textContent: this.textSetting.content, + fontSize: this.textSetting.fontSize + })) + private containerWidth: number = 0 + private containerHeight: number = 0 + private marqueeAnimate?: AnimatorResult = undefined + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.textSetting = requireNotNull(this.model?.subTextSetting, () => "subTextSetting is null") + this.model?.playingState.observe((playingState) => { + if (playingState === PLVMarqueePlayingState.PLAY) { + this.marqueeAnimate?.play() + } else { + this.marqueeAnimate?.pause() + } + }).pushTo(this.observers) + } + + build() { + Column() { + Text(this.textSetting.content) + .width(px2vp(this.textSizeOption.value.width as number)) + .height(px2vp(this.textSizeOption.value.height as number)) + .fontColor(this.textSetting.fontColor) + .fontSize(this.textSetting.fontSize) + .maxLines(1) + .textShadow({ + radius: this.textSetting.isShadow ? this.textSetting.shadowRadius : 0, + color: this.textSetting.isShadow ? this.textSetting.shadowColor : undefined, + offsetX: this.textSetting.isShadow ? this.textSetting.shadowOffsetX : undefined, + offsetY: this.textSetting.isShadow ? this.textSetting.shadowOffsetY : undefined, + }) + .opacity(this.textOpacity) + .offset({ + x: this.textOffsetX, + y: this.textOffsetY + }) + } + .width('100%') + .height('100%') + .alignItems(HorizontalAlign.Start) + .justifyContent(FlexAlign.Start) + .onAreaChange((_, newArea) => { + this.containerWidth = newArea.width as number + this.containerHeight = newArea.height as number + this.prepareAnimate() + }) + } + + aboutToDisappear(): void { + this.marqueeAnimate?.cancel() + this.marqueeAnimate = undefined + MutableObserver.disposeAll(this.observers) + this.observers = [] + } + + private prepareAnimate() { + const containerWidth = this.containerWidth + const containerHeight = this.containerHeight + const textWidth = px2vp(this.textSizeOption.value.width as number) + const textHeight = px2vp(this.textSizeOption.value.height as number) + this.marqueeAnimate?.cancel() + + const animOption: AnimatorOptions = { + duration: seconds(5).toMillis(), + delay: 0, + easing: 'linear', + fill: "none", + direction: "normal", + iterations: -1, + begin: 0, + end: 0 + } + this.marqueeAnimate = Animator.create(animOption) + this.marqueeAnimate.onRepeat = () => { + this.textOffsetX = random(0, extendNumber(containerWidth - textWidth).coerceAtLeast_ext(0)) + this.textOffsetY = random(0, extendNumber(containerHeight - textHeight).coerceAtLeast_ext(0)) + } + this.marqueeAnimate.onCancel = () => { + this.textOpacity = 0 + } + + this.textOffsetX = random(0, extendNumber(containerWidth - textWidth).coerceAtLeast_ext(0)) + this.textOffsetY = random(0, extendNumber(containerHeight - textHeight).coerceAtLeast_ext(0)) + this.textOpacity = 1 + if (this.model?.playingState.value === PLVMarqueePlayingState.PLAY) { + this.marqueeAnimate?.play() + } + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeFlicker15PercentView.ets b/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeFlicker15PercentView.ets new file mode 100644 index 0000000..c623c5f --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeFlicker15PercentView.ets @@ -0,0 +1,133 @@ +import { + extendNumber, + lateInit, + Lazy, + lazy, + MutableObserver, + random, + requireNotNull +} from '@polyvharmony/media-player-sdk' +import { PLVMarqueeModel, PLVMarqueePlayingState } from '../model/PLVMarqueeModel' +import MeasureText from '@ohos.measure' +import { PLVMarqueeTextSettingVO } from '../model/PLVMarqueeTextSettingVO' +import { PLVMarqueeAnimateSettingVO } from '../model/PLVMarqueeAnimateSettingVO' +import Animator, { AnimatorOptions, AnimatorResult } from '@ohos.animator' + +@Component +export struct PLVMarqueeFlicker15PercentView { + model?: PLVMarqueeModel + @State textOpacity: number = 0 + @State textOffsetX: number = 0 + @State textOffsetY: number = 0 + private animateSetting: PLVMarqueeAnimateSettingVO = lateInit() + private textSetting: PLVMarqueeTextSettingVO = lateInit() + private textSizeOption: Lazy = lazy(() => MeasureText.measureTextSize({ + textContent: this.textSetting.content, + fontSize: this.textSetting.fontSize + })) + private containerWidth: number = 0 + private containerHeight: number = 0 + private marqueeAnimate?: AnimatorResult = undefined + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.animateSetting = requireNotNull(this.model).animateSetting + this.textSetting = requireNotNull(this.model).mainTextSetting + this.model?.playingState.observe((playingState) => { + if (playingState === PLVMarqueePlayingState.PLAY) { + this.marqueeAnimate?.play() + } else { + this.marqueeAnimate?.pause() + } + }).pushTo(this.observers) + } + + build() { + Column() { + Text(this.textSetting.content) + .width(px2vp(this.textSizeOption.value.width as number)) + .height(px2vp(this.textSizeOption.value.height as number)) + .fontColor(this.textSetting.fontColor) + .fontSize(this.textSetting.fontSize) + .maxLines(1) + .textShadow({ + radius: this.textSetting.isShadow ? this.textSetting.shadowRadius : 0, + color: this.textSetting.isShadow ? this.textSetting.shadowColor : undefined, + offsetX: this.textSetting.isShadow ? this.textSetting.shadowOffsetX : undefined, + offsetY: this.textSetting.isShadow ? this.textSetting.shadowOffsetY : undefined, + }) + .opacity(this.textOpacity) + .offset({ + x: this.textOffsetX, + y: this.textOffsetY + }) + } + .width('100%') + .height('100%') + .alignItems(HorizontalAlign.Start) + .justifyContent(FlexAlign.Start) + .onAreaChange((_, newArea) => { + this.containerWidth = newArea.width as number + this.containerHeight = newArea.height as number + this.prepareAnimate() + }) + } + + aboutToDisappear(): void { + this.marqueeAnimate?.cancel() + this.marqueeAnimate = undefined + MutableObserver.disposeAll(this.observers) + this.observers = [] + } + + private prepareAnimate() { + const containerWidth = this.containerWidth + const containerHeight = this.containerHeight + const textWidth = px2vp(this.textSizeOption.value.width as number) + const textHeight = px2vp(this.textSizeOption.value.height as number) + this.marqueeAnimate?.cancel() + + const minOpacity = this.animateSetting.isAlwaysShowWhenRun ? 0.1 : 0 + const maxOpacity = 1 + const beginValue = minOpacity + const endValue = maxOpacity + (maxOpacity - minOpacity) + + const animOption: AnimatorOptions = { + duration: this.animateSetting.tweenTime.toMillis(), + delay: 0, + easing: 'linear', + fill: "none", + direction: "normal", + iterations: 1, + begin: beginValue, + end: endValue + } + this.marqueeAnimate = Animator.create(animOption) + this.marqueeAnimate.onFrame = (value: number) => { + this.textOpacity = maxOpacity - Math.abs(value - maxOpacity) + } + this.marqueeAnimate.onFinish = () => { + animOption.delay = this.animateSetting.tweenInterval.toMillis() + this.marqueeAnimate?.reset(animOption) + this.textOffsetX = random(0, extendNumber(containerWidth - textWidth).coerceAtLeast_ext(0)) + this.textOffsetY = this.randomTextOffsetY(containerHeight, textHeight) + this.marqueeAnimate?.play() + } + this.marqueeAnimate.onCancel = () => { + this.textOpacity = 0 + } + + this.textOffsetX = random(0, extendNumber(containerWidth - textWidth).coerceAtLeast_ext(0)) + this.textOffsetY = this.randomTextOffsetY(containerHeight, textHeight) + if (this.model?.playingState.value === PLVMarqueePlayingState.PLAY) { + this.marqueeAnimate?.play() + } + } + + private randomTextOffsetY(containerHeight: number, textHeight: number) { + const availableHeight = containerHeight * 0.15 + const onTop = Math.random() < 0.5 + const randomOffsetY = random(0, extendNumber(availableHeight - textHeight).coerceAtLeast_ext(0)) + return onTop ? randomOffsetY : containerHeight - availableHeight + randomOffsetY + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeFlickerDoubleMarqueeView.ets b/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeFlickerDoubleMarqueeView.ets new file mode 100644 index 0000000..4d45408 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeFlickerDoubleMarqueeView.ets @@ -0,0 +1,19 @@ +import { PLVMarqueeModel } from '../model/PLVMarqueeModel' +import { PLVMarqueeDoubleMarqueeSubTextView } from './PLVMarqueeDoubleMarqueeSubTextView' +import { PLVMarqueeFlickerView } from './PLVMarqueeFlickerView' + +@Component +export struct PLVMarqueeFlickerDoubleMarqueeView { + model?: PLVMarqueeModel + + build() { + Stack() { + PLVMarqueeFlickerView({ + model: this.model + }) + PLVMarqueeDoubleMarqueeSubTextView({ + model: this.model + }) + } + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeFlickerView.ets b/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeFlickerView.ets new file mode 100644 index 0000000..942096d --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeFlickerView.ets @@ -0,0 +1,126 @@ +import { + extendNumber, + lateInit, + Lazy, + lazy, + MutableObserver, + random, + requireNotNull +} from '@polyvharmony/media-player-sdk' +import { PLVMarqueeModel, PLVMarqueePlayingState } from '../model/PLVMarqueeModel' +import MeasureText from '@ohos.measure' +import { PLVMarqueeTextSettingVO } from '../model/PLVMarqueeTextSettingVO' +import { PLVMarqueeAnimateSettingVO } from '../model/PLVMarqueeAnimateSettingVO' +import Animator, { AnimatorOptions, AnimatorResult } from '@ohos.animator' + +@Component +export struct PLVMarqueeFlickerView { + model?: PLVMarqueeModel + @State textOpacity: number = 0 + @State textOffsetX: number = 0 + @State textOffsetY: number = 0 + private animateSetting: PLVMarqueeAnimateSettingVO = lateInit() + private textSetting: PLVMarqueeTextSettingVO = lateInit() + private textSizeOption: Lazy = lazy(() => MeasureText.measureTextSize({ + textContent: this.textSetting.content, + fontSize: this.textSetting.fontSize + })) + private containerWidth: number = 0 + private containerHeight: number = 0 + private marqueeAnimate?: AnimatorResult = undefined + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.animateSetting = requireNotNull(this.model).animateSetting + this.textSetting = requireNotNull(this.model).mainTextSetting + this.model?.playingState.observe((playingState) => { + if (playingState === PLVMarqueePlayingState.PLAY) { + this.marqueeAnimate?.play() + } else { + this.marqueeAnimate?.pause() + } + }).pushTo(this.observers) + } + + build() { + Column() { + Text(this.textSetting.content) + .width(px2vp(this.textSizeOption.value.width as number)) + .height(px2vp(this.textSizeOption.value.height as number)) + .fontColor(this.textSetting.fontColor) + .fontSize(this.textSetting.fontSize) + .maxLines(1) + .textShadow({ + radius: this.textSetting.isShadow ? this.textSetting.shadowRadius : 0, + color: this.textSetting.isShadow ? this.textSetting.shadowColor : undefined, + offsetX: this.textSetting.isShadow ? this.textSetting.shadowOffsetX : undefined, + offsetY: this.textSetting.isShadow ? this.textSetting.shadowOffsetY : undefined, + }) + .opacity(this.textOpacity) + .offset({ + x: this.textOffsetX, + y: this.textOffsetY + }) + } + .width('100%') + .height('100%') + .alignItems(HorizontalAlign.Start) + .justifyContent(FlexAlign.Start) + .onAreaChange((_, newArea) => { + this.containerWidth = newArea.width as number + this.containerHeight = newArea.height as number + this.prepareAnimate() + }) + } + + aboutToDisappear(): void { + this.marqueeAnimate?.cancel() + this.marqueeAnimate = undefined + MutableObserver.disposeAll(this.observers) + this.observers = [] + } + + private prepareAnimate() { + const containerWidth = this.containerWidth + const containerHeight = this.containerHeight + const textWidth = px2vp(this.textSizeOption.value.width as number) + const textHeight = px2vp(this.textSizeOption.value.height as number) + this.marqueeAnimate?.cancel() + + const minOpacity = this.animateSetting.isAlwaysShowWhenRun ? 0.1 : 0 + const maxOpacity = 1 + const beginValue = minOpacity + const endValue = maxOpacity + (maxOpacity - minOpacity) + + const animOption: AnimatorOptions = { + duration: this.animateSetting.tweenTime.toMillis(), + delay: 0, + easing: 'linear', + fill: "none", + direction: "normal", + iterations: 1, + begin: beginValue, + end: endValue + } + this.marqueeAnimate = Animator.create(animOption) + this.marqueeAnimate.onFrame = (value: number) => { + this.textOpacity = maxOpacity - Math.abs(value - maxOpacity) + } + this.marqueeAnimate.onFinish = () => { + animOption.delay = this.animateSetting.tweenInterval.toMillis() + this.marqueeAnimate?.reset(animOption) + this.textOffsetX = random(0, extendNumber(containerWidth - textWidth).coerceAtLeast_ext(0)) + this.textOffsetY = random(0, extendNumber(containerHeight - textHeight).coerceAtLeast_ext(0)) + this.marqueeAnimate?.play() + } + this.marqueeAnimate.onCancel = () => { + this.textOpacity = 0 + } + + this.textOffsetX = random(0, extendNumber(containerWidth - textWidth).coerceAtLeast_ext(0)) + this.textOffsetY = random(0, extendNumber(containerHeight - textHeight).coerceAtLeast_ext(0)) + if (this.model?.playingState.value === PLVMarqueePlayingState.PLAY) { + this.marqueeAnimate?.play() + } + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeRoll15PercentView.ets b/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeRoll15PercentView.ets new file mode 100644 index 0000000..6db0d09 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeRoll15PercentView.ets @@ -0,0 +1,143 @@ +import { + extendNumber, + lateInit, + Lazy, + lazy, + MutableObserver, + random, + requireNotNull +} from '@polyvharmony/media-player-sdk' +import { PLVMarqueeModel, PLVMarqueePlayingState } from '../model/PLVMarqueeModel' +import MeasureText from '@ohos.measure' +import { PLVMarqueeTextSettingVO } from '../model/PLVMarqueeTextSettingVO' +import { PLVMarqueeAnimateSettingVO } from '../model/PLVMarqueeAnimateSettingVO' +import Animator, { AnimatorOptions, AnimatorResult } from '@ohos.animator' + +@Component +export struct PLVMarqueeRoll15PercentView { + model?: PLVMarqueeModel + @State isVisible: boolean = false + @State textOffsetX: number = 0 + @State textOffsetY: number = 0 + private animateSetting: PLVMarqueeAnimateSettingVO = lateInit() + private textSetting: PLVMarqueeTextSettingVO = lateInit() + private textSizeOption: Lazy = lazy(() => MeasureText.measureTextSize({ + textContent: this.textSetting.content, + fontSize: this.textSetting.fontSize + })) + private containerWidth: number = 0 + private containerHeight: number = 0 + private marqueeAnimate?: AnimatorResult = undefined + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.animateSetting = requireNotNull(this.model).animateSetting + this.textSetting = requireNotNull(this.model).mainTextSetting + this.model?.playingState.observe((playingState) => { + if (playingState === PLVMarqueePlayingState.PLAY) { + this.marqueeAnimate?.play() + } else { + this.marqueeAnimate?.pause() + } + }).pushTo(this.observers) + } + + build() { + Column() { + Text(this.textSetting.content) + .width(px2vp(this.textSizeOption.value.width as number)) + .height(px2vp(this.textSizeOption.value.height as number)) + .fontColor(this.textSetting.fontColor) + .fontSize(this.textSetting.fontSize) + .maxLines(1) + .textShadow({ + radius: this.textSetting.isShadow ? this.textSetting.shadowRadius : 0, + color: this.textSetting.isShadow ? this.textSetting.shadowColor : undefined, + offsetX: this.textSetting.isShadow ? this.textSetting.shadowOffsetX : undefined, + offsetY: this.textSetting.isShadow ? this.textSetting.shadowOffsetY : undefined, + }) + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + .offset({ + x: this.textOffsetX, + y: this.textOffsetY + }) + } + .width('100%') + .height('100%') + .alignItems(HorizontalAlign.Start) + .justifyContent(FlexAlign.Start) + .onAreaChange((_, newArea) => { + this.containerWidth = newArea.width as number + this.containerHeight = newArea.height as number + this.prepareAnimate() + }) + } + + aboutToDisappear(): void { + this.marqueeAnimate?.cancel() + this.marqueeAnimate = undefined + MutableObserver.disposeAll(this.observers) + this.observers = [] + } + + private prepareAnimate() { + const containerWidth = this.containerWidth + const containerHeight = this.containerHeight + const textWidth = px2vp(this.textSizeOption.value.width as number) + const textHeight = px2vp(this.textSizeOption.value.height as number) + this.marqueeAnimate?.cancel() + + let startOffsetX = 0 + let endOffsetX = 0 + if (this.animateSetting.isAlwaysShowWhenRun) { + if (containerWidth < textWidth) { + startOffsetX = 0 + endOffsetX = containerWidth - textWidth + } else { + startOffsetX = containerWidth - textWidth + endOffsetX = 0 + } + } else { + startOffsetX = containerWidth + endOffsetX = -textWidth + } + + const animOption: AnimatorOptions = { + duration: this.animateSetting.rollTime.toMillis(), + delay: 0, + easing: 'linear', + fill: "none", + direction: "normal", + iterations: 1, + begin: startOffsetX, + end: endOffsetX + } + this.marqueeAnimate = Animator.create(animOption) + this.marqueeAnimate.onFrame = (value: number) => { + this.isVisible = true + this.textOffsetX = value + } + this.marqueeAnimate.onFinish = () => { + this.isVisible = false + animOption.delay = this.animateSetting.rollInterval.toMillis() + this.marqueeAnimate?.reset(animOption) + this.textOffsetY = this.randomTextOffsetY(containerHeight, textHeight) + this.marqueeAnimate?.play() + } + this.marqueeAnimate.onCancel = () => { + this.isVisible = false + } + + this.textOffsetY = this.randomTextOffsetY(containerHeight, textHeight) + if (this.model?.playingState.value === PLVMarqueePlayingState.PLAY) { + this.marqueeAnimate?.play() + } + } + + private randomTextOffsetY(containerHeight: number, textHeight: number) { + const availableHeight = containerHeight * 0.15 + const onTop = Math.random() < 0.5 + const randomOffsetY = random(0, extendNumber(availableHeight - textHeight).coerceAtLeast_ext(0)) + return onTop ? randomOffsetY : containerHeight - availableHeight + randomOffsetY + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeRollDoubleMarqueeView.ets b/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeRollDoubleMarqueeView.ets new file mode 100644 index 0000000..8fc14e5 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeRollDoubleMarqueeView.ets @@ -0,0 +1,19 @@ +import { PLVMarqueeModel } from '../model/PLVMarqueeModel' +import { PLVMarqueeDoubleMarqueeSubTextView } from './PLVMarqueeDoubleMarqueeSubTextView' +import { PLVMarqueeRollView } from './PLVMarqueeRollView' + +@Component +export struct PLVMarqueeRollDoubleMarqueeView { + model?: PLVMarqueeModel + + build() { + Stack() { + PLVMarqueeRollView({ + model: this.model + }) + PLVMarqueeDoubleMarqueeSubTextView({ + model: this.model + }) + } + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeRollFlickerView.ets b/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeRollFlickerView.ets new file mode 100644 index 0000000..c1767ee --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeRollFlickerView.ets @@ -0,0 +1,182 @@ +import { + extendNumber, + lateInit, + Lazy, + lazy, + MutableObserver, + random, + requireNotNull +} from '@polyvharmony/media-player-sdk' +import { PLVMarqueeModel, PLVMarqueePlayingState } from '../model/PLVMarqueeModel' +import MeasureText from '@ohos.measure' +import { PLVMarqueeTextSettingVO } from '../model/PLVMarqueeTextSettingVO' +import { PLVMarqueeAnimateSettingVO } from '../model/PLVMarqueeAnimateSettingVO' +import Animator, { AnimatorOptions, AnimatorResult } from '@ohos.animator' + +@Component +export struct PLVMarqueeRollFlickerView { + model?: PLVMarqueeModel + @State textOpacity: number = 0 + @State isVisible: boolean = false + @State textOffsetX: number = 0 + @State textOffsetY: number = 0 + private animateSetting: PLVMarqueeAnimateSettingVO = lateInit() + private textSetting: PLVMarqueeTextSettingVO = lateInit() + private textSizeOption: Lazy = lazy(() => MeasureText.measureTextSize({ + textContent: this.textSetting.content, + fontSize: this.textSetting.fontSize + })) + private containerWidth: number = 0 + private containerHeight: number = 0 + private rollAnimate?: AnimatorResult = undefined + private tweenAnimate?: AnimatorResult = undefined + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.animateSetting = requireNotNull(this.model).animateSetting + this.textSetting = requireNotNull(this.model).mainTextSetting + this.model?.playingState.observe((playingState) => { + if (playingState === PLVMarqueePlayingState.PLAY) { + this.rollAnimate?.play() + this.tweenAnimate?.play() + } else { + this.rollAnimate?.pause() + this.tweenAnimate?.pause() + } + }).pushTo(this.observers) + } + + build() { + Column() { + Text(this.textSetting.content) + .width(px2vp(this.textSizeOption.value.width as number)) + .height(px2vp(this.textSizeOption.value.height as number)) + .fontColor(this.textSetting.fontColor) + .fontSize(this.textSetting.fontSize) + .maxLines(1) + .textShadow({ + radius: this.textSetting.isShadow ? this.textSetting.shadowRadius : 0, + color: this.textSetting.isShadow ? this.textSetting.shadowColor : undefined, + offsetX: this.textSetting.isShadow ? this.textSetting.shadowOffsetX : undefined, + offsetY: this.textSetting.isShadow ? this.textSetting.shadowOffsetY : undefined, + }) + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + .opacity(this.textOpacity) + .offset({ + x: this.textOffsetX, + y: this.textOffsetY + }) + } + .width('100%') + .height('100%') + .alignItems(HorizontalAlign.Start) + .justifyContent(FlexAlign.Start) + .onAreaChange((_, newArea) => { + this.containerWidth = newArea.width as number + this.containerHeight = newArea.height as number + this.prepareAnimate() + }) + } + + aboutToDisappear(): void { + this.rollAnimate?.cancel() + this.tweenAnimate?.cancel() + this.rollAnimate = undefined + this.tweenAnimate = undefined + MutableObserver.disposeAll(this.observers) + this.observers = [] + } + + private prepareAnimate() { + const containerWidth = this.containerWidth + const containerHeight = this.containerHeight + const textWidth = px2vp(this.textSizeOption.value.width as number) + const textHeight = px2vp(this.textSizeOption.value.height as number) + + this.prepareRollAnimate(containerWidth, containerHeight, textWidth, textHeight) + this.prepareTweenAnimate(containerWidth, containerHeight, textWidth, textHeight) + + this.textOffsetY = random(0, extendNumber(containerHeight - textHeight).coerceAtLeast_ext(0)) + if (this.model?.playingState.value === PLVMarqueePlayingState.PLAY) { + this.rollAnimate?.play() + this.tweenAnimate?.play() + } + } + + private prepareRollAnimate(containerWidth: number, containerHeight: number, textWidth: number, textHeight: number) { + this.rollAnimate?.cancel() + + let startOffsetX = 0 + let endOffsetX = 0 + if (this.animateSetting.isAlwaysShowWhenRun) { + if (containerWidth < textWidth) { + startOffsetX = 0 + endOffsetX = containerWidth - textWidth + } else { + startOffsetX = containerWidth - textWidth + endOffsetX = 0 + } + } else { + startOffsetX = containerWidth + endOffsetX = -textWidth + } + + const rollAnimOption: AnimatorOptions = { + duration: this.animateSetting.rollTime.toMillis(), + delay: 0, + easing: 'linear', + fill: "none", + direction: "normal", + iterations: 1, + begin: startOffsetX, + end: endOffsetX + } + this.rollAnimate = Animator.create(rollAnimOption) + this.rollAnimate.onFrame = (value: number) => { + this.isVisible = true + this.textOffsetX = value + } + this.rollAnimate.onFinish = () => { + this.isVisible = false + rollAnimOption.delay = this.animateSetting.rollInterval.toMillis() + this.rollAnimate?.reset(rollAnimOption) + this.textOffsetY = random(0, extendNumber(containerHeight - textHeight).coerceAtLeast_ext(0)) + this.rollAnimate?.play() + } + this.rollAnimate.onCancel = () => { + this.isVisible = false + } + } + + private prepareTweenAnimate(containerWidth: number, containerHeight: number, textWidth: number, textHeight: number) { + this.tweenAnimate?.cancel() + + const minOpacity = this.animateSetting.isAlwaysShowWhenRun ? 0.1 : 0 + const maxOpacity = 1 + const tweenBeginValue = minOpacity + const tweenEndValue = maxOpacity + (maxOpacity - minOpacity) + + const tweenAnimOption: AnimatorOptions = { + duration: this.animateSetting.tweenTime.toMillis(), + delay: 0, + easing: 'linear', + fill: "none", + direction: "normal", + iterations: 1, + begin: tweenBeginValue, + end: tweenEndValue + } + this.tweenAnimate = Animator.create(tweenAnimOption) + this.tweenAnimate.onFrame = (value: number) => { + this.textOpacity = maxOpacity - Math.abs(value - maxOpacity) + } + this.tweenAnimate.onFinish = () => { + tweenAnimOption.delay = this.animateSetting.tweenInterval.toMillis() + this.tweenAnimate?.reset(tweenAnimOption) + this.tweenAnimate?.play() + } + this.tweenAnimate.onCancel = () => { + this.textOpacity = 0 + } + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeRollView.ets b/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeRollView.ets new file mode 100644 index 0000000..b32afc5 --- /dev/null +++ b/polyv/src/main/ets/common/ui/component/marquee/view/PLVMarqueeRollView.ets @@ -0,0 +1,134 @@ +import { + extendNumber, + lateInit, + Lazy, + lazy, + MutableObserver, + random, + requireNotNull +} from '@polyvharmony/media-player-sdk' +import { PLVMarqueeModel, PLVMarqueePlayingState } from '../model/PLVMarqueeModel' +import MeasureText from '@ohos.measure' +import { PLVMarqueeTextSettingVO } from '../model/PLVMarqueeTextSettingVO' +import { PLVMarqueeAnimateSettingVO } from '../model/PLVMarqueeAnimateSettingVO' +import Animator, { AnimatorOptions, AnimatorResult } from '@ohos.animator' + +@Component +export struct PLVMarqueeRollView { + model?: PLVMarqueeModel + @State isVisible: boolean = false + @State textOffsetX: number = 0 + @State textOffsetY: number = 0 + private animateSetting: PLVMarqueeAnimateSettingVO = lateInit() + private textSetting: PLVMarqueeTextSettingVO = lateInit() + private textSizeOption: Lazy = lazy(() => MeasureText.measureTextSize({ + textContent: this.textSetting.content, + fontSize: this.textSetting.fontSize + })) + private containerWidth: number = 0 + private containerHeight: number = 0 + private marqueeAnimate?: AnimatorResult = undefined + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.animateSetting = requireNotNull(this.model).animateSetting + this.textSetting = requireNotNull(this.model).mainTextSetting + this.model?.playingState.observe((playingState) => { + if (playingState === PLVMarqueePlayingState.PLAY) { + this.marqueeAnimate?.play() + } else { + this.marqueeAnimate?.pause() + } + }).pushTo(this.observers) + } + + build() { + Column() { + Text(this.textSetting.content) + .width(px2vp(this.textSizeOption.value.width as number)) + .height(px2vp(this.textSizeOption.value.height as number)) + .fontColor(this.textSetting.fontColor) + .fontSize(this.textSetting.fontSize) + .maxLines(1) + .textShadow({ + radius: this.textSetting.isShadow ? this.textSetting.shadowRadius : 0, + color: this.textSetting.isShadow ? this.textSetting.shadowColor : undefined, + offsetX: this.textSetting.isShadow ? this.textSetting.shadowOffsetX : undefined, + offsetY: this.textSetting.isShadow ? this.textSetting.shadowOffsetY : undefined, + }) + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + .offset({ + x: this.textOffsetX, + y: this.textOffsetY + }) + } + .width('100%') + .height('100%') + .alignItems(HorizontalAlign.Start) + .justifyContent(FlexAlign.Start) + .onAreaChange((_, newArea) => { + this.containerWidth = newArea.width as number + this.containerHeight = newArea.height as number + this.prepareAnimate() + }) + } + + aboutToDisappear(): void { + this.marqueeAnimate?.cancel() + this.marqueeAnimate = undefined + MutableObserver.disposeAll(this.observers) + this.observers = [] + } + + private prepareAnimate() { + const textWidth = px2vp(this.textSizeOption.value.width as number) + const textHeight = px2vp(this.textSizeOption.value.height as number) + this.marqueeAnimate?.cancel() + + let startOffsetX = 0 + let endOffsetX = 0 + if (this.animateSetting.isAlwaysShowWhenRun) { + if (this.containerWidth < textWidth) { + startOffsetX = 0 + endOffsetX = this.containerWidth - textWidth + } else { + startOffsetX = this.containerWidth - textWidth + endOffsetX = 0 + } + } else { + startOffsetX = this.containerWidth + endOffsetX = -textWidth + } + + const animOption: AnimatorOptions = { + duration: this.animateSetting.rollTime.toMillis(), + delay: 0, + easing: 'linear', + fill: "none", + direction: "normal", + iterations: 1, + begin: startOffsetX, + end: endOffsetX + } + this.marqueeAnimate = Animator.create(animOption) + this.marqueeAnimate.onFrame = (value: number) => { + this.isVisible = true + this.textOffsetX = value + } + this.marqueeAnimate.onFinish = () => { + this.isVisible = false + animOption.delay = this.animateSetting.rollInterval.toMillis() + this.marqueeAnimate?.reset(animOption) + this.textOffsetY = random(0, extendNumber(this.containerHeight - textHeight).coerceAtLeast_ext(0)) + this.marqueeAnimate?.play() + } + this.marqueeAnimate.onCancel = () => { + this.isVisible = false + } + + this.textOffsetY = random(0, extendNumber(this.containerHeight - textHeight).coerceAtLeast_ext(0)) + if (this.model?.playingState.value === PLVMarqueePlayingState.PLAY) { + this.marqueeAnimate?.play() + } + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/enums/PLVMediaPlayerScenes.ts b/polyv/src/main/ets/common/ui/enums/PLVMediaPlayerScenes.ts new file mode 100644 index 0000000..84d5457 --- /dev/null +++ b/polyv/src/main/ets/common/ui/enums/PLVMediaPlayerScenes.ts @@ -0,0 +1,44 @@ +import {PLVMediaResource} from '@polyvharmony/media-player-sdk' + +export class PLVMediaPlayerScenes { + readonly name: string + + private constructor( + name: string + ) { + this.name = name + } + + static readonly DOWNLOAD_CENTER = new PLVMediaPlayerScenes("scene_download_center") + static readonly SINGLE_VIDEO = new PLVMediaPlayerScenes("scene_single_video") + static readonly FEED_VIDEO = new PLVMediaPlayerScenes("scene_feed_video") + +} + +export abstract class PLVMediaPlayerScenePageParam { +} + +export class PLVMediaPlayerDownloadCenterPageParam extends PLVMediaPlayerScenePageParam { + constructor( + readonly defaultTabIndex: number = 0 + ) { + super() + } +} + +export class PLVMediaPlayerSingleVideoPageParam extends PLVMediaPlayerScenePageParam { + constructor( + readonly mediaResource: PLVMediaResource, + readonly enterFromDownloadCenter: boolean = false + ) { + super(); + } +} + +export class PLVMediaPlayerFeedVideoPageParam extends PLVMediaPlayerScenePageParam { + constructor( + readonly initialMediaSources: PLVMediaResource[] + ) { + super(); + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/ui/ext/ImageKitExts.ets b/polyv/src/main/ets/common/ui/ext/ImageKitExts.ets new file mode 100644 index 0000000..e6d5422 --- /dev/null +++ b/polyv/src/main/ets/common/ui/ext/ImageKitExts.ets @@ -0,0 +1,20 @@ +import fs from '@ohos.file.fs'; +import { image } from '@kit.ImageKit'; + +export async function readFileAsPixelMap(url: string): Promise { + try { + if (!await fs.access(url)) { + return null + } + const file = await fs.open(url, fs.OpenMode.READ_ONLY) + const size = (await fs.stat(file.fd)).size + const buffer = new ArrayBuffer(size) + await fs.read(file.fd, buffer) + const imageSource = image.createImageSource(buffer) + return await imageSource.createPixelMap({ + desiredPixelFormat: image.PixelMapFormat.RGB_565 + }) + } catch (e) { + return null + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/utils/PLVAbilityContexts.ts b/polyv/src/main/ets/common/utils/PLVAbilityContexts.ts new file mode 100644 index 0000000..c860f01 --- /dev/null +++ b/polyv/src/main/ets/common/utils/PLVAbilityContexts.ts @@ -0,0 +1,33 @@ +import common from '@ohos.app.ability.common'; +import {extendArray} from '@polyvharmony/media-player-sdk'; + +export class PLVAbilityContexts { + + // + + private static readonly instance = new PLVAbilityContexts() + + private constructor() { + } + + static getInstance() { + return PLVAbilityContexts.instance + } + + // + + private readonly uiAbilityContexts: common.UIAbilityContext[] = [] + + registerContext(context: common.UIAbilityContext) { + this.uiAbilityContexts.push(context) + } + + unregisterContext(context: common.UIAbilityContext) { + extendArray(this.uiAbilityContexts).remove_ext(context) + } + + lastContext(): common.UIAbilityContext | undefined { + return this.uiAbilityContexts[this.uiAbilityContexts.length - 1] + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/utils/PLVBackgroundTaskManager.ts b/polyv/src/main/ets/common/utils/PLVBackgroundTaskManager.ts new file mode 100644 index 0000000..dc80043 --- /dev/null +++ b/polyv/src/main/ets/common/utils/PLVBackgroundTaskManager.ts @@ -0,0 +1,88 @@ +import {common, wantAgent} from '@kit.AbilityKit' +import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager' +import {extendArray, isNullOrUndefined, MutableState} from '@polyvharmony/media-player-sdk' + +export class PLVBackgroundTaskManager { + + private static readonly instance = new PLVBackgroundTaskManager() + + private constructor() { + } + + static getInstance() { + return PLVBackgroundTaskManager.instance + } + + runningBackgroundTaskType: MutableState = new MutableState(null) + + private refContext: common.UIAbilityContext | null = null + private backgroundTasks: backgroundTaskManager.BackgroundMode[] = [] + + setupContext(context: common.UIAbilityContext) { + this.refContext = context + } + + async pushBackgroundTask(type: backgroundTaskManager.BackgroundMode) { + this.backgroundTasks.push(type) + if (isNullOrUndefined(this.runningBackgroundTaskType.value)) { + await this.updateCurrentRunningBackgroundTask() + } + } + + async removeBackgroundTask(type: backgroundTaskManager.BackgroundMode) { + extendArray(this.backgroundTasks).remove_ext(type) + if (this.runningBackgroundTaskType.value === type) { + await this.updateCurrentRunningBackgroundTask() + } + } + + private async updateCurrentRunningBackgroundTask() { + let currentRunningTask = this.runningBackgroundTaskType.value + const shouldStopBackgroundTask = !isNullOrUndefined(currentRunningTask) && this.backgroundTasks.find(task => task === currentRunningTask) === undefined + if (shouldStopBackgroundTask) { + await this.stopBackgroundTask() + } + + currentRunningTask = this.runningBackgroundTaskType.value + const shouldStartBackgroundTask = isNullOrUndefined(currentRunningTask) && this.backgroundTasks.length > 0 + if (shouldStartBackgroundTask) { + await this.startBackgroundTask(this.backgroundTasks[0]) + } + } + + private async startBackgroundTask(type: backgroundTaskManager.BackgroundMode) { + if (this.refContext === null) { + return + } + const wantAgentInfo: wantAgent.WantAgentInfo = { + wants: [ + { + bundleName: this.refContext.abilityInfo.bundleName, + abilityName: this.refContext.abilityInfo.name + } + ], + actionType: wantAgent.OperationType.START_ABILITY, + requestCode: 0, + actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG] + }; + + const wantAgentObject = await wantAgent.getWantAgent(wantAgentInfo) + await backgroundTaskManager.startBackgroundRunning(this.refContext, type, wantAgentObject) + this.runningBackgroundTaskType.setValue(type) + } + + private async stopBackgroundTask() { + if (this.refContext === null) { + return + } + await backgroundTaskManager.stopBackgroundRunning(this.refContext) + this.runningBackgroundTaskType.setValue(null) + } + + async destroy() { + this.backgroundTasks = [] + await this.stopBackgroundTask() + this.refContext = null + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/utils/PLVBrightnessManager.ts b/polyv/src/main/ets/common/utils/PLVBrightnessManager.ts new file mode 100644 index 0000000..8239d5d --- /dev/null +++ b/polyv/src/main/ets/common/utils/PLVBrightnessManager.ts @@ -0,0 +1,45 @@ +import window from '@ohos.window' +import {PLVAbilityContexts} from './PLVAbilityContexts' + +export class PLVBrightnessManager { + + // + + private static readonly instance = new PLVBrightnessManager() + + private constructor() { + } + + static getInstance() { + return PLVBrightnessManager.instance + } + + // + + /** + * 获取当前窗口亮度 + * @returns 手动设置范围 [0.0, 1.0],跟随系统亮度返回 -1 + */ + async getBrightness(): Promise { + const context = PLVAbilityContexts.getInstance().lastContext() + if (!context) { + return undefined + } + const lastWindow = await window.getLastWindow(context) + return lastWindow.getWindowProperties().brightness + } + + /** + * 设置当前窗口亮度 + * @param brightness 手动设置范围 [0.0, 1.0],跟随系统亮度传 -1 + */ + async setBrightness(brightness: number): Promise { + const context = PLVAbilityContexts.getInstance().lastContext() + if (!context) { + return + } + const lastWindow = await window.getLastWindow(context) + await lastWindow.setWindowBrightness(brightness) + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/utils/PLVComponentLifecycle.ts b/polyv/src/main/ets/common/utils/PLVComponentLifecycle.ts new file mode 100644 index 0000000..8ac6bd9 --- /dev/null +++ b/polyv/src/main/ets/common/utils/PLVComponentLifecycle.ts @@ -0,0 +1,45 @@ +import {MutableEvent, MutableState} from '@polyvharmony/media-player-sdk'; + +export class PLVComponentLifecycle { + + readonly state: MutableState = new MutableState(LifecycleState.INITIALIZED) + readonly event: MutableEvent = new MutableEvent() + + onAboutToAppear() { + this.changeStateTo(LifecycleState.APPEARED) + } + + onPageShow() { + this.changeStateTo(LifecycleState.SHOWING) + } + + onPageHide() { + this.changeStateTo(LifecycleState.APPEARED) + } + + onAboutToDisappear() { + this.changeStateTo(LifecycleState.INITIALIZED) + } + + private changeStateTo(state: LifecycleState) { + this.event.value = new LifecycleChangeEvent(this.state.value!, state) + this.state.value = state + } + +} + +export enum LifecycleState { + INITIALIZED = 0, + APPEARED = 1, + SHOWING = 2 +} + +export class LifecycleChangeEvent { + + constructor( + readonly from: LifecycleState, + readonly to: LifecycleState + ) { + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/utils/PLVDisplayUtils.ts b/polyv/src/main/ets/common/utils/PLVDisplayUtils.ts new file mode 100644 index 0000000..26e4ed7 --- /dev/null +++ b/polyv/src/main/ets/common/utils/PLVDisplayUtils.ts @@ -0,0 +1,81 @@ +import display from '@ohos.display' + +export function isPortrait() { + const orientation = display.getDefaultDisplaySync().orientation + return orientation === display.Orientation.PORTRAIT || orientation === display.Orientation.PORTRAIT_INVERTED +} + +export function isLandscape() { + const orientation = display.getDefaultDisplaySync().orientation + return orientation === display.Orientation.LANDSCAPE || orientation === display.Orientation.LANDSCAPE_INVERTED +} + +/** + * display width + * @returns width in px + */ +export function getDisplayWindowWidth() { + return px(display.getDefaultDisplaySync().width) +} + +/** + * display height + * @returns height in px + */ +export function getDisplayWindowHeight() { + return px(display.getDefaultDisplaySync().height) +} + +export class Pixels { + constructor( + readonly value: number + ) { + } + + get px() { + return this.value + } + + get vp() { + return px2vp(this.value) + } + + toString() { + return `${this.value}px` + } +} + +export function px(value: number) { + return new Pixels(value) +} + +export class VirtualPixels { + constructor( + readonly value: number + ) { + } + + get px() { + return vp2px(this.value) + } + + get vp() { + return this.value + } + + toString() { + return `${this.value}vp` + } +} + +export function vp(value: number) { + return new VirtualPixels(value) +} + +// + +declare function px2vp(value: number): number; + +declare function vp2px(value: number): number; + +// \ No newline at end of file diff --git a/polyv/src/main/ets/common/utils/PLVOrientationManager.ts b/polyv/src/main/ets/common/utils/PLVOrientationManager.ts new file mode 100644 index 0000000..f1e46ee --- /dev/null +++ b/polyv/src/main/ets/common/utils/PLVOrientationManager.ts @@ -0,0 +1,29 @@ +import {MutableState} from '@polyvharmony/media-player-sdk'; +import {isPortrait} from './PLVDisplayUtils'; +import window from '@ohos.window'; +import {PLVAbilityContexts} from './PLVAbilityContexts'; + +export class PLVOrientationManager { + private static readonly instance = new PLVOrientationManager(); + + private constructor() { + } + + static getInstance(): PLVOrientationManager { + return PLVOrientationManager.instance; + } + + readonly isPortrait: MutableState = new MutableState(isPortrait()) + + requestOrientation(orientation: 'port' | 'land') { + const context = PLVAbilityContexts.getInstance().lastContext() + const windowOrientation = orientation === 'port' ? window.Orientation.PORTRAIT : window.Orientation.LANDSCAPE + if (!context) { + return + } + window.getLastWindow(context).then((lastWindow) => { + lastWindow.setPreferredOrientation(windowOrientation) + }) + } + +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/utils/PLVOrientationManagerObserver.ets b/polyv/src/main/ets/common/utils/PLVOrientationManagerObserver.ets new file mode 100644 index 0000000..2767ae5 --- /dev/null +++ b/polyv/src/main/ets/common/utils/PLVOrientationManagerObserver.ets @@ -0,0 +1,23 @@ +import mediaquery from '@ohos.mediaquery'; +import { isPortrait } from './PLVDisplayUtils'; +import { PLVOrientationManager } from './PLVOrientationManager'; + +@Component +export struct PLVOrientationManagerObserver { + private readonly listener = mediaquery.matchMediaSync('(orientation: landscape)'); + + aboutToAppear(): void { + this.listener.on('change', this.onOrientationChanged) + } + + aboutToDisappear(): void { + this.listener.off('change', this.onOrientationChanged) + } + + onOrientationChanged(isOrientationLandscape: mediaquery.MediaQueryResult) { + PLVOrientationManager.getInstance().isPortrait.value = isPortrait(); + } + + build() { + } +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/utils/PLVTimeUtils.ts b/polyv/src/main/ets/common/utils/PLVTimeUtils.ts new file mode 100644 index 0000000..54a1111 --- /dev/null +++ b/polyv/src/main/ets/common/utils/PLVTimeUtils.ts @@ -0,0 +1,24 @@ +/** + * format milliseconds to mm:ss or HH:mm:ss + */ +export function formatDuration(durationInMs: number) { + const duration = Math.floor(durationInMs / 1000) + if (duration < 60) { + const secondText = duration.toString().padStart(2, '0') + return `00:${secondText}` + } + if (duration < 3600) { + const minutes = Math.floor(duration / 60) + const seconds = Math.floor(duration % 60) + const minuteText = minutes.toString().padStart(2, '0') + const secondText = seconds.toString().padStart(2, '0') + return `${minuteText}:${secondText}` + } + const hours = Math.floor(duration / 3600) + const minutes = Math.floor((duration % 3600) / 60) + const seconds = Math.floor(duration % 60) + const hourText = hours.toString().padStart(2, '0') + const minuteText = minutes.toString().padStart(2, '0') + const secondText = seconds.toString().padStart(2, '0') + return `${hourText}:${minuteText}:${secondText}` +} \ No newline at end of file diff --git a/polyv/src/main/ets/common/utils/arkts-no-everything.ts b/polyv/src/main/ets/common/utils/arkts-no-everything.ts new file mode 100644 index 0000000..d4f2d9f --- /dev/null +++ b/polyv/src/main/ets/common/utils/arkts-no-everything.ts @@ -0,0 +1,85 @@ +export function usePadding(padding: { + top?: number; + bottom?: number; + left?: number; + right?: number; + vertical?: number; + horizontal?: number; +}) { + return { + top: padding.top ?? padding.vertical, + bottom: padding.bottom ?? padding.vertical, + left: padding.left ?? padding.horizontal, + right: padding.right ?? padding.horizontal + } +} + +export const parent: string = '__container__' + +export function toTopOf(anchor: string) { + return { + anchor, + align: VerticalAlign.Top + } +} + +export function toBottomOf(anchor: string) { + return { + anchor, + align: VerticalAlign.Bottom + } +} + +export function toCenterOf(anchor: string) { + return { + anchor, + align: VerticalAlign.Center + } +} + +export function toStartOf(anchor: string) { + return { + anchor, + align: HorizontalAlign.Start + } +} + +export function toEndOf(anchor: string) { + return { + anchor, + align: HorizontalAlign.End + } +} + +export function toMiddleOf(anchor: string) { + return { + anchor, + align: HorizontalAlign.Center + } +} + +export const any: (any: any) => any = (any: any) => any + +let createIdCounter = 1 + +export function createId(id?: string): string { + return id ?? `plv_media_player_comp_id_${createIdCounter++}` +} + +export type whatever = unknown + +// + +declare enum VerticalAlign { + Top, + Center, + Bottom +} + +declare enum HorizontalAlign { + Start, + Center, + End +} + +// \ No newline at end of file diff --git a/polyv/src/main/module.json5 b/polyv/src/main/module.json5 new file mode 100644 index 0000000..c80c264 --- /dev/null +++ b/polyv/src/main/module.json5 @@ -0,0 +1,10 @@ +{ + "module": { + "name": "polyv", + "type": "har", + "deviceTypes": [ + "default", + "tablet" + ] + } +} diff --git a/polyv/src/main/resources/base/element/string.json b/polyv/src/main/resources/base/element/string.json new file mode 100644 index 0000000..cd99c33 --- /dev/null +++ b/polyv/src/main/resources/base/element/string.json @@ -0,0 +1,132 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + }, + { + "name": "plv_media_player_ui_component_audio_mode_hint_desc_text", + "value": "在锁屏和切到后台时也能播放音频" + }, + { + "name": "plv_media_player_ui_component_audio_mode_hint_text", + "value": "音频模式" + }, + { + "name": "plv_media_player_ui_component_auto_continue_hint_pre", + "value": "您上次观看至 " + }, + { + "name": "plv_media_player_ui_component_auto_continue_hint_post", + "value": ",已为您自动续播" + }, + { + "name": "plv_media_player_ui_component_auxiliary_time_left_text", + "value": "广告也精彩:%s秒" + }, + { + "name": "plv_media_player_ui_component_bit_rate_hint_text_portrait", + "value": "清晰度:" + }, + { + "name": "plv_media_player_ui_component_buffering_speed_hint_text", + "value": "正在加载中 请稍等" + }, + { + "name": "plv_media_player_ui_component_complete_hint_restart_text", + "value": "重播" + }, + { + "name": "plv_media_player_ui_component_error_hint_restart_text", + "value": "点击重试" + }, + { + "name": "plv_media_player_ui_component_error_hint_text", + "value": "视频加载失败,请检查网络设置" + }, + { + "name": "plv_media_player_ui_component_long_press_speed_control_hint_text", + "value": "快进中" + }, + { + "name": "plv_media_player_ui_component_network_poor_hint_text", + "value": "当前网络较差,可尝试" + }, + { + "name": "plv_media_player_ui_component_network_poor_switch_bitrate_action_text_prefix", + "value": "切换到" + }, + { + "name": "plv_media_player_ui_component_speed_hint_text_portrait", + "value": "倍速:" + }, + { + "name": "plv_media_player_ui_component_switch_bit_rate_finish_text_pre", + "value": "您已成功切换到" + }, + { + "name": "plv_media_player_ui_component_switch_bit_rate_finish_text_post", + "value": "清晰度" + }, + { + "name": "plv_media_player_ui_component_switch_bit_rate_start_text_pre", + "value": "正在为您切换到" + }, + { + "name": "plv_media_player_ui_component_switch_bit_rate_start_text_post", + "value": "清晰度,请耐心等待~" + }, + { + "name": "plv_media_player_ui_component_switch_video_hint_text", + "value": "切回视频" + }, + { + "name": "plv_media_player_ui_component_subtitle_setting_text", + "value": "字幕设置" + }, + { + "name": "plv_media_player_ui_component_subtitle_setting_show_switch_label", + "value": "显示字幕" + }, + { + "name": "plv_media_player_ui_component_subtitle_setting_double_subtitle_prefix", + "value": "显示双语:%s" + }, + { + "name": "plv_media_player_ui_component_download_text", + "value": "下载" + }, + { + "name": "plv_media_player_ui_component_download_text_paused", + "value": "暂停中" + }, + { + "name": "plv_media_player_ui_component_download_text_downloading", + "value": "下载中" + }, + { + "name": "plv_media_player_ui_component_download_text_waiting", + "value": "等待中" + }, + { + "name": "plv_media_player_ui_component_download_text_completed", + "value": "已下载" + }, + { + "name": "plv_media_player_ui_component_download_text_failed", + "value": "下载失败" + }, + { + "name": "plv_media_player_ui_component_screenshot_save_success", + "value": "视频截图保存成功" + }, + { + "name": "plv_media_player_ui_component_screenshot_save_failed", + "value": "视频截图保存失败" + }, + { + "name": "plv_media_player_ui_component_screenshot_save_to_local", + "value": "保存视频截图" + } + ] +} \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_audio_mode_audio_volume_visualize_port.svg b/polyv/src/main/resources/base/media/plv_media_player_audio_mode_audio_volume_visualize_port.svg new file mode 100644 index 0000000..0bb1a34 --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_audio_mode_audio_volume_visualize_port.svg @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_audio_mode_cover_placeholder.svg b/polyv/src/main/resources/base/media/plv_media_player_audio_mode_cover_placeholder.svg new file mode 100644 index 0000000..cceab60 --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_audio_mode_cover_placeholder.svg @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_audio_mode_image_wrap_bg.svg b/polyv/src/main/resources/base/media/plv_media_player_audio_mode_image_wrap_bg.svg new file mode 100644 index 0000000..334abfb --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_audio_mode_image_wrap_bg.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_audio_mode_switch_video_icon.svg b/polyv/src/main/resources/base/media/plv_media_player_audio_mode_switch_video_icon.svg new file mode 100644 index 0000000..605ed88 --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_audio_mode_switch_video_icon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_back_icon.svg b/polyv/src/main/resources/base/media/plv_media_player_back_icon.svg new file mode 100644 index 0000000..1e588ba --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_back_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_brightness_hint_icon.svg b/polyv/src/main/resources/base/media/plv_media_player_brightness_hint_icon.svg new file mode 100644 index 0000000..455841f --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_brightness_hint_icon.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_float_menu_close_icon.svg b/polyv/src/main/resources/base/media/plv_media_player_float_menu_close_icon.svg new file mode 100644 index 0000000..4dd1fc1 --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_float_menu_close_icon.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_full_screen_icon.svg b/polyv/src/main/resources/base/media/plv_media_player_full_screen_icon.svg new file mode 100644 index 0000000..9b99568 --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_full_screen_icon.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_lock_orientation_icon_locking.svg b/polyv/src/main/resources/base/media/plv_media_player_lock_orientation_icon_locking.svg new file mode 100644 index 0000000..b996bd7 --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_lock_orientation_icon_locking.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_lock_orientation_icon_no_lock.svg b/polyv/src/main/resources/base/media/plv_media_player_lock_orientation_icon_no_lock.svg new file mode 100644 index 0000000..a54e621 --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_lock_orientation_icon_no_lock.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_long_press_speed_control_anim.webp b/polyv/src/main/resources/base/media/plv_media_player_long_press_speed_control_anim.webp new file mode 100644 index 0000000000000000000000000000000000000000..52456534fa0240f20576a585479ba5ce541b69ec GIT binary patch literal 20884 zcmeHvbySq=+Wr6o(m4v!&7hQYNk|S2Qi{^u9nu2=A{|PXgo3ov9nv942}rkqG*a`M zfqlN?K4-hnKIc2%TE9PZ`L1`p@yuGjUh7)dbKTc{t3jlt!&CtPEvb7dnkxL-m;eBP z5Psxc{uNh(D5AlyVDLWxfVh&PbT%>oKm-5~0|0sefD`~g0{|e4E2zloAOQeKq8S-B zWW1W_)6{3g1W5UHieeGQ@)1*K`Dk8`?fuQk^-EFbFmGw;`e}YwcY%##9KvQ%X-`Cv zj`7@P>^zwG@&Y#+r(eG7-G84x{aCuDr}xq6Ip#JHyv(r6IKaa8{tlDtS=C<{j z0DznCth{JVZP>e>-e*g(qSGDv+g#o_Oxyj(f|<) zmPn~=+DuY51%Mb@U}*pd;I%b7N?*s0^a1&SmWLg!P7_s=h@Ysa=b402aVEcYt3EVc z>FpY4o0IcUcjNQyJt3%D+;C}_?B-nEC$ko6iDPqyCcwp+yana`@V$t-tq;fpQ@-l< zAE$h4I!<-@vSqQ3GH7(`Ggd&oZgQZ)QFk}lQF1)bQkqe@ znLK>Ab&jm*?QiCT05@WtgkQ={X!8|TlQ*r zui!!ajuO_h(h25|2mJ18tRGb51K@zY#A?ltSe3rSDhdLtRtT)hWaQe=^ST3-L=Sj< zB!S~6K0{G-lP~C)Vdua`^JoFnYhB1e7vTGBekR$^U-Is5*Ge@6f1`t=^^iDCq;b|} zz%6jNxVQUrKS9GGD;JY2I~{Gw{%x-E=zikb_>Q`i3~Psp(r*`XyxnVZaZlOD7=cJ~ zyzLQOG^-iLM3Qp~=*V%_p%iBK2F1G=Ls}B?f#3;{P=8=XK#U^(Q!W~Gl`>}66b$ie z49V1(X7}(jjPHNtCxYV2@rr}`1F6Nja>pGQffRApP%!*0``tG!0(hY<9(m)ibV|_E zJ;}K^1E_6WzJ0e}3wQ*mbQ9nCl{yi>iI_p0C2}4%dW~v6Mha&Jha8XpeKg?oPlX`%4}jJhM&k3`60>aHntJb zZhwg7!@IBj5SX=1@-jVcd>_Nb0PXdTNAL1xu$gjn8Y1@fBz!oBy2iS|}OG!X1{cG!ldiY!;v9_`%drU6ke%{4Rhmvta-Xb&6sL4{KCY3{P(`8DDG+0-R&r!7zAPgA1TBNiD=Nii(} z2t2!D>!N~phm-bsL1CE+_k@mYaq4fewO&X;3asNMwWl+zZ_UWG)n%V{wkxw83s31- zmRYtQsNFr=6rNerjFwdEj#cgRuasdskh~T$Gp1*(Bue-Ggd#?`0uoAM zEX?#OMux~gDDXsc;@z6b{k~HAv(~k(uMp8Y5Q_bE(t^Y>lQXWbbB>ZPxBIrW97Bn! zl$*;Rus^5qRoj-i9xNK`yY;m|*EBkWf7Qy!q10uLDOxD*^xKQ%z@Gdo4Cns@!eBF@vL}B=|y!TLQ zafAdAkIFzu1!`FpggU^d2P0GjI$<1KIDmi{a&WDH2?gfBH3h0TbOw|{abq5HQ}y2Rr$!&s4UixzvJD9!EOMnGSMEj01JB6MB|BNPj0(nALOH-f}4B`|83u^Fn=Ia&3LlPoAWZQirB ztk+O-w!YzyV<+!UHq%sozf#W-q_g7^Z=rcnqu9V+WBC14$y+Y~*dxj}lrEpDb5Wh= zQ`GW$;WfW$^U*!C7rxuCXRPQA!#Zg{BpCBloOw3-mvDnIy92UHoO#O5+_VB{p8D}; z5aI`eG-Y~xk6yrv(&AyZ>@Yo!SM}i_&p7KIsv{2~9bAQY9jAKR6X#w#H53H%vcSK* z%AYbX<+qL8eO`b)nJ3$@TaqI~9nu-fD$3qbV;isF2~kilFFN!7dVeg744)!P!Euc; zR~3_oBKc|B@BtrQyHPbpVh}XxQb)KTvtcu%+)wHZJ9wF?AZe(Y?>)7TA`R!=T!$ng zUD^E@mP@_e=GdNamixOHkdsSv%||t=Ua?!(7kit@@EfWN$Hl{CM zjMIFmvicZfdcQQgX-dJ2@>R>wTmOeB50R8r4ef3|y!n>=4Y^u`Ixa>Aun8#+z#kaZ zHiUnV&@%uIV01ia%OD3e9LKaJ^e?3KAz#DA4UO-}hOuBq423~oaW6E{zxdz}`KkzD zu)jQzvZK@h%-Qt2^e^N_Z-ZSA)yiWSA4W1vTY1+Ut$ul%{Y5Q*!@E)*{Qb+huhvDQ z&JpY>4Ay_{yD4g06)AtQkak+NE^1plfOWB_^xh-9lXf)uWhu9Hy9;XEwZdw)dzdr@ zMH_x{F$QamY<=Gyk=H zn)lGss8rPnU~lZt-3X2TPkP52;pnZ0qjwT4r0_U05pO; zV?ub$AcA}m7sZFd7O@m2Ho~4A?dNMpgRIvLjH{o|jPK-Xp3k2t^#!0BzS$cbGjz6J z9h4C7os(C;-95%dj=x_iI>UW&RZ=c1fYil*9YNb7aN?`WBB0GmGL+8^K1z@l_W$f&?DaDbwH|urV4YkCQM;h_t2eq}HA!+&fRV(>=Y_dh`CWak( zT2Ev}>eW86kPyaD24$HxM@8*(fOxQi0<(iNUq9fWr@xCW)d!`aSu+4z-oVCy;7O>K z>&wbMAqh=FmAE(5s`3|>_NgQ<58dIWqM*7^ei?z)w>(bIg?*Dy5S$4AeQXqoI|j0{ zBxpf~Em2V%*x2oXG&BYzB%v4*_gXkOKtw@-fd=|uMr;g>)+QAN5<P}PQh^Ti<4U-;U+?xp1Z5> zTOlug0`dlp)YrH(wbk3z`Gi?3Te{;1ni^6cx3{)RSKf*+(wm(-7<6nhNp)>9-Q~t; zg=U+tWoXBZugh1K6-^=JI5_h*X0?@|#+(D_@UAnp5|1?*(N3WWUN z^y+sWu=l+!d4Q=!*o05f!(~4PI%eD>=$GG~{&wlAB&L$FNVxy|%Xd!5P-OUT zx?Mee=X6gsW07&S*`fVn^9z6RO#k&$7(xUR4?p`z`xF-EU2e zZ_vUtkfYFMZoive#(ri8@CBRz&d#@Ya3UH4=gM)QnBIcr**ye}iB}R9+QUU$BMMv& zcRTv**AtCQ)hf<*X>{30F-a>FbyZX62~DGokTpIyPPqH=#0+|7kUd}08eYLkEDTB}SE$eF$s zXGv7uprK)_Ew+!pmNy7Nc&#we_DL& zWT@FR{2-Zm$~gE%!1dX7a+b)r;Kk_OD1=`ihT$iHPNR=B8ssN_jQKau0I}slem;v; z!V8TQ*Tg0cXtP(fkK@FQQ2XHT5Q_XZ{64*(CUOT*1-S!m87N{>KV`&74^*cFmPL=! z4#{nB4!Inp)s~WK%m{~Ye=70}eROnkq`|Z`=`mYfZo-sS>w)?1E$qwd3*GfLPdiF? zyU8-5?aj-Z(-xbx(#8bdNW-L)^KMk~t!*8Q5BFL{W!WZS9V=vo74BTReP+hvTI0JHtE8C9nJ28Qk{s79tn zbAEITSeH&i0u2sX6flxKI?c0rbQ&NzJ|z%!PF#f~NzwuC5}^4@y@oqoF4!@W4rq}1 zQD%NQQMgkOi4qI$_lHc?0OV#<5C^hsRu!`Byk?g;;lo#wcRoHe{Tw$sZN9)c++(x1 zBdUqV6`r$`Ah^xQBd4ns)3u>_$5Rh%x-Hba@6DRvV{5-Rz+ih(Eo1ZTQ4X)WEtgIm zaRTcXp*F^C9$NV!%ooycue}ID75C7_k7KexWd=&2Fb4)A#Yb^u5UOk6jX|DQy^Y#N zm$}JclQbBbJ@Lk@6^2@|&3wpcF&vTkd7sJ9x}_{RxhvhX0fKg^6n-)9zt6lsQwxs} zNdAKggVE%R)a4W=!{Qhvw0JvyfGFit>KY}j3 z5K!-#+3K(d_05RECY3mB88L^zOz_PJKVVRnF+XxB9kx`L zJ&>LQ#L!BhLT&(-3!3==sgr;O;CJyRDpFG&MMq zBE~*054+!xWP4oQaO6#2&_f0TYW^5~Q2Y&hGlL2iC@GWX;)H#YtFFZf8e27~u+I`k zaF+cNWHMJbW-c^fsPTCQh5QGIx@ zzQ>cV!hVR><@7LL16BhH2Fb-dFP4Hl`UA8VJ5^-@@!PS#f*Y~o7Ke92|KQoO%#{GZ|YcL{|bGB(x- z9RE?q#z@DLj(H{zT4zqDIDW0vFVpvtIdKzbTnzJvi&NYCGr?VmjLk9D*ox5f+8$Q( zlL&A=JZDq0X(9=fn&EVE9R6;|bP}I9QSsJcg4v_Drwb?VcB$+ay{=r0TMdtrKYhiX z;7$#KKv;sTlQIm{%DIe(okfVh6;`zm96+05eBmN(EoOPe_8s(5{-fdi6f?QlRsJ+%Gc~kPch22!Pv(9I+m6}2bpjyjn-MQT9kVwf@>f@2THKVZsM4fME<(al z${zY~v~B<4jws7&gg>oJ=jCS2!Y7(;in+EaOOFKY(81wGU%B7@H5FsGqWMpyVsAu! zAAXa#6DAOi%$YXDE}u5tVVdSpQtHXZKjbHNC(^FylX4^T3*yxm*n$;x5NCYfd0Mie zXR7<@N&$T)evsLIvn!s9^ zU?Ir>j#e*hUJ{bJ(&@tfYww%@Bra4x8kIn!teY<{Vh{FlS=0 zto!OSa`juPjoYE+4(kmMDv(uv+qwNLq3~O!?4L4?Hnri4fJKF^J8QjjEEywFkovgK zIN*D1!RP5kub%k6L+A&;^I&4Ums4}6bkT_cHLywV+U*CXj0felJ#G}rjQf?fLWhqQ z8J$*#cPWl^h&<{)Hq-BV6r?0VaT%{K^@5x^-hj<=GXfw|0$ivR3b(ownOF7IRQnag zuf>KsnqmqjsK;^LA8gLf`jaIzt4^l3wi2w`3z&}9y6O#c=Ob>0av3LPVBZWCdx6m% zorzBOUpv7z4Xvn)D_cTLRt;Wx1yd-a8Tl%=98z*R-8_2*|5s1kaZ(}ntr@_nb8cn- z@_5qrZ|dQ1h=nH_O@2!>veUy8jgqB-{D9hYTG1LYa(zBzr*?}HJN8A5eZb9~_wQ!R zP}kw(CEf9Q*J*15i@qto;6DIIR zUu9?4#}LWs+{j)>PN*AcLK-`pWjcMiU?2EIalLJ>f{oVhbiOlv=#|#+w1!@hU_=AI zjutT&epjQLA!KnxX~+&XiUB)t^@WWjrdkqt@hn);zxmdTd>s7YV{W(wR;hFzdVf~M zfp3<=pqmwu7nx(h-Nl*i0is2PyhGth<`EbHLAQ+NPGPY9<}4hrm&wM8AJxMZ>t3dC zO|R5LMmRl>8yZPk*9RF%K+%~p+TwV{q%@((I2KsO^z_!?3hRhN8k_jQ?tJwxRz5^vOJTJJ-tV<6~dTP_u~JXzQ$sC?ac(#?~^`=yN0*4N0(JlHS3{m=OJ zciQ&{&ER^4UhWur9*?ali=dUr5CHv(FR3Fr#F3u)6p+FWSe&86?L zBgZXJB-7EwBBj9d`IvIm6xCb|LO4u~6wnDleByi`0;$Cvi2hrb4pSo{e9OZPwqY3E zt!ZtTp3@Es7n5rhhXS;WvU&x#O1pLGc%>UobX*KJD*0BV8=trc9c&6BxcCknf5 zNkZ5AcxmLg>LFlqhCB7&$ma@x#1L5pyWT@XJ^C+T3_KmDMJCi*qS`>FhcJhbow9ol;Why@ZW&$8vp*=^&-VjRS(o4^}9Mz(yxH!4hk4 zQO?SIVQ-0G>tA880^V_RCB9qPmVp)=PHpIX!fCGyG&uqa9avt?F`nBct=sL`7Rujz zw==6`EY(R=8VV~N7!tcBv_jH3{TmAx{m8;Uq;nr5aLjj^FLy=v+4MdOK_9%PC+&Z-`a&K zPgAx~A6K`XPj|VTh!DO5mnWr~<7UVFvyG2&Q@9Ax)0R3r=e~H!%OULSujUA`xqzefqKRQ_p3UP2U>6lo>tr?-zm& zq?blg2TW9j^r2~Xe|gj=ADuq-yye#4 zcozRh$1Dob36b>oE*@{t>S%DyXLNWvtoARY!#q-(g}TjGrVi8JRQWJfg)}Np&A`s5C-nw#2Ee(&Q)c-sTOlbgD20hR z^deZ=rvz@7ahEZcPfo3q>^<>8JbTXcw8ioZHncS8v~#Rna$ed%ks~79NjLeBQ{7CU zcWM15fgXQFzQU4|+*EJpgdL&hp04ey8s6;wsmZ zrEv8mE)ROS?+#9=TM^b$yy$x|CEx$u1K&G&VoWT4xeh-*sc=;$wmj`_`K^>RE-nVM zY8toYmMZf{BvjAv=IC~4#6Z*}A;>@pf($V*G6+2=KwO`n1Q^_cWDh3y3#8eRB_WJ_ zs_&qUej^y|)tis{+&q*ac`YbA>Veevco7F2u$T;fG5125q+&X7tYylrOGn8o`ElH8 zDP(FDiS62_)9&{iQNn6NVbeh{_Ia4w9Bkv*fzInuDAiF*$H=jRke6;qx?4@3RW`Oo zqsPscEt^$EeoOkEMbA}GUq2pm$-o?J*ih4ZW_MguvsZJs{=GHv*@($jL8!>{NdKg` zuvqh5tBO~3NdXUH++wjVVh9m9{g>TlzcLHI%Pjn^+w2E5aM^9fE=DbO(@Mgfjuv?v ztuvaw%jQKB3BYd=cCq2;H`MKCbyg|$JavmQ6isq}CVEmjBKt!^Q<|f@#P?vX)EB2; za@JmrN;UXQSJvqi9@x+wE^VcgJuDwBgxAnysH_SFV{2P7HB$pTud!xa*Jp6w1>B}p z3l&dLT?wO~w7w!z*gbiqFz6^532LDr{RbIV`LONxt_E9U3|wQU z14dKA#-{EeW8N&!N*!gjabtWTj2|9i^oW<{B#`LHYvV$#pwCK+0+*UL=NUhoymo)> z{i#hm1QK-9knw||ByStZojYhPL(R~VXLmqxcpy~8u;!>1YJ<);65Q9=_}3J|qO*o1 zbEH|jI2l;(N2MO=Zk{<0r3tm$#+X=QTi!QP+3LGPq^=^WpmXCCR!~K};sG zbF%opiyZD-2tWoJth~z8{pF8)F}1s@PqFvWJ!j;|7o>r2|{P2-~f#`0HBJtPG%^Rj7vaXN=xio1gr?0Q< z0vpBPY2icOpv>Jh&r}I!Pb{pv7;ipzxSMr2w7JHj72wF>x`RTWD{M6iIkH<^BF+z>y z*5roFqsG$fazl`Su^=&Sh!`*yD9QZ>6@&3T^~Y>9#)MBzlChjB9Ly+Ev4rXz%;Hk9 zhx*OgceT0K@YoXCwD~CBl1Y9Q453)a3aHYrbHy(XRz6=2;J2~4AW8`29~jIoU8u~0 z7zJAlb%w`E+oo_E?qw4sGlhSmfY7a*&V9dy#W7ouZeA&t$M^_^w`WUKg z7GysQ?L9e{5}r-KEjabbUTb{+-uohdg#Y~AXv4K~`N9LEsJ@DdJIRGEtc_MyBKHe% zYe&6Y-@A4a>fT^yor_nAE$t5J(l%)WC;{+hf#2|@fw%^ev($otXdh{~!@~RxjIqPS z7&_@xFm}O`q1>3!wBdoV`*d^k=jI%We54A);YcR-6e!)Zg1Aww!dFKCpN)fr%e!0~ zyEk=Hwc&g#I)c-_X$#9cxp{ZnVY;>!1hr&gsX08TWnrmwi{^@%EJkLsHy_^T=oz=T zQ^@ml*p+T6dlVG?jw(miLQ$1@7+s@9_^m=haC|Ap>xie~E?^vsz@aE0ER4Ui%NidbJv@mrR>GQdHcxN+M1r?zNelhBQ z!l?hIaxJsrQ&{|DR?9GKYQBdY6O`0_cWqm5)OUOBIqGk^pe^x1D(|-I@x68XuXibV z>$0C$>z|las431k>Dud=R#1fKI`$8snU(sfU6)&&Cv`;pocB& z`G==F(Z~2AgjC3qNKk*U7zYRmGmuuyA0b=e>J=_q0dVz-kgag_ijb{v^=i(MM2aJQ zf5G4VmN34u6gsW34C27Z$W}oHGiST2(ev6y$s{R0k6(V*Ub@UXWl^R0ysNQllJ~vW z_D*BL@r=zejP}Bp_@Xg_im#<3=h%VW>rpsWW$Vh*V~@PaI_d7H!{nC^%u^8?nA*4% zD>djEexF?)U&{w<*0LSM9|si|2o+Bb+x0Ze7P9R<@WPfnQXAgyuuhR2j)~s2WLt)a zz!6A46T--|F(!EKNL|csUbNfA+*>=9~BTkdOt*{jXbiUN3{zZ{Av@ zzc-@x(87kD>rSpXmp5J^``Q=zoZoQEv~F}lU$__5Z>$WJb{1I ztA5xMBbyCLfXPjG$5{Jc=@@%YfWg(QjvA|@jX7<0Cpvhz3E{%x-0`yH^}rWyFOF?P zmKJI4#YGt~cowjkQO%n=P4<~!rpZX7&rQ_#pqKBN7CHyC^4m0yj}i?zWaNhE9%Tyg zu)1iAwj?vSF3{_C-xRxo#X&dvd!*SrOJMpYFrWKoiM5i+(1~bO&6_BY3`>B)L(sT1K#U#b$o^=q-6&Z1xgX+ zTG_GQUYn(vwX-;Nu|J z5(R8LV8it{Pm|WHDnxN#ZR3@g{7nl){ps%N*Bpd@o`diQRjnwpfhTYH>TQ7jZsv-hgZDBpG1T_(;)UO=np$5EiFAD zF1Fp@9C@Q!Df~Zf?B5aye>j)MduVplv>ML5GnKH0*gF5eT=`lhr1$qjaIW=2pqnxM zr##58{vtIYCzTUlzHLm#QP;tgPE2j?w124=VY9~31iTMqE4^V8X)`vyV=?aLa7hjr z<@7GKIdnkI0<%1i^G=`&@n_C?)PaMrCC_+2_VZ4TYDZ0O_fh=E^XGiMS+7pX4_`OIGq>XLbPo!!_Gw~{VuZyWk2r4eyHAF z_CCTVfc;bUV6J4P zqd31`aEjA=+f#!zDvgt?^q?T$GRk&9NhtEb%ipnc#Xnq`{Zqyxz0MC0;G<`FfD`;! z@JTd5)MRGx!8D%`Wn6eKFDcYI1wMO5O(H7196pN15?;r>0iR5R125~s2i3fx`3s93 z*Olf{Jf#-A)Kg%}N1-38@AI1^QHKp(S4xD>NlI>FCW3Ov@%i6p1WChz+#Lvi1Rs}# z@l>@2K39nb{gpF(*wQ@%1mqRaTfo!6KU+pNM$8BferbI)R&F~r$u)DLsX>(8(ThDP za`v`{ + + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_more_audio_mode_action_icon_active.svg b/polyv/src/main/resources/base/media/plv_media_player_more_audio_mode_action_icon_active.svg new file mode 100644 index 0000000..7b0b967 --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_more_audio_mode_action_icon_active.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_more_audio_mode_action_icon_inactive_land.svg b/polyv/src/main/resources/base/media/plv_media_player_more_audio_mode_action_icon_inactive_land.svg new file mode 100644 index 0000000..6fd00f6 --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_more_audio_mode_action_icon_inactive_land.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_more_audio_mode_action_icon_inactive_port.svg b/polyv/src/main/resources/base/media/plv_media_player_more_audio_mode_action_icon_inactive_port.svg new file mode 100644 index 0000000..04b7bb3 --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_more_audio_mode_action_icon_inactive_port.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_more_download_action_icon_land.svg b/polyv/src/main/resources/base/media/plv_media_player_more_download_action_icon_land.svg new file mode 100644 index 0000000..e09545c --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_more_download_action_icon_land.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_more_download_action_icon_port.svg b/polyv/src/main/resources/base/media/plv_media_player_more_download_action_icon_port.svg new file mode 100644 index 0000000..6d1f074 --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_more_download_action_icon_port.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_more_subtitle_action_icon_active_land.svg b/polyv/src/main/resources/base/media/plv_media_player_more_subtitle_action_icon_active_land.svg new file mode 100644 index 0000000..90192c2 --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_more_subtitle_action_icon_active_land.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_more_subtitle_action_icon_active_port.svg b/polyv/src/main/resources/base/media/plv_media_player_more_subtitle_action_icon_active_port.svg new file mode 100644 index 0000000..e6488fc --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_more_subtitle_action_icon_active_port.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_more_subtitle_action_icon_inactive_land.svg b/polyv/src/main/resources/base/media/plv_media_player_more_subtitle_action_icon_inactive_land.svg new file mode 100644 index 0000000..6fb5440 --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_more_subtitle_action_icon_inactive_land.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_more_subtitle_action_icon_inactive_port.svg b/polyv/src/main/resources/base/media/plv_media_player_more_subtitle_action_icon_inactive_port.svg new file mode 100644 index 0000000..6078031 --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_more_subtitle_action_icon_inactive_port.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_play_button_icon_to_pause.svg b/polyv/src/main/resources/base/media/plv_media_player_play_button_icon_to_pause.svg new file mode 100644 index 0000000..1cf08b6 --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_play_button_icon_to_pause.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_play_button_icon_to_play.svg b/polyv/src/main/resources/base/media/plv_media_player_play_button_icon_to_play.svg new file mode 100644 index 0000000..f6f7027 --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_play_button_icon_to_play.svg @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_restart_icon.svg b/polyv/src/main/resources/base/media/plv_media_player_restart_icon.svg new file mode 100644 index 0000000..0c009df --- /dev/null +++ b/polyv/src/main/resources/base/media/plv_media_player_restart_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/base/media/plv_media_player_screenshot_icon.png b/polyv/src/main/resources/base/media/plv_media_player_screenshot_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..72e9ec7fe7435e46a5b365e7caccfa039df3eb61 GIT binary patch literal 1204 zcmeAS@N?(olHy`uVBq!ia0vp^jzH|m!3-ort9DBPsf__XA+A9B|BwRPXB5o=I@+ov z$S;`TH`k+YA78WG&GS{3;d?$kbzi2Fjgc_($w_UEHN`iMZd^JyR#W`*l}l?@ESS}6 zu6uUp*786%rgxzcS+$883=GT_o-U3d5r?Nve_6H4K%mVtVpT|i^OdZ-A~S%p>-VE1&7tNdETAjuHMKg_$M%NtHz28?NgeV1!pI%o59^E8kya- zwMwvYF{`2NrXP$=_InRpYU%mEyXipPRn9#vI(!{&$F&l zn5o&uDs?BFiILgU-PCmH!Ie+BugHk}s1k9%ykoZd0sjfVSZ2jc+HTXTsc?D9)@HYB z=fo=5kDR;ln(ypCBku>N7*?iLo^<;CQ$4r-FYDa(Z4ErZVfnl#w5&@iTX!f`Y{_f9 zV6dr>(a`y-8N&&F)>l3$iz4>VMd$tX}w-fnW35#s&j}Eg!bN+H@gAX<_z@$TbSfBwQzPW8@7cg>6E7z@IF6g)|to_nehjsIglphW^q*NpRxWv6_43<|} z_oi^MiSdip?;S~jyV8#>+GbmNgkNU!sS9Z*XB?I^*uKZ%1@jFhUR@nA)fq`ytUD)n z+PS~|{cif}XWo^J=fzWNAH@BcsBRYaPC?M!q`mupRLldej?yWymvwX=PMmchMpD+Q z(>3{m{In?k>GiemcTzecoar!h3-^I#)$|=(_PD&MX=h^kfFLib@IFwtS@qbQ9 z>P&~u*j>dTk_ys6)BIw%7O-S`m*)J=%&^<%C>Q#MUF43ywoQA&-_2Wlvoh_Bdp>hy zNA}q`vm+ZuTb~Lcb(TXqbKj?`)pm-Poeq_U2{AJGZoFoz=xH$vH1X*S0n%sEL1S{ru$6c2&N2 zeI;*mrFPC2n^?6;R=Xp}=lZ=0eMwVh?;Sq(v~TQ-I$m;WX_{Mn%v?3Ys@LqI3k<^R zA203s5MnUb + + + + + + + + + + + + \ No newline at end of file diff --git a/polyv/src/main/resources/en_US/element/string.json b/polyv/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000..9659948 --- /dev/null +++ b/polyv/src/main/resources/en_US/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} \ No newline at end of file diff --git a/polyv/src/main/resources/zh_CN/element/string.json b/polyv/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000..9659948 --- /dev/null +++ b/polyv/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} \ No newline at end of file diff --git a/products/expert/oh-package-lock.json5 b/products/expert/oh-package-lock.json5 index 31a3a06..3009694 100644 --- a/products/expert/oh-package-lock.json5 +++ b/products/expert/oh-package-lock.json5 @@ -5,14 +5,58 @@ "lockfileVersion": 3, "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", "specifiers": { + "@aliyun/error@1.0.2": "@aliyun/error@1.0.2", + "@aliyun/httpdns@1.1.1": "@aliyun/httpdns@1.1.1", + "@aliyun/logger@1.0.2": "@aliyun/logger@1.0.2", "@itcast/basic@../../commons/basic": "@itcast/basic@../../commons/basic", + "@ohos/crypto-js@2.0.3": "@ohos/crypto-js@2.0.4", + "@ohos/crypto-js@^2.0.2": "@ohos/crypto-js@2.0.4", "@ohos/crypto-js@^2.0.4": "@ohos/crypto-js@2.0.4", + "@ohos/httpclient@2.0.2": "@ohos/httpclient@2.0.2", + "@polyvharmony/httpdns-api@1.0.2": "@polyvharmony/httpdns-api@1.0.2", + "@polyvharmony/httpdns-impl-ali@1.0.2": "@polyvharmony/httpdns-impl-ali@1.0.2", + "@polyvharmony/httpdns-impl-local@1.0.2": "@polyvharmony/httpdns-impl-local@1.0.2", + "@polyvharmony/media-player-business@2.5.0": "@polyvharmony/media-player-business@2.5.0", + "@polyvharmony/media-player-core-api@2.5.0": "@polyvharmony/media-player-core-api@2.5.0", + "@polyvharmony/media-player-core-ijk@2.5.0": "@polyvharmony/media-player-core-ijk@2.5.0", + "@polyvharmony/media-player-foundation@2.5.0": "@polyvharmony/media-player-foundation@2.5.0", + "@polyvharmony/media-player-sdk-addon-cache-down@2.5.0": "@polyvharmony/media-player-sdk-addon-cache-down@2.5.0", + "@polyvharmony/media-player-sdk@2.5.0": "@polyvharmony/media-player-sdk@2.5.0", + "base64-js@^1.5.1": "base64-js@1.5.1", "home@../../features/Home": "home@../../features/Home", + "media-player-common@../../polyv": "media-player-common@../../polyv", "mypage@../../features/mypage": "mypage@../../features/mypage", + "pako@^2.1.0": "pako@2.1.0", "register@../../features/register": "register@../../features/register", + "scene_single_video@../../scene_single_video": "scene_single_video@../../scene_single_video", "utils@../../commons/utils": "utils@../../commons/utils" }, "packages": { + "@aliyun/error@1.0.2": { + "name": "@aliyun/error", + "version": "1.0.2", + "integrity": "sha512-hESGtOCjFmyWRAWjEwJ7izFJsOXDto1DGvOrFO3II6mPEWvpow3UMpYqeBVzLvBUcdFtFkoF/rCEPLHXOK8nmQ==", + "resolved": "https://repo.harmonyos.com/ohpm/@aliyun/error/-/error-1.0.2.har", + "registryType": "ohpm" + }, + "@aliyun/httpdns@1.1.1": { + "name": "@aliyun/httpdns", + "version": "1.1.1", + "integrity": "sha512-VyjxuvnlOiaijdGRXnK0q4NQO8IX5E2zETap/9JObpS2vjcBWAiok5MxprhoXl9YeAgv70pnOD0M70BqHq8++w==", + "resolved": "https://repo.harmonyos.com/ohpm/@aliyun/httpdns/-/httpdns-1.1.1.har", + "registryType": "ohpm", + "dependencies": { + "@aliyun/logger": "1.0.2", + "@aliyun/error": "1.0.2" + } + }, + "@aliyun/logger@1.0.2": { + "name": "@aliyun/logger", + "version": "1.0.2", + "integrity": "sha512-8iRVhBv1GHy1J7swZWDdDghcUzxieOc+Bf9UVUX4n8D99EPFo6GnM97ObfbF6f6JREByQ9YSH+dsxYTFNM5e1g==", + "resolved": "https://repo.harmonyos.com/ohpm/@aliyun/logger/-/logger-1.0.2.har", + "registryType": "ohpm" + }, "@itcast/basic@../../commons/basic": { "name": "@itcast/basic", "version": "1.0.0", @@ -26,13 +70,145 @@ "resolved": "https://repo.harmonyos.com/ohpm/@ohos/crypto-js/-/crypto-js-2.0.4.har", "registryType": "ohpm" }, + "@ohos/httpclient@2.0.2": { + "name": "@ohos/httpclient", + "version": "2.0.2", + "integrity": "sha512-acFEfQ9ZJti4KEYDBErCq0W85uc2khen41LIkBLVcfFXmgZAGeRKV+CPiSyG8v+5nUD7Wf2ncjBxxPxr6moqVg==", + "resolved": "https://repo.harmonyos.com/ohpm/@ohos/httpclient/-/httpclient-2.0.2.har", + "registryType": "ohpm", + "dependencies": { + "pako": "^2.1.0", + "@ohos/crypto-js": "^2.0.2", + "base64-js": "^1.5.1" + } + }, + "@polyvharmony/httpdns-api@1.0.2": { + "name": "@polyvharmony/httpdns-api", + "version": "1.0.2", + "integrity": "sha512-p3vd3i4oClAp/rf2RD3vIdKRSisabOB/+K+wYB9nHuf5ZDTw3Yv4o+dLQewm0atqf00JGF3Y0IiwURCpryJX5Q==", + "resolved": "https://repo.harmonyos.com/ohpm/@polyvharmony/httpdns-api/-/httpdns-api-1.0.2.har", + "registryType": "ohpm" + }, + "@polyvharmony/httpdns-impl-ali@1.0.2": { + "name": "@polyvharmony/httpdns-impl-ali", + "version": "1.0.2", + "integrity": "sha512-DQGPBfTHZyGxcUJRBxRhge2HoXW3aZZjkjqy7KeI9bUXCnltq4dCBwz6GCldtoZduGvJDuv+HogZL3DA8BatvA==", + "resolved": "https://repo.harmonyos.com/ohpm/@polyvharmony/httpdns-impl-ali/-/httpdns-impl-ali-1.0.2.har", + "registryType": "ohpm", + "dependencies": { + "@polyvharmony/httpdns-api": "1.0.2", + "@aliyun/httpdns": "1.1.1" + } + }, + "@polyvharmony/httpdns-impl-local@1.0.2": { + "name": "@polyvharmony/httpdns-impl-local", + "version": "1.0.2", + "integrity": "sha512-KKK1PUmpudmwucbi0uZ+a56Bqryw2HlEI0hiA8mc5LjhLVZ49QxPX3ARZw3Sw/IRasWBvkSXuRpNrA8AuxchKA==", + "resolved": "https://repo.harmonyos.com/ohpm/@polyvharmony/httpdns-impl-local/-/httpdns-impl-local-1.0.2.har", + "registryType": "ohpm", + "dependencies": { + "@polyvharmony/httpdns-api": "1.0.2" + } + }, + "@polyvharmony/media-player-business@2.5.0": { + "name": "@polyvharmony/media-player-business", + "version": "2.5.0", + "integrity": "sha512-ta9YBq8cvwNPuRwQpAopodFB0WHbAfRUhgosGqT+UDEHZtlXsajLFmplPhoqYpFb649FVe6vDjVdHgsp6bKmtQ==", + "resolved": "https://repo.harmonyos.com/ohpm/@polyvharmony/media-player-business/-/media-player-business-2.5.0.har", + "registryType": "ohpm", + "dependencies": { + "@polyvharmony/media-player-core-api": "2.5.0", + "@polyvharmony/media-player-foundation": "2.5.0", + "@ohos/httpclient": "2.0.2", + "@ohos/crypto-js": "2.0.3" + } + }, + "@polyvharmony/media-player-core-api@2.5.0": { + "name": "@polyvharmony/media-player-core-api", + "version": "2.5.0", + "integrity": "sha512-23Z4p0nPWAGJuifAr5qfhB39b0RdU7LQnOKPJTtPX4fc3qyGDoyQ7Y4/e+WulTm/JaDgsQ7xTn+Hmq+ryNl5cA==", + "resolved": "https://repo.harmonyos.com/ohpm/@polyvharmony/media-player-core-api/-/media-player-core-api-2.5.0.har", + "registryType": "ohpm", + "dependencies": { + "@polyvharmony/media-player-foundation": "2.5.0" + } + }, + "@polyvharmony/media-player-core-ijk@2.5.0": { + "name": "@polyvharmony/media-player-core-ijk", + "version": "2.5.0", + "integrity": "sha512-LieKq0vm8QpTNLzw+QSVRok9xoJV0fDVR4Aw1bRClnpd4Mmb99JvwuW6isCRYsrZJievsxbbNet/qSqjb+OJhw==", + "resolved": "https://repo.harmonyos.com/ohpm/@polyvharmony/media-player-core-ijk/-/media-player-core-ijk-2.5.0.har", + "registryType": "ohpm", + "dependencies": { + "@polyvharmony/media-player-core-api": "2.5.0", + "@polyvharmony/media-player-foundation": "2.5.0" + } + }, + "@polyvharmony/media-player-foundation@2.5.0": { + "name": "@polyvharmony/media-player-foundation", + "version": "2.5.0", + "integrity": "sha512-P1beEIZs2ySGrNGWo2RRi8/hQopZkLE6Cdi+53/UJI4iXFeciWZQMXold3KBqlMzNm369BEysaoprtCsWLMC8Q==", + "resolved": "https://repo.harmonyos.com/ohpm/@polyvharmony/media-player-foundation/-/media-player-foundation-2.5.0.har", + "registryType": "ohpm", + "dependencies": { + "@ohos/httpclient": "2.0.2", + "@ohos/crypto-js": "2.0.3", + "@polyvharmony/httpdns-api": "1.0.2" + } + }, + "@polyvharmony/media-player-sdk-addon-cache-down@2.5.0": { + "name": "@polyvharmony/media-player-sdk-addon-cache-down", + "version": "2.5.0", + "integrity": "sha512-wN6OQaQm65GPL39nVWYx9vRyua6TflbIXddoataxHrlW2Nf7N3PJZjmFW0zaTPC5EyzsnmkRh/4zN2zHvyQDUA==", + "resolved": "https://repo.harmonyos.com/ohpm/@polyvharmony/media-player-sdk-addon-cache-down/-/media-player-sdk-addon-cache-down-2.5.0.har", + "registryType": "ohpm", + "dependencies": { + "@polyvharmony/media-player-business": "2.5.0", + "@polyvharmony/media-player-foundation": "2.5.0", + "@polyvharmony/media-player-core-ijk": "2.5.0" + } + }, + "@polyvharmony/media-player-sdk@2.5.0": { + "name": "@polyvharmony/media-player-sdk", + "version": "2.5.0", + "integrity": "sha512-Of8xCFhAD0O8s7Q0jzwSf4potYJJcI/j+MrNvU23sgXnEAX6DG7azIehuCn+k6AMHvxgLF1LlEMp17v3pkc/XA==", + "resolved": "https://repo.harmonyos.com/ohpm/@polyvharmony/media-player-sdk/-/media-player-sdk-2.5.0.har", + "registryType": "ohpm", + "dependencies": { + "@polyvharmony/media-player-business": "2.5.0", + "@polyvharmony/media-player-core-api": "2.5.0", + "@polyvharmony/media-player-foundation": "2.5.0" + } + }, + "base64-js@1.5.1": { + "name": "base64-js", + "version": "1.5.1", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "resolved": "https://repo.harmonyos.com/ohpm/base64-js/-/base64-js-1.5.1.tgz", + "shasum": "1b1b440160a5bf7ad40b650f095963481903930a", + "registryType": "ohpm" + }, "home@../../features/Home": { "name": "home", "version": "1.0.0", "resolved": "../../features/Home", "registryType": "local", "dependencies": { - "@itcast/basic": "file:../../commons/basic" + "@itcast/basic": "file:../../commons/basic", + "@polyvharmony/media-player-sdk": "2.5.0", + "@polyvharmony/media-player-sdk-addon-cache-down": "2.5.0", + "scene_single_video": "file:../../scene_single_video", + "media-player-common": "file:../../polyv" + } + }, + "media-player-common@../../polyv": { + "name": "media-player-common", + "version": "2.5.0", + "resolved": "../../polyv", + "registryType": "local", + "dependencies": { + "@polyvharmony/media-player-sdk": "2.5.0", + "@polyvharmony/media-player-sdk-addon-cache-down": "2.5.0" } }, "mypage@../../features/mypage": { @@ -44,6 +220,14 @@ "@itcast/basic": "file:../../commons/basic" } }, + "pako@2.1.0": { + "name": "pako", + "version": "2.1.0", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "resolved": "https://repo.harmonyos.com/ohpm/pako/-/pako-2.1.0.tgz", + "shasum": "266cc37f98c7d883545d11335c00fbd4062c9a86", + "registryType": "ohpm" + }, "register@../../features/register": { "name": "register", "version": "1.0.0", @@ -53,6 +237,16 @@ "@itcast/basic": "file:../../commons/basic" } }, + "scene_single_video@../../scene_single_video": { + "name": "scene_single_video", + "version": "2.5.0", + "resolved": "../../scene_single_video", + "registryType": "local", + "dependencies": { + "media-player-common": "file:../polyv", + "@polyvharmony/media-player-sdk": "2.5.0" + } + }, "utils@../../commons/utils": { "name": "utils", "version": "1.0.0", diff --git a/products/expert/oh-package.json5 b/products/expert/oh-package.json5 index d7e038f..f16fc4b 100644 --- a/products/expert/oh-package.json5 +++ b/products/expert/oh-package.json5 @@ -10,6 +10,14 @@ "utils": "file:../../commons/utils", "mypage":"file:../../features/mypage", "home": 'file:../../features/Home', - "register": 'file:../../features/register' + "register": 'file:../../features/register', + "scene_single_video": "file:../../scene_single_video", + "media-player-common": "file:../../polyv", + "@polyvharmony/media-player-sdk": "2.5.0", + "@polyvharmony/media-player-core-ijk": "2.5.0", + "@polyvharmony/media-player-sdk-addon-cache-down": "2.5.0", + "@polyvharmony/httpdns-api": "1.0.2", + "@polyvharmony/httpdns-impl-local": "1.0.2", + "@polyvharmony/httpdns-impl-ali": "1.0.2" } } \ No newline at end of file diff --git a/products/expert/src/main/ets/entryability/EntryAbility.ets b/products/expert/src/main/ets/entryability/EntryAbility.ets index 966c723..2b86595 100644 --- a/products/expert/src/main/ets/entryability/EntryAbility.ets +++ b/products/expert/src/main/ets/entryability/EntryAbility.ets @@ -2,7 +2,8 @@ import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.Ab import { hilog } from '@kit.PerformanceAnalysisKit'; import { window } from '@kit.ArkUI'; import notificationManager from '@ohos.notificationManager'; - +import { PLVMediaPlayerStartUp } from '../startup/PLVMediaPlayerStartUp'; +import contextConstant from '@ohos.app.ability.contextConstant'; const DOMAIN = 0x0000; export default class EntryAbility extends UIAbility { @@ -12,6 +13,10 @@ export default class EntryAbility extends UIAbility { this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + + + this.context.area = contextConstant.AreaMode.EL2 + PLVMediaPlayerStartUp.start(this.context) } onDestroy(): void { diff --git a/products/expert/src/main/ets/pages/VideoPage/PLVMediaPlayerSingleVideoPage.ets b/products/expert/src/main/ets/pages/VideoPage/PLVMediaPlayerSingleVideoPage.ets new file mode 100644 index 0000000..bb2b2ce --- /dev/null +++ b/products/expert/src/main/ets/pages/VideoPage/PLVMediaPlayerSingleVideoPage.ets @@ -0,0 +1,72 @@ +import { PLVMediaPlayerSingleVideoLayout } from 'scene_single_video'; +import { DependScope, Disposable, requireNotNull, runCatching } from '@polyvharmony/media-player-sdk'; +import { + createId, + PLVMediaPlayerScenes, + PLVMediaPlayerSingleVideoPageParam, + PLVMPPageControlViewModel, + PLVOrientationManager, + PLVOrientationManagerObserver +} from 'media-player-common'; +import common from '@ohos.app.ability.common'; +import window from '@ohos.window'; +import router from '@ohos.router'; +import { createDependScope } from '@polyvharmony/media-player-sdk' +import { + commonPageModule, +} from 'media-player-common' +@Entry +@Component +export struct PLVMediaPlayerSingleVideoPage { + @Provide pageDependScope: DependScope = createDependScope(commonPageModule) + // @Consume pageDependScope: DependScope + private param: PLVMediaPlayerSingleVideoPageParam = router.getParams() as PLVMediaPlayerSingleVideoPageParam + private context = getContext(this) as common.UIAbilityContext + private pageControlViewModel: PLVMPPageControlViewModel = this.pageDependScope.get(PLVMPPageControlViewModel) + private onBackPressDisposable: Disposable | undefined = undefined + private readonly plv_media_player_single_video_background: string = createId() + + aboutToAppear(): void { + requireNotNull(this.param, () => "param is null") + this.onBackPressDisposable = this.pageControlViewModel.onBackPressHandler.register(10, () => this.onBackPress()) + runCatching(async () => { + const windowInstance = await window.getLastWindow(this.context) + windowInstance.setWindowKeepScreenOn(true) + }) + } + + build() { + Stack() { + // 背景图 + // Image($r('app.media.plv_media_player_video_item_background_portrait')) + // .id(this.plv_media_player_single_video_background) + // .objectFit(ImageFit.Cover) + // .expandSafeArea(undefined, [SafeAreaEdge.BOTTOM]) + + PLVMediaPlayerSingleVideoLayout({ + mediaResource: this.param.mediaResource, + enterFromDownloadCenter: this.param.enterFromDownloadCenter + }) + + // 屏幕方向监听器 + PLVOrientationManagerObserver() + } + } + + onBackPress(): boolean { + if (!PLVOrientationManager.getInstance().isPortrait.value) { + PLVOrientationManager.getInstance().requestOrientation('port'); + return true; + } + return false; + } + + aboutToDisappear(): void { + this.onBackPressDisposable?.dispose() + runCatching(async () => { + const windowInstance = await window.getLastWindow(this.context) + windowInstance.setWindowKeepScreenOn(false) + }) + } + +} \ No newline at end of file diff --git a/products/expert/src/main/ets/startup/PLVMediaPlayerStartUp.ets b/products/expert/src/main/ets/startup/PLVMediaPlayerStartUp.ets new file mode 100644 index 0000000..e595b46 --- /dev/null +++ b/products/expert/src/main/ets/startup/PLVMediaPlayerStartUp.ets @@ -0,0 +1,104 @@ +import { + IPLVKVStore, + Logger, + PLVKVStore, + PLVMediaPlayerAppContext, + PLVMediaPlayerFactory, + PLVMediaPlayerHttpDns, + PLVMediaPlayerLogger, + runCatching +} from '@polyvharmony/media-player-sdk'; +import { PLVMediaPlayerCoreIjkProvider } from '@polyvharmony/media-player-core-ijk'; +import { Context } from '@ohos.abilityAccessCtrl'; +import distributedKVStore from '@ohos.data.distributedKVStore'; +import hilog from '@ohos.hilog'; +import { PLVMediaDownloaderManager, PLVMediaDownloadSetting } from '@polyvharmony/media-player-sdk-addon-cache-down'; +import { PLVLocalDnsService } from '@polyvharmony/httpdns-impl-local'; +import { PLVHttpDnsService } from '@polyvharmony/httpdns-impl-ali'; +import { PLVHttpDnsManager } from '@polyvharmony/httpdns-api'; + +export class PLVMediaPlayerStartUp { + private static isInit = false + + static start(context: Context) { + if (PLVMediaPlayerStartUp.isInit) { + return + } + PLVMediaPlayerStartUp.isInit = true + + PLVMediaPlayerAppContext.getInstance().setupAppContext(context) + PLVMediaPlayerLogger.loggerImpl = new HiLogImpl() + PLVKVStore.setupImplement(new PLVKVStoreOhosImpl(context)) + PLVMediaPlayerFactory.getInstance().register(PLVMediaPlayerCoreIjkProvider.getInstance()) + PLVMediaDownloaderManager.getInstance().init(PLVMediaDownloadSetting.defaultSetting(context)) + + PLVHttpDnsManager.getInstance().logger = (tag: string, message: string) => { + hilog.info(0x0000, tag, message.substring(0, 1000)) + } + PLVMediaPlayerHttpDns.getInstance().register(PLVLocalDnsService.getInstance()) + PLVMediaPlayerHttpDns.getInstance().register(PLVHttpDnsService.getInstance()) + PLVMediaPlayerHttpDns.getInstance().enable = true + } +} + +class HiLogImpl implements Logger { + debug(tag: string, message: string) { + hilog.debug(0x0000, tag, message.substring(0, 1000)) + } + + info(tag: string, message: string) { + hilog.info(0x0000, tag, message.substring(0, 1000)) + } + + warn(tag: string, message: string) { + hilog.warn(0x0000, tag, message.substring(0, 1000)) + } + + error(tag: string, message: string) { + hilog.error(0x0000, tag, message.substring(0, 1000)) + } +} + +class PLVKVStoreOhosImpl implements IPLVKVStore { + private static readonly kvStoreId = "PLVKVStoreOhosImpl" + + constructor(context: Context) { + this.kvStoreManager = distributedKVStore.createKVManager({ + context: context, + bundleName: context.applicationInfo.name + }) + this.ensureKvStore() + } + + private kvStoreManager: distributedKVStore.KVManager | undefined = undefined; + private kvStore: distributedKVStore.DeviceKVStore | undefined = undefined; + + async getValue(key: string): Promise { + if (!this.kvStore) { + await this.ensureKvStore() + } + + const result = await runCatching(this.kvStore?.get(key)) + if (result.success) { + return result.data as string + } else { + return undefined + } + } + + async setValue(key: string, value: string): Promise { + await this.ensureKvStore() + this.kvStore?.put(key, value) + } + + async delete(key: string): Promise { + await this.ensureKvStore() + this.kvStore?.delete(key) + } + + private async ensureKvStore() { + this.kvStore = await this.kvStoreManager?.getKVStore(PLVKVStoreOhosImpl.kvStoreId, { + securityLevel: distributedKVStore.SecurityLevel.S1 + }) as distributedKVStore.DeviceKVStore + } +} \ No newline at end of file diff --git a/products/expert/src/main/resources/base/profile/main_pages.json b/products/expert/src/main/resources/base/profile/main_pages.json index 9c3ad8e..cba9f23 100644 --- a/products/expert/src/main/resources/base/profile/main_pages.json +++ b/products/expert/src/main/resources/base/profile/main_pages.json @@ -19,6 +19,7 @@ "pages/MinePage/ChooseOfficePhone", "pages/MinePage/EditIntroductionPage", "pages/VideoPage/PlayBackPage", - "pages/VideoPage/VideoMorePage" + "pages/VideoPage/VideoMorePage", + "pages/VideoPage/PLVMediaPlayerSingleVideoPage" ] } \ No newline at end of file diff --git a/scene_single_video/.gitignore b/scene_single_video/.gitignore new file mode 100644 index 0000000..28ab5cf --- /dev/null +++ b/scene_single_video/.gitignore @@ -0,0 +1,11 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test +BuildProfile.ets +oh-package-lock.json5 +.npmrc +package.json +package-lock.json \ No newline at end of file diff --git a/scene_single_video/Index.ets b/scene_single_video/Index.ets new file mode 100644 index 0000000..0a3ca43 --- /dev/null +++ b/scene_single_video/Index.ets @@ -0,0 +1 @@ +export * from './src/main/ets/scene/single/PLVMediaPlayerSingleVideoLayout' diff --git a/scene_single_video/build-profile.json5 b/scene_single_video/build-profile.json5 new file mode 100644 index 0000000..d9ca574 --- /dev/null +++ b/scene_single_video/build-profile.json5 @@ -0,0 +1,28 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./obfuscation-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + } + ] +} diff --git a/scene_single_video/hvigorfile.ts b/scene_single_video/hvigorfile.ts new file mode 100644 index 0000000..4218707 --- /dev/null +++ b/scene_single_video/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/scene_single_video/obfuscation-rules.txt b/scene_single_video/obfuscation-rules.txt new file mode 100644 index 0000000..985b2ae --- /dev/null +++ b/scene_single_video/obfuscation-rules.txt @@ -0,0 +1,18 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope \ No newline at end of file diff --git a/scene_single_video/oh-package.json5 b/scene_single_video/oh-package.json5 new file mode 100644 index 0000000..e194f15 --- /dev/null +++ b/scene_single_video/oh-package.json5 @@ -0,0 +1,12 @@ +{ + "name": "scene_single_video", + "version": "2.5.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "MIT", + "dependencies": { + "media-player-common": "file:../polyv", + "@polyvharmony/media-player-sdk": "2.5.0" + } +} diff --git a/scene_single_video/src/main/ets/scene/single/PLVMediaPlayerSingleVideoLayout.ets b/scene_single_video/src/main/ets/scene/single/PLVMediaPlayerSingleVideoLayout.ets new file mode 100644 index 0000000..1396916 --- /dev/null +++ b/scene_single_video/src/main/ets/scene/single/PLVMediaPlayerSingleVideoLayout.ets @@ -0,0 +1,205 @@ +import { + createDependScope, + DependScope, + MutableObserver, + PLVMediaPlayerOptionEnum, + PLVMediaResource, + requireNotNull +} from '@polyvharmony/media-player-sdk'; +import { + commonItemModule, + createId, + getDisplayWindowHeight, + getDisplayWindowWidth, + isPortrait, + parent, + PLVMediaPlayerHandleOnEnterBackgroundComponent, + PLVMPDownloadItemViewModel, + PLVMPMediaControllerViewModel, + PLVMPMediaViewModel, + PLVOrientationManager, + toCenterOf, + toMiddleOf, + toTopOf +} from 'media-player-common'; +import { PLVMediaPlayerAuxiliaryLayout } from './component/PLVMediaPlayerAuxiliaryLayout'; +import { PLVMediaPlayerSingleVideoControllerLayout } from './layout/PLVMediaPlayerSingleVideoControllerLayout'; +import { PLVMediaPlayerSingleVideoFloatActionLayout } from './layout/PLVMediaPlayerSingleVideoFloatActionLayout'; + +@Preview +@Component +export struct PLVMediaPlayerSingleVideoLayout { + mediaResource?: PLVMediaResource + enterFromDownloadCenter: boolean = false + @Provide dependScope: DependScope = createDependScope(commonItemModule) + @Consume pageDependScope: DependScope + private viewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel); + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel); + private downloadItemViewModel: PLVMPDownloadItemViewModel = this.dependScope.get(PLVMPDownloadItemViewModel) + @State videoWidth: number = 0 + @State videoHeight: number = 0 + @State isPortrait: boolean = isPortrait() + @State isFloatActionLayoutVisible: boolean = false + private componentController: XComponentController = new XComponentController() + private observers: MutableObserver[] = [] + // + private readonly plv_media_player_single_video_xcomponent: string = createId() + private readonly plv_media_player_single_video_controller_layout: string = createId() + private readonly plv_media_player_single_video_container: string = createId() + private readonly plv_media_player_single_video_float_action_layout: string = createId() + private readonly plv_media_player_single_video_layout_root: string = createId() + private readonly plv_media_player_handle_on_enter_background_component: string = createId() + private readonly plv_media_player_auxiliary_layout: string = createId() + + // + + aboutToAppear(): void { + requireNotNull(this.mediaResource, () => 'mediaResource must not be null') + this.observeMediaViewState() + this.observeOrientation() + this.downloadItemViewModel.setDownloadActionVisible(!this.enterFromDownloadCenter) + this.setupMediaPlayer() + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isFloatActionLayoutVisible = viewState.isFloatActionLayoutVisible() + this.videoWidth = viewState.videoViewLocation.width + this.videoHeight = viewState.videoViewLocation.height + }).pushTo(this.observers) + } + + private setupMediaPlayer() { + this.viewModel.setAutoContinue(true) + this.viewModel.setPlayerOption([ + PLVMediaPlayerOptionEnum.ENABLE_ACCURATE_SEEK.value("1"), + PLVMediaPlayerOptionEnum.SKIP_ACCURATE_SEEK_AT_START.value("1"), + ]) + this.viewModel.setMediaResource(this.mediaResource) + } + + build() { + RelativeContainer() { + RelativeContainer() { + // 视频播放器 + XComponent({ + id: `plv_media_player_single_video_xcomponent`, + type: "surface", + libraryname: "plvplayer_xcomponent", + controller: this.componentController + }) { + } + .id(this.plv_media_player_single_video_xcomponent) + .width(this.videoWidth) + .height(this.videoHeight) + .alignRules({ + center: toCenterOf(parent), + middle: toMiddleOf(parent) + }) + .onLoad((xcomponent) => { + this.viewModel.setXComponent(xcomponent) + this.viewModel.onScreenshot = async () => { + return await this.getUIContext().getComponentSnapshot().get(this.plv_media_player_single_video_xcomponent) + } + }) + + // 播放控制皮肤 + PLVMediaPlayerSingleVideoControllerLayout() + .id(this.plv_media_player_single_video_controller_layout) + .width('100%') + .height('100%') + .alignRules({ + center: toCenterOf(parent), + middle: toMiddleOf(parent) + }) + } + .id(this.plv_media_player_single_video_container) + .width('100%') + .height(this.videoContainerHeight()) + .alignRules({ + top: toTopOf(parent) + }) + .backgroundColor('#000000') + + // 更多功能弹层布局 + if (this.isFloatActionLayoutVisible) { + PLVMediaPlayerSingleVideoFloatActionLayout() + .id(this.plv_media_player_single_video_float_action_layout) + .width('100%') + .height('100%') + } + + // 进入后台播放处理逻辑 + PLVMediaPlayerHandleOnEnterBackgroundComponent() + .id(this.plv_media_player_handle_on_enter_background_component) + .visibility(Visibility.None) + + // 广告播放器布局 + PLVMediaPlayerAuxiliaryLayout() + .id(this.plv_media_player_auxiliary_layout) + + } + .id(this.plv_media_player_single_video_layout_root) + .width('100%') + .height('100%') + } + + // + + private observeMediaViewState() { + this.viewModel.mediaInfoViewState.observe(() => { + this.updateVideoSize() + }).pushTo(this.observers) + } + + private observeOrientation() { + PLVOrientationManager.getInstance().isPortrait.observe((isPortrait) => { + this.isPortrait = isPortrait + this.updateVideoSize() + // 只有横屏有操作锁定,竖屏没有 + if (isPortrait) { + this.controllerViewModel.lockMediaController('unlock') + } + }).pushTo(this.observers) + } + + private updateVideoSize() { + const viewState = this.viewModel.mediaInfoViewState.value + if (!viewState) { + return + } + const containerWidth = getDisplayWindowWidth().vp + const containerHeight = this.isPortrait ? containerWidth / 16 * 9 : getDisplayWindowHeight().vp + const containerRatio = containerWidth / containerHeight + let videoWidth = containerWidth + let videoHeight = containerHeight + const videoRatio = viewState.videoSize.width() / viewState.videoSize.height() + // fit center + if (containerRatio > videoRatio) { + videoWidth = containerHeight / viewState.videoSize.height() * viewState.videoSize.width() + } else if (containerRatio < videoRatio) { + videoHeight = containerWidth / viewState.videoSize.width() * viewState.videoSize.height() + } + this.controllerViewModel.updateVideoViewLocation({ + width: videoWidth, + height: videoHeight, + offset: { + x: 0, + y: 0 + } + }) + } + + private videoContainerHeight() { + if (this.isPortrait) { + return getDisplayWindowWidth().vp / 16 * 9 + } else { + return '100%' + } + } + + // + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + this.dependScope.destroy() + } +} diff --git a/scene_single_video/src/main/ets/scene/single/component/PLVMediaPlayerAuxiliaryLayout.ets b/scene_single_video/src/main/ets/scene/single/component/PLVMediaPlayerAuxiliaryLayout.ets new file mode 100644 index 0000000..759c731 --- /dev/null +++ b/scene_single_video/src/main/ets/scene/single/component/PLVMediaPlayerAuxiliaryLayout.ets @@ -0,0 +1,152 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { + createId, + getDisplayWindowHeight, + getDisplayWindowWidth, + isPortrait, + parent, + PLVMediaPlayerAuxiliaryCountDownTextView, + PLVMPAuxiliaryViewModel, + PLVOrientationManager, + toCenterOf, + toEndOf, + toMiddleOf, + toTopOf +} from 'media-player-common' + +@Component +export struct PLVMediaPlayerAuxiliaryLayout { + @Consume dependScope: DependScope + private auxiliaryViewModel: PLVMPAuxiliaryViewModel = this.dependScope.get(PLVMPAuxiliaryViewModel) + @State isVisible: boolean = false + @State isImage: boolean = false + @State imageUrl: string = "" + @State videoWidth: number = 0 + @State videoHeight: number = 0 + @State isPortrait: boolean = isPortrait() + private observers: MutableObserver[] = [] + // + private readonly plv_media_player_auxiliary_advert_image: string = createId() + private readonly plv_media_player_auxiliary_advert_video_xcomponent: string = createId() + private readonly plv_media_player_auxiliary_count_down_text_view: string = createId() + + // + + aboutToAppear(): void { + this.auxiliaryViewModel.bind() + + this.observeMediaViewState() + this.observeOrientation() + + this.auxiliaryViewModel.auxiliaryInfoViewState.observe((viewState) => { + this.isVisible = viewState !== null + if (viewState === null) { + return + } + this.isImage = viewState.isImage + this.imageUrl = viewState.url + }).pushTo(this.observers) + + } + + build() { + RelativeContainer() { + // 视频播放器 + XComponent({ + id: `plv_media_player_auxiliary_video_xcomponent`, + type: "surface", + libraryname: "plvplayer_xcomponent" + }) { + } + .id(this.plv_media_player_auxiliary_advert_video_xcomponent) + .width(this.videoWidth) + .height(this.videoHeight) + .alignRules({ + center: toCenterOf(parent), + middle: toMiddleOf(parent) + }) + .onLoad((xComponent) => { + this.auxiliaryViewModel.setXComponent(xComponent) + }) + + // 图片类型广告 + if (this.isImage) { + Image(this.imageUrl) + .id(this.plv_media_player_auxiliary_advert_image) + .width('100%') + .height('100%') + .backgroundColor('#000000') + .objectFit(ImageFit.Contain) + .alignRules({ + center: toCenterOf(parent), + middle: toMiddleOf(parent) + }) + } + + // 广告倒计时 + PLVMediaPlayerAuxiliaryCountDownTextView() + .id(this.plv_media_player_auxiliary_count_down_text_view) + .alignRules({ + right: toEndOf(parent), + top: toTopOf(parent) + }) + + } + .backgroundColor('#000000') + .width('100%') + .height(this.videoContainerHeight()) + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + } + + // + + private observeMediaViewState() { + this.auxiliaryViewModel.auxiliaryVideoInfoViewState.observe(() => { + this.updateVideoSize() + }).pushTo(this.observers) + } + + private observeOrientation() { + PLVOrientationManager.getInstance().isPortrait.observe((isPortrait) => { + this.isPortrait = isPortrait + this.updateVideoSize() + }).pushTo(this.observers) + } + + private updateVideoSize() { + const viewState = this.auxiliaryViewModel.auxiliaryVideoInfoViewState.value + if (!viewState) { + return + } + const containerWidth = getDisplayWindowWidth().vp + const containerHeight = this.isPortrait ? containerWidth / 16 * 9 : getDisplayWindowHeight().vp + const containerRatio = containerWidth / containerHeight + let videoWidth = containerWidth + let videoHeight = containerHeight + const videoRatio = viewState.videoSize.width() / viewState.videoSize.height() + // fit center + if (containerRatio > videoRatio) { + videoWidth = containerHeight / viewState.videoSize.height() * viewState.videoSize.width() + } else if (containerRatio < videoRatio) { + videoHeight = containerWidth / viewState.videoSize.width() * viewState.videoSize.height() + } + this.videoWidth = videoWidth + this.videoHeight = videoHeight + } + + private videoContainerHeight() { + if (this.isPortrait) { + return getDisplayWindowWidth().vp / 16 * 9 + } else { + return '100%' + } + } + + // + + aboutToDisappear(): void { + this.auxiliaryViewModel.unbind() + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/scene_single_video/src/main/ets/scene/single/component/PLVMediaPlayerProgressSliderSingleLand.ets b/scene_single_video/src/main/ets/scene/single/component/PLVMediaPlayerProgressSliderSingleLand.ets new file mode 100644 index 0000000..fb9373e --- /dev/null +++ b/scene_single_video/src/main/ets/scene/single/component/PLVMediaPlayerProgressSliderSingleLand.ets @@ -0,0 +1,119 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { isLandscape, PLVMPMediaControllerViewModel, PLVMPMediaViewModel } from 'media-player-common' + +// +const SLIDER_MAX_PROGRESS = 10000 +const SLIDER_BACKGROUND_COLOR = '#33FFFFFF' +const SLIDER_PROGRESS_COLOR_NORMAL = '#99FFFFFF' +const SLIDER_PROGRESS_COLOR_ON_DRAG = '#CCFFFFFF' +const SLIDER_THUMB_COLOR = '#FFFFFF' +const SLIDER_HEIGHT_NORMAL = 4 +const SLIDER_HEIGHT_ON_DRAG = 8 +const SLIDER_RADIUS_NORMAL = 4 +const SLIDER_RADIUS_ON_DRAG = 8 +const THUMB_WIDTH_NORMAL = 8 +const THUMB_HEIGHT_NORMAL = 8 +const THUMB_RADIUS_NORMAL = 8 +const THUMB_WIDTH_ON_DRAG = 8 +const THUMB_HEIGHT_ON_DRAG = 14 +const THUMB_RADIUS_ON_DRAG = 4 + +// + +@Component +export struct PLVMediaPlayerProgressSliderSingleLand { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + private videoProgress: number = 0 + private videoDuration: number = 0 + private dragProgress: number = 0 + private waitDragSeekFinish: boolean = false + @State sliderProgress: number = 0 + @State isDragging: boolean = false + @State isVisible: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear() { + this.mediaViewModel.mediaPlayViewState.observe((viewState) => { + this.videoProgress = viewState.currentProgress + this.videoDuration = viewState.duration + this.updateSliderProgress() + }).pushTo(this.observers) + + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isDragging = viewState.progressSliderDragging + this.dragProgress = viewState.progressSliderDragPosition + this.waitDragSeekFinish = viewState.progressSliderWaitSeekFinish + this.updateSliderProgress() + }) + + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isVisible = viewState.controllerVisible + && !viewState.isMediaStopOverlayVisible() + && !viewState.controllerLocking + && !(viewState.isFloatActionLayoutVisible() && isLandscape()) + }).pushTo(this.observers) + } + + build() { + Slider({ + value: this.sliderProgress, + min: 0, + max: SLIDER_MAX_PROGRESS, + direction: Axis.Horizontal + }) + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + .trackColor(SLIDER_BACKGROUND_COLOR) + .trackThickness(this.isDragging ? SLIDER_HEIGHT_ON_DRAG : SLIDER_HEIGHT_NORMAL) + .trackBorderRadius(this.isDragging ? SLIDER_RADIUS_ON_DRAG : SLIDER_RADIUS_NORMAL) + .selectedColor(this.isDragging ? SLIDER_PROGRESS_COLOR_ON_DRAG : SLIDER_PROGRESS_COLOR_NORMAL) + .blockStyle({ + type: SliderBlockType.SHAPE, + shape: new Rect() + .width(this.isDragging ? THUMB_WIDTH_ON_DRAG : THUMB_WIDTH_NORMAL) + .height(this.isDragging ? THUMB_HEIGHT_ON_DRAG : THUMB_HEIGHT_NORMAL) + .radius(this.isDragging ? THUMB_RADIUS_ON_DRAG : THUMB_RADIUS_NORMAL) + }) + .blockSize({ + width: this.isDragging ? THUMB_WIDTH_ON_DRAG : THUMB_WIDTH_NORMAL, + height: this.isDragging ? THUMB_HEIGHT_ON_DRAG : THUMB_HEIGHT_NORMAL + }) + .blockColor(SLIDER_THUMB_COLOR) + .onChange((value: number, mode: SliderChangeMode) => { + switch (mode) { + case SliderChangeMode.Begin: + this.controllerViewModel.handleDragSlider('slide', value / SLIDER_MAX_PROGRESS * this.videoDuration) + break; + case SliderChangeMode.Moving: + this.controllerViewModel.handleDragSlider('slide', value / SLIDER_MAX_PROGRESS * this.videoDuration) + break; + case SliderChangeMode.End: + this.controllerViewModel.handleDragSlider('slideFinish', value / SLIDER_MAX_PROGRESS * this.videoDuration) + break; + case SliderChangeMode.Click: + this.controllerViewModel.handleDragSlider('click', value / SLIDER_MAX_PROGRESS * this.videoDuration) + break; + } + }) + } + + private updateSliderProgress() { + if (this.videoDuration === 0) { + return + } + if (!this.isDragging && this.waitDragSeekFinish) { + return + } + if (this.isDragging) { + this.sliderProgress = this.dragProgress / this.videoDuration * SLIDER_MAX_PROGRESS + return + } + this.sliderProgress = this.videoProgress / this.videoDuration * SLIDER_MAX_PROGRESS + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/scene_single_video/src/main/ets/scene/single/component/PLVMediaPlayerProgressSliderSinglePort.ets b/scene_single_video/src/main/ets/scene/single/component/PLVMediaPlayerProgressSliderSinglePort.ets new file mode 100644 index 0000000..c08713e --- /dev/null +++ b/scene_single_video/src/main/ets/scene/single/component/PLVMediaPlayerProgressSliderSinglePort.ets @@ -0,0 +1,119 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { isLandscape, PLVMPMediaControllerViewModel, PLVMPMediaViewModel } from 'media-player-common' + +// +const SLIDER_MAX_PROGRESS = 10000 +const SLIDER_BACKGROUND_COLOR = '#33FFFFFF' +const SLIDER_PROGRESS_COLOR_NORMAL = '#99FFFFFF' +const SLIDER_PROGRESS_COLOR_ON_DRAG = '#CCFFFFFF' +const SLIDER_THUMB_COLOR = '#FFFFFF' +const SLIDER_HEIGHT_NORMAL = 2 +const SLIDER_HEIGHT_ON_DRAG = 8 +const SLIDER_RADIUS_NORMAL = 4 +const SLIDER_RADIUS_ON_DRAG = 8 +const THUMB_WIDTH_NORMAL = 4 +const THUMB_HEIGHT_NORMAL = 4 +const THUMB_RADIUS_NORMAL = 4 +const THUMB_WIDTH_ON_DRAG = 8 +const THUMB_HEIGHT_ON_DRAG = 14 +const THUMB_RADIUS_ON_DRAG = 4 + +// + +@Component +export struct PLVMediaPlayerProgressSliderSinglePort { + @Consume dependScope: DependScope + private mediaViewModel: PLVMPMediaViewModel = this.dependScope.get(PLVMPMediaViewModel) + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + private videoProgress: number = 0 + private videoDuration: number = 0 + private dragProgress: number = 0 + private waitDragSeekFinish: boolean = false + @State sliderProgress: number = 0 + @State isDragging: boolean = false + @State isVisible: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear() { + this.mediaViewModel.mediaPlayViewState.observe((viewState) => { + this.videoProgress = viewState.currentProgress + this.videoDuration = viewState.duration + this.updateSliderProgress() + }).pushTo(this.observers) + + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isDragging = viewState.progressSliderDragging + this.dragProgress = viewState.progressSliderDragPosition + this.waitDragSeekFinish = viewState.progressSliderWaitSeekFinish + this.updateSliderProgress() + }) + + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isVisible = viewState.controllerVisible + && !viewState.isMediaStopOverlayVisible() + && !viewState.controllerLocking + && !(viewState.isFloatActionLayoutVisible() && isLandscape()) + }).pushTo(this.observers) + } + + build() { + Slider({ + value: this.sliderProgress, + min: 0, + max: SLIDER_MAX_PROGRESS, + direction: Axis.Horizontal + }) + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + .trackColor(SLIDER_BACKGROUND_COLOR) + .trackThickness(this.isDragging ? SLIDER_HEIGHT_ON_DRAG : SLIDER_HEIGHT_NORMAL) + .trackBorderRadius(this.isDragging ? SLIDER_RADIUS_ON_DRAG : SLIDER_RADIUS_NORMAL) + .selectedColor(this.isDragging ? SLIDER_PROGRESS_COLOR_ON_DRAG : SLIDER_PROGRESS_COLOR_NORMAL) + .blockStyle({ + type: SliderBlockType.SHAPE, + shape: new Rect() + .width(this.isDragging ? THUMB_WIDTH_ON_DRAG : THUMB_WIDTH_NORMAL) + .height(this.isDragging ? THUMB_HEIGHT_ON_DRAG : THUMB_HEIGHT_NORMAL) + .radius(this.isDragging ? THUMB_RADIUS_ON_DRAG : THUMB_RADIUS_NORMAL) + }) + .blockSize({ + width: this.isDragging ? THUMB_WIDTH_ON_DRAG : THUMB_WIDTH_NORMAL, + height: this.isDragging ? THUMB_HEIGHT_ON_DRAG : THUMB_HEIGHT_NORMAL + }) + .blockColor(SLIDER_THUMB_COLOR) + .onChange((value: number, mode: SliderChangeMode) => { + switch (mode) { + case SliderChangeMode.Begin: + this.controllerViewModel.handleDragSlider('slide', value / SLIDER_MAX_PROGRESS * this.videoDuration) + break; + case SliderChangeMode.Moving: + this.controllerViewModel.handleDragSlider('slide', value / SLIDER_MAX_PROGRESS * this.videoDuration) + break; + case SliderChangeMode.End: + this.controllerViewModel.handleDragSlider('slideFinish', value / SLIDER_MAX_PROGRESS * this.videoDuration) + break; + case SliderChangeMode.Click: + this.controllerViewModel.handleDragSlider('click', value / SLIDER_MAX_PROGRESS * this.videoDuration) + break; + } + }) + } + + private updateSliderProgress() { + if (this.videoDuration === 0) { + return + } + if (!this.isDragging && this.waitDragSeekFinish) { + return + } + if (this.isDragging) { + this.sliderProgress = this.dragProgress / this.videoDuration * SLIDER_MAX_PROGRESS + return + } + this.sliderProgress = this.videoProgress / this.videoDuration * SLIDER_MAX_PROGRESS + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/scene_single_video/src/main/ets/scene/single/component/PLVMediaPlayerSwitchToFullScreenButtonSinglePort.ets b/scene_single_video/src/main/ets/scene/single/component/PLVMediaPlayerSwitchToFullScreenButtonSinglePort.ets new file mode 100644 index 0000000..db37d61 --- /dev/null +++ b/scene_single_video/src/main/ets/scene/single/component/PLVMediaPlayerSwitchToFullScreenButtonSinglePort.ets @@ -0,0 +1,30 @@ +import { DependScope, MutableObserver } from '@polyvharmony/media-player-sdk' +import { PLVMPMediaControllerViewModel, PLVOrientationManager } from 'media-player-common' + +@Component +export struct PLVMediaPlayerSwitchToFullScreenButtonSinglePort { + @Consume dependScope: DependScope + private controllerViewModel: PLVMPMediaControllerViewModel = this.dependScope.get(PLVMPMediaControllerViewModel) + @State isVisible: boolean = false + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + this.controllerViewModel.mediaControllerViewState.observe((viewState) => { + this.isVisible = viewState.controllerVisible + && !viewState.isMediaStopOverlayVisible() + }).pushTo(this.observers) + } + + build() { + Image($r('app.media.plv_media_player_full_screen_icon')) + .visibility(this.isVisible ? Visibility.Visible : Visibility.None) + .onClick(() => { + PLVOrientationManager.getInstance().requestOrientation('land') + }) + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/scene_single_video/src/main/ets/scene/single/layout/PLVMediaPlayerSingleVideoControllerLayout.ets b/scene_single_video/src/main/ets/scene/single/layout/PLVMediaPlayerSingleVideoControllerLayout.ets new file mode 100644 index 0000000..6b3581a --- /dev/null +++ b/scene_single_video/src/main/ets/scene/single/layout/PLVMediaPlayerSingleVideoControllerLayout.ets @@ -0,0 +1,29 @@ +import { MutableObserver } from '@polyvharmony/media-player-sdk' +import { isPortrait, PLVOrientationManager } from 'media-player-common' +import { PLVMediaPlayerSingleVideoControllerLayoutLand } from './PLVMediaPlayerSingleVideoControllerLayoutLand' +import { PLVMediaPlayerSingleVideoControllerLayoutPort } from './PLVMediaPlayerSingleVideoControllerLayoutPort' + +@Component +export struct PLVMediaPlayerSingleVideoControllerLayout { + @State isPortrait: boolean = isPortrait() + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + PLVOrientationManager.getInstance().isPortrait.observe((isPortrait: boolean) => { + this.isPortrait = isPortrait + }).pushTo(this.observers) + } + + build() { + if (this.isPortrait) { + PLVMediaPlayerSingleVideoControllerLayoutPort() + } else { + PLVMediaPlayerSingleVideoControllerLayoutLand() + } + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/scene_single_video/src/main/ets/scene/single/layout/PLVMediaPlayerSingleVideoControllerLayoutLand.ets b/scene_single_video/src/main/ets/scene/single/layout/PLVMediaPlayerSingleVideoControllerLayoutLand.ets new file mode 100644 index 0000000..6a06763 --- /dev/null +++ b/scene_single_video/src/main/ets/scene/single/layout/PLVMediaPlayerSingleVideoControllerLayoutLand.ets @@ -0,0 +1,330 @@ +import { createDependScope, DependScope } from '@polyvharmony/media-player-sdk' +import { + commonItemModule, + createId, + parent, + PLVMediaPlayerAudioModeCoverLayoutLand, + PLVMediaPlayerAutoContinueHintLayout, + PLVMediaPlayerBackImageView, + PLVMediaPlayerBitRateTextView, + PLVMediaPlayerBrightnessVolumeUpdateHintLayout, + PLVMediaPlayerBufferingSpeedLayout, + PLVMediaPlayerControllerGradientMaskLayout, + PLVMediaPlayerGestureHandleLayout, + PLVMediaPlayerLockControllerImageView, + PLVMediaPlayerLongPressSpeedHintLayout, + PLVMediaPlayerMarqueeLayout, + PLVMediaPlayerMoreActionImageView, + PLVMediaPlayerNetworkPoorIndicateLayout, + PLVMediaPlayerPlayButton, + PLVMediaPlayerPlayCompleteManualRestartOverlayLayout, + PLVMediaPlayerPlayErrorOverlayLayout, + PLVMediaPlayerProgressTextView, + PLVMediaPlayerScreenshotImageView, + PLVMediaPlayerSeekProgressPreviewLayout, + PLVMediaPlayerSpeedTextView, + PLVMediaPlayerSubtitleTextLayout, + PLVMediaPlayerSwitchBitRateHintLayout, + PLVMediaPlayerTitleTextView, + toBottomOf, + toCenterOf, + toEndOf, + toMiddleOf, + toStartOf, + toTopOf, + usePadding +} from 'media-player-common' +import { PLVMediaPlayerProgressSliderSingleLand } from '../component/PLVMediaPlayerProgressSliderSingleLand' + +@Component +export struct PLVMediaPlayerSingleVideoControllerLayoutLand { + @Consume dependScope: DependScope + // + private readonly plv_media_player_subtitle_text_layout: string = createId() + private readonly plv_media_player_controller_gradient_mask_layout: string = createId() + private readonly plv_media_player_gesture_handle_layout: string = createId() + private readonly plv_media_player_marquee_layout: string = createId() + private readonly plv_media_player_audio_mode_cover_layout: string = createId() + private readonly plv_media_player_buffering_speed_layout: string = createId() + private readonly plv_media_player_complete_overlay_layout: string = createId() + private readonly plv_media_player_error_overlay_layout: string = createId() + private readonly plv_media_player_back_iv: string = createId() + private readonly plv_media_player_title_tv: string = createId() + private readonly plv_media_player_more_action_iv: string = createId() + private readonly plv_media_player_play_button: string = createId() + private readonly plv_media_player_progress_text_view: string = createId() + private readonly plv_media_player_progress_slider: string = createId() + private readonly plv_media_player_bit_rate_text_view: string = createId() + private readonly plv_media_player_speed_text_view: string = createId() + private readonly plv_media_player_lock_controller_iv: string = createId() + private readonly plv_media_player_network_poor_indicate_layout: string = createId() + private readonly plv_media_player_long_press_speed_hint_layout: string = createId() + private readonly plv_media_player_auto_continue_hint_layout: string = createId() + private readonly plv_media_player_switch_bit_rate_hint_layout: string = createId() + private readonly plv_media_player_brightness_update_hint_layout: string = createId() + private readonly plv_media_player_seek_progress_preview_layout: string = createId() + private readonly plv_media_player_screenshot_iv: string = createId() + + // + + build() { + RelativeContainer() { + // 字幕 + PLVMediaPlayerSubtitleTextLayout() + .id(this.plv_media_player_subtitle_text_layout) + .hitTestBehavior(HitTestMode.None) + .margin({ + bottom: 34 + }) + .alignRules({ + bottom: toBottomOf(parent), + middle: toMiddleOf(parent) + }) + + // 皮肤控件底部渐变遮罩蒙层 + PLVMediaPlayerControllerGradientMaskLayout() + .id(this.plv_media_player_controller_gradient_mask_layout) + .hitTestBehavior(HitTestMode.None) + + // 手势处理 + PLVMediaPlayerGestureHandleLayout() + .id(this.plv_media_player_gesture_handle_layout) + + // 播放器跑马灯 + PLVMediaPlayerMarqueeLayout() + .id(this.plv_media_player_marquee_layout) + .hitTestBehavior(HitTestMode.None) + + // 音频模式 + PLVMediaPlayerAudioModeCoverLayoutLand() + .id(this.plv_media_player_audio_mode_cover_layout) + .hitTestBehavior(HitTestMode.Transparent) + + // 缓冲加载速度显示 + PLVMediaPlayerBufferingSpeedLayout() + .id(this.plv_media_player_buffering_speed_layout) + .hitTestBehavior(HitTestMode.None) + + // 播放结束覆盖层 + PLVMediaPlayerPlayCompleteManualRestartOverlayLayout() + .id(this.plv_media_player_complete_overlay_layout) + + // 播放异常覆盖层 + PLVMediaPlayerPlayErrorOverlayLayout() + .id(this.plv_media_player_error_overlay_layout) + + // 返回按钮 + PLVMediaPlayerBackImageView() + .id(this.plv_media_player_back_iv) + .width(28) + .height(28) + .margin(usePadding({ + left: 16, + vertical: 24, + })) + .alignRules({ + left: toStartOf(parent), + top: toTopOf(parent) + }) + .hitTestBehavior(HitTestMode.Transparent) + + // 视频标题 + PLVMediaPlayerTitleTextView() + .id(this.plv_media_player_title_tv) + .alignRules({ + center: toCenterOf(this.plv_media_player_back_iv), + left: toEndOf(this.plv_media_player_back_iv), + right: toStartOf(this.plv_media_player_more_action_iv) + }) + .hitTestBehavior(HitTestMode.None) + + // 更多按钮 + PLVMediaPlayerMoreActionImageView() + .id(this.plv_media_player_more_action_iv) + .width(28) + .height(28) + .alignRules({ + top: toTopOf(parent), + right: toEndOf(parent), + }) + .margin({ + top: 24, + right: 16 + }) + .hitTestBehavior(HitTestMode.Transparent) + + // 播放/暂停按钮 + PLVMediaPlayerPlayButton() + .id(this.plv_media_player_play_button) + .width(24) + .height(24) + .margin(usePadding({ + left: 16, + vertical: 28 + })) + .alignRules({ + left: toStartOf(parent), + bottom: toBottomOf(parent) + }) + .hitTestBehavior(HitTestMode.Transparent) + + // 播放进度文本 + PLVMediaPlayerProgressTextView() + .id(this.plv_media_player_progress_text_view) + .alignRules({ + left: toEndOf(this.plv_media_player_play_button), + center: toCenterOf(this.plv_media_player_play_button) + }) + .margin({ + left: 9 + }) + .hitTestBehavior(HitTestMode.None) + + // 播放进度条 + PLVMediaPlayerProgressSliderSingleLand() + .id(this.plv_media_player_progress_slider) + .width('100%') + .height(14) + .padding(usePadding({ + horizontal: 12 + })) + .margin({ + bottom: 6 + }) + .alignRules({ + bottom: toTopOf(this.plv_media_player_play_button) + }) + .hitTestBehavior(HitTestMode.Transparent) + + // 清晰度 + PLVMediaPlayerBitRateTextView() + .id(this.plv_media_player_bit_rate_text_view) + .alignRules({ + right: toEndOf(parent), + bottom: toBottomOf(parent) + }) + .margin({ + right: 16, + bottom: 30 + }) + .hitTestBehavior(HitTestMode.Transparent) + + // 倍速 + PLVMediaPlayerSpeedTextView() + .id(this.plv_media_player_speed_text_view) + .alignRules({ + right: toStartOf(this.plv_media_player_bit_rate_text_view), + bottom: toBottomOf(parent) + }) + .margin({ + right: 24, + bottom: 30 + }) + .hitTestBehavior(HitTestMode.Transparent) + + // 横屏操作锁定按钮 + PLVMediaPlayerLockControllerImageView() + .id(this.plv_media_player_lock_controller_iv) + .alignRules({ + left: toStartOf(parent), + center: toCenterOf(parent) + }) + .margin({ + left: 16 + }) + .hitTestBehavior(HitTestMode.Transparent) + + // 视频截图按钮 + PLVMediaPlayerScreenshotImageView() + .id(this.plv_media_player_screenshot_iv) + .alignRules({ + center: toCenterOf(parent), + end: toEndOf(parent) + }) + .margin({ + right: 16 + }) + .hitTestBehavior(HitTestMode.Transparent) + + // 弱网提示 + PLVMediaPlayerNetworkPoorIndicateLayout() + .id(this.plv_media_player_network_poor_indicate_layout) + .alignRules({ + right: toEndOf(parent), + bottom: toTopOf(this.plv_media_player_bit_rate_text_view) + }) + .margin({ + right: 15, + bottom: 12 + }) + + // 长按快进控制提示 + PLVMediaPlayerLongPressSpeedHintLayout() + .id(this.plv_media_player_long_press_speed_hint_layout) + .alignRules({ + top: toTopOf(parent), + middle: toMiddleOf(parent) + }) + .margin({ + top: 28 + }) + + // 自动续播提示条 + PLVMediaPlayerAutoContinueHintLayout() + .id(this.plv_media_player_auto_continue_hint_layout) + .alignRules({ + left: toStartOf(parent), + bottom: toTopOf(this.plv_media_player_progress_slider) + }) + .margin({ + left: 12, + bottom: 12 + }) + + // 切换清晰度提示条 + PLVMediaPlayerSwitchBitRateHintLayout() + .id(this.plv_media_player_switch_bit_rate_hint_layout) + .alignRules({ + top: toTopOf(parent), + middle: toMiddleOf(parent) + }) + .margin({ + top: 28 + }) + + // 亮度/音量 调节提示 + PLVMediaPlayerBrightnessVolumeUpdateHintLayout() + .id(this.plv_media_player_brightness_update_hint_layout) + .margin({ + top: 28 + }) + .alignRules({ + top: toTopOf(parent), + middle: toMiddleOf(parent) + }) + + // 拖动进度条时进度预览 + PLVMediaPlayerSeekProgressPreviewLayout() + .id(this.plv_media_player_seek_progress_preview_layout) + .margin({ + bottom: 8 + }) + .alignRules({ + bottom: toTopOf(this.plv_media_player_progress_slider), + middle: toMiddleOf(parent) + }) + + } + } +} + + +@Preview +@Component +struct PLVMediaPlayerSingleVideoControllerLayoutLandPreview { + @Provide dependScope: DependScope = createDependScope(commonItemModule) + + build() { + PLVMediaPlayerSingleVideoControllerLayoutLand() + } +} \ No newline at end of file diff --git a/scene_single_video/src/main/ets/scene/single/layout/PLVMediaPlayerSingleVideoControllerLayoutPort.ets b/scene_single_video/src/main/ets/scene/single/layout/PLVMediaPlayerSingleVideoControllerLayoutPort.ets new file mode 100644 index 0000000..eca5148 --- /dev/null +++ b/scene_single_video/src/main/ets/scene/single/layout/PLVMediaPlayerSingleVideoControllerLayoutPort.ets @@ -0,0 +1,286 @@ +import { createDependScope, DependScope } from '@polyvharmony/media-player-sdk' +import { + commonItemModule, + createId, + parent, + PLVMediaPlayerAudioModeCoverLayoutPort, + PLVMediaPlayerAutoContinueHintLayout, + PLVMediaPlayerBackImageView, + PLVMediaPlayerBrightnessVolumeUpdateHintLayout, + PLVMediaPlayerBufferingSpeedLayout, + PLVMediaPlayerControllerGradientMaskLayout, + PLVMediaPlayerGestureHandleLayout, + PLVMediaPlayerLongPressSpeedHintLayout, + PLVMediaPlayerMarqueeLayout, + PLVMediaPlayerMoreActionImageView, + PLVMediaPlayerNetworkPoorIndicateLayout, + PLVMediaPlayerPlayButton, + PLVMediaPlayerPlayCompleteManualRestartOverlayLayout, + PLVMediaPlayerPlayErrorOverlayLayout, + PLVMediaPlayerProgressTextView, + PLVMediaPlayerSeekProgressPreviewLayout, + PLVMediaPlayerSubtitleTextLayout, + PLVMediaPlayerSwitchBitRateHintLayout, + PLVMediaPlayerTitleTextView, + toBottomOf, + toCenterOf, + toEndOf, + toMiddleOf, + toStartOf, + toTopOf, + usePadding +} from 'media-player-common' +import { PLVMediaPlayerProgressSliderSinglePort } from '../component/PLVMediaPlayerProgressSliderSinglePort' +import { + PLVMediaPlayerSwitchToFullScreenButtonSinglePort +} from '../component/PLVMediaPlayerSwitchToFullScreenButtonSinglePort' + +@Component +export struct PLVMediaPlayerSingleVideoControllerLayoutPort { + @Consume dependScope: DependScope + // + private readonly plv_media_player_subtitle_text_layout: string = createId() + private readonly plv_media_player_controller_gradient_mask_layout: string = createId() + private readonly plv_media_player_gesture_handle_layout: string = createId() + private readonly plv_media_player_marquee_layout: string = createId() + private readonly plv_media_player_audio_mode_cover_layout: string = createId() + private readonly plv_media_player_buffering_speed_layout: string = createId() + private readonly plv_media_player_complete_overlay_layout: string = createId() + private readonly plv_media_player_error_overlay_layout: string = createId() + private readonly plv_media_player_back_iv: string = createId() + private readonly plv_media_player_title_tv: string = createId() + private readonly plv_media_player_more_action_iv: string = createId() + private readonly plv_media_player_play_button: string = createId() + private readonly plv_media_player_progress_text_view: string = createId() + private readonly plv_media_player_switch_full_screen_btn: string = createId() + private readonly plv_media_player_progress_slider: string = createId() + private readonly plv_media_player_network_poor_indicate_layout: string = createId() + private readonly plv_media_player_long_press_speed_hint_layout: string = createId() + private readonly plv_media_player_auto_continue_hint_layout: string = createId() + private readonly plv_media_player_switch_bit_rate_hint_layout: string = createId() + private readonly plv_media_player_brightness_update_hint_layout: string = createId() + private readonly plv_media_player_seek_progress_preview_layout: string = createId() + + // + + build() { + RelativeContainer() { + // 字幕 + PLVMediaPlayerSubtitleTextLayout() + .id(this.plv_media_player_subtitle_text_layout) + .hitTestBehavior(HitTestMode.None) + .margin({ + bottom: 12 + }) + .alignRules({ + bottom: toBottomOf(parent), + middle: toMiddleOf(parent) + }) + + // 皮肤控件底部渐变遮罩蒙层 + PLVMediaPlayerControllerGradientMaskLayout({ maskHeight: 64 }) + .id(this.plv_media_player_controller_gradient_mask_layout) + .hitTestBehavior(HitTestMode.None) + + // 手势处理 + PLVMediaPlayerGestureHandleLayout() + .id(this.plv_media_player_gesture_handle_layout) + + // 播放器跑马灯 + PLVMediaPlayerMarqueeLayout() + .id(this.plv_media_player_marquee_layout) + .hitTestBehavior(HitTestMode.None) + + // 音频模式 + PLVMediaPlayerAudioModeCoverLayoutPort() + .id(this.plv_media_player_audio_mode_cover_layout) + .hitTestBehavior(HitTestMode.Transparent) + + // 缓冲加载速度显示 + PLVMediaPlayerBufferingSpeedLayout() + .id(this.plv_media_player_buffering_speed_layout) + .hitTestBehavior(HitTestMode.None) + + // 播放结束覆盖层 + PLVMediaPlayerPlayCompleteManualRestartOverlayLayout() + .id(this.plv_media_player_complete_overlay_layout) + + // 播放异常覆盖层 + PLVMediaPlayerPlayErrorOverlayLayout() + .id(this.plv_media_player_error_overlay_layout) + + // 返回按钮 + PLVMediaPlayerBackImageView() + .id(this.plv_media_player_back_iv) + .width(28) + .height(28) + .margin(usePadding({ + left: 16, + vertical: 8, + })) + .alignRules({ + left: toStartOf(parent), + top: toTopOf(parent) + }) + .hitTestBehavior(HitTestMode.Transparent) + + // 视频标题 + PLVMediaPlayerTitleTextView() + .id(this.plv_media_player_title_tv) + .alignRules({ + center: toCenterOf(this.plv_media_player_back_iv), + left: toEndOf(this.plv_media_player_back_iv), + right: toStartOf(this.plv_media_player_more_action_iv) + }) + .hitTestBehavior(HitTestMode.None) + + // 更多按钮 + PLVMediaPlayerMoreActionImageView() + .id(this.plv_media_player_more_action_iv) + .width(28) + .height(28) + .alignRules({ + top: toTopOf(parent), + right: toEndOf(parent), + }) + .margin({ + top: 8, + right: 16 + }) + .hitTestBehavior(HitTestMode.Transparent) + + // 播放/暂停按钮 + PLVMediaPlayerPlayButton() + .id(this.plv_media_player_play_button) + .width(32) + .height(32) + .margin(usePadding({ + left: 4, + vertical: 4 + })) + .padding(8) + .alignRules({ + left: toStartOf(parent), + bottom: toBottomOf(parent) + }) + .hitTestBehavior(HitTestMode.Transparent) + + // 播放进度文本 + PLVMediaPlayerProgressTextView() + .id(this.plv_media_player_progress_text_view) + .alignRules({ + left: toEndOf(this.plv_media_player_play_button), + center: toCenterOf(this.plv_media_player_play_button) + }) + .hitTestBehavior(HitTestMode.None) + + // 切换全屏按钮 + PLVMediaPlayerSwitchToFullScreenButtonSinglePort() + .id(this.plv_media_player_switch_full_screen_btn) + .width(32) + .height(32) + .margin({ + right: 4, + bottom: 4 + }) + .padding(8) + .alignRules({ + right: toEndOf(parent), + bottom: toBottomOf(parent) + }) + .hitTestBehavior(HitTestMode.Transparent) + + // 播放进度条 + PLVMediaPlayerProgressSliderSinglePort() + .id(this.plv_media_player_progress_slider) + .width('100%') + .height(14) + .padding(usePadding({ + horizontal: 8 + })) + .alignRules({ + bottom: toTopOf(this.plv_media_player_play_button) + }) + .hitTestBehavior(HitTestMode.Transparent) + + // 弱网提示 + PLVMediaPlayerNetworkPoorIndicateLayout() + .id(this.plv_media_player_network_poor_indicate_layout) + .alignRules({ + top: toBottomOf(this.plv_media_player_more_action_iv), + right: toEndOf(parent) + }) + .margin({ + right: 8 + }) + + // 长按快进控制提示 + PLVMediaPlayerLongPressSpeedHintLayout() + .id(this.plv_media_player_long_press_speed_hint_layout) + .alignRules({ + top: toTopOf(parent), + middle: toMiddleOf(parent) + }) + .margin({ + top: 28 + }) + + // 自动续播提示条 + PLVMediaPlayerAutoContinueHintLayout() + .id(this.plv_media_player_auto_continue_hint_layout) + .alignRules({ + left: toStartOf(parent), + bottom: toTopOf(this.plv_media_player_progress_slider) + }) + .margin({ + left: 12, + bottom: 6 + }) + + // 切换清晰度提示条 + PLVMediaPlayerSwitchBitRateHintLayout() + .id(this.plv_media_player_switch_bit_rate_hint_layout) + .alignRules({ + top: toTopOf(parent), + middle: toMiddleOf(parent) + }) + .margin({ + top: 28 + }) + + // 亮度/音量 调节提示 + PLVMediaPlayerBrightnessVolumeUpdateHintLayout() + .id(this.plv_media_player_brightness_update_hint_layout) + .margin({ + top: 28 + }) + .alignRules({ + top: toTopOf(parent), + middle: toMiddleOf(parent) + }) + + // 拖动进度条时进度预览 + PLVMediaPlayerSeekProgressPreviewLayout() + .id(this.plv_media_player_seek_progress_preview_layout) + .margin({ + bottom: 8 + }) + .alignRules({ + bottom: toTopOf(this.plv_media_player_progress_slider), + middle: toMiddleOf(parent) + }) + + } + } +} + + +@Preview +@Component +struct PLVMediaPlayerSingleVideoControllerLayoutPortPreview { + @Provide dependScope: DependScope = createDependScope(commonItemModule) + + build() { + PLVMediaPlayerSingleVideoControllerLayoutPort() + } +} \ No newline at end of file diff --git a/scene_single_video/src/main/ets/scene/single/layout/PLVMediaPlayerSingleVideoFloatActionLayout.ets b/scene_single_video/src/main/ets/scene/single/layout/PLVMediaPlayerSingleVideoFloatActionLayout.ets new file mode 100644 index 0000000..367f4f4 --- /dev/null +++ b/scene_single_video/src/main/ets/scene/single/layout/PLVMediaPlayerSingleVideoFloatActionLayout.ets @@ -0,0 +1,40 @@ +import { MutableObserver } from '@polyvharmony/media-player-sdk' +import { + isPortrait, + PLVMediaPlayerBitRateSelectLayoutLand, + PLVMediaPlayerMoreLayoutLand, + PLVMediaPlayerMoreLayoutPort, + PLVMediaPlayerMoreSubtitleSettingLayoutLand, + PLVMediaPlayerMoreSubtitleSettingLayoutPort, + PLVMediaPlayerSpeedSelectLayoutLand, + PLVOrientationManager +} from 'media-player-common' + +@Component +export struct PLVMediaPlayerSingleVideoFloatActionLayout { + @State isPortrait: boolean = isPortrait() + private observers: MutableObserver[] = [] + + aboutToAppear(): void { + PLVOrientationManager.getInstance().isPortrait.observe((isPortrait: boolean) => { + this.isPortrait = isPortrait + }).pushTo(this.observers) + } + + build() { + if (this.isPortrait) { + PLVMediaPlayerMoreLayoutPort() + PLVMediaPlayerMoreSubtitleSettingLayoutPort() + } else { + PLVMediaPlayerMoreLayoutLand() + PLVMediaPlayerBitRateSelectLayoutLand() + PLVMediaPlayerSpeedSelectLayoutLand() + PLVMediaPlayerMoreSubtitleSettingLayoutLand() + } + } + + aboutToDisappear(): void { + MutableObserver.disposeAll(this.observers) + this.observers = [] + } +} \ No newline at end of file diff --git a/scene_single_video/src/main/module.json5 b/scene_single_video/src/main/module.json5 new file mode 100644 index 0000000..2977167 --- /dev/null +++ b/scene_single_video/src/main/module.json5 @@ -0,0 +1,10 @@ +{ + "module": { + "name": "scene_single_video", + "type": "har", + "deviceTypes": [ + "default", + "tablet" + ] + } +} diff --git a/scene_single_video/src/main/resources/base/element/string.json b/scene_single_video/src/main/resources/base/element/string.json new file mode 100644 index 0000000..9659948 --- /dev/null +++ b/scene_single_video/src/main/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} \ No newline at end of file diff --git a/scene_single_video/src/main/resources/en_US/element/string.json b/scene_single_video/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000..9659948 --- /dev/null +++ b/scene_single_video/src/main/resources/en_US/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} \ No newline at end of file diff --git a/scene_single_video/src/main/resources/zh_CN/element/string.json b/scene_single_video/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000..9659948 --- /dev/null +++ b/scene_single_video/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} \ No newline at end of file