接入保利威视demo

This commit is contained in:
XiuYun CHEN
2025-05-15 13:11:26 +08:00
parent b2bdaeaff3
commit bed645747d
182 changed files with 9605 additions and 8 deletions
+11
View File
@@ -0,0 +1,11 @@
/node_modules
/oh_modules
/.preview
/build
/.cxx
/.test
BuildProfile.ets
oh-package-lock.json5
.npmrc
package.json
package-lock.json
+64
View File
@@ -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'
+28
View File
@@ -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"
}
]
}
+6
View File
@@ -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. */
}
+18
View File
@@ -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
+12
View File
@@ -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"
}
}
@@ -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)
@@ -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)
))
@@ -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<PLVMPAuxiliaryInfoViewState | null>(null);
readonly auxiliaryPlayViewState = new MutableState<PLVMPAuxiliaryPlayViewState>(new PLVMPAuxiliaryPlayViewState());
readonly auxiliaryVideoInfoViewState = new MutableState<PLVMPAuxiliaryVideoInfoViewState>(new PLVMPAuxiliaryVideoInfoViewState());
onDestroy() {
MutableSource.disposeAll(this)
}
}
@@ -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()
}
}
@@ -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<PLVMPAuxiliaryInfoViewState | null>
readonly auxiliaryPlayViewState: MutableState<PLVMPAuxiliaryPlayViewState>
readonly auxiliaryVideoInfoViewState: MutableState<PLVMPAuxiliaryVideoInfoViewState>
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()
}
}
@@ -0,0 +1,26 @@
import {
IPLVAuxiliaryOnBeforeAdvertListener,
PLVAdvertMediaDataSource,
PLVMediaPlayStage
} from '@polyvharmony/media-player-sdk';
export class AuxiliaryBeforePlayListener implements IPLVAuxiliaryOnBeforeAdvertListener {
private readonly playedAdvertIds = new Set<string>()
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
}
}
@@ -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 = []
}
}
@@ -0,0 +1,10 @@
import {AuxiliaryUpdateMediaStateUseCase} from "./AuxiliaryUpdateMediaStateUseCase";
import {AuxiliaryBeforePlayListener} from "./AuxiliaryBeforePlayListener";
export class PLVMPAuxiliaryUseCases {
constructor(
readonly beforePlayListener: AuxiliaryBeforePlayListener,
readonly updateMediaStateUseCase: AuxiliaryUpdateMediaStateUseCase
) {
}
}
@@ -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
}
@@ -0,0 +1,5 @@
export class PLVMPAuxiliaryPlayViewState {
timeLeftInSeconds: number = 0
}
@@ -0,0 +1,7 @@
import {Rect} from '@polyvharmony/media-player-sdk';
export class PLVMPAuxiliaryVideoInfoViewState {
videoSize: Rect = new Rect()
}
@@ -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 {
// <editor-fold defaultstate="collapsed" desc="单例">
private static readonly instance = new PLVMPDownloadListViewModel();
private constructor() {
}
static getInstance() {
return PLVMPDownloadListViewModel.instance;
}
// </editor-fold>
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
}
@@ -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)
))
@@ -0,0 +1,7 @@
import {MutableState} from "@polyvharmony/media-player-sdk";
import {PLVMPDownloadListViewState} from "../viewstate/PLVMPDownloadListViewState";
export class PLVMPDownloadListMediator {
readonly downloadedList = new MutableState<PLVMPDownloadListViewState>()
readonly downloadingList = new MutableState<PLVMPDownloadListViewState>()
}
@@ -0,0 +1,8 @@
import {PLVMPDownloadListMediator} from "../mediator/PLVMPDownloadListMediator";
export class PLVMPDownloadListRepo {
constructor(
readonly mediator: PLVMPDownloadListMediator
) {
}
}
@@ -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
}
}
@@ -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<PLVMPDownloadListItemViewState> {
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
))
}
}
@@ -0,0 +1,10 @@
import {DownloadRequestBackgroundTaskUseCase} from './DownloadRequestBackgroundTaskUseCase';
import {DownloadUpdateListUseCase} from "./DownloadUpdateListUseCase";
export class PLVMPDownloadListUseCases {
constructor(
readonly downloadUpdateListUseCase: DownloadUpdateListUseCase,
readonly downloadRequestBackgroundTaskUseCase: DownloadRequestBackgroundTaskUseCase
) {
}
}
@@ -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<PLVMPDownloadListItemViewState>[] = []
) {
}
}
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)
}
}
@@ -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)
))
@@ -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<PLVMPDownloadItemViewState | null>(null)
onDestroy() {
MutableSource.disposeAll(this)
}
}
@@ -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
) {
}
}
@@ -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)
}
}
@@ -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<boolean>(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 = []
}
}
@@ -0,0 +1,8 @@
import {DownloadItemUpdateStateUseCase} from "./DownloadItemUpdateStateUseCase";
export class PLVMPDownloadItemUseCases {
constructor(
readonly downloadItemUpdateStateUseCase: DownloadItemUpdateStateUseCase
) {
}
}
@@ -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
) {
}
}
@@ -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))
})
@@ -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<PLVMediaResource>()
readonly mediaPlayViewState = new MutableState(new PLVMPMediaPlayViewState());
readonly mediaInfoViewState = new MutableState(new PLVMPMediaInfoViewState());
readonly networkPoorEvent = new MutableEvent<number>();
readonly onChangeBitRateEvent = new MutableEvent<PLVMediaBitRate>();
readonly onPreparedEvent = new MutableEvent<PLVMediaPlayerOnPreparedEvent>();
readonly onAutoContinueEvent = new MutableEvent<PLVMediaPlayerAutoContinueEvent>();
readonly onInfoEvent = new MutableEvent<PLVMediaPlayerOnInfoEvent>();
readonly onCompleteEvent = new MutableEvent<PLVMediaPlayerOnCompletedEvent>();
readonly playingState = new MutableState(PLVMediaPlayerPlayingState.PAUSED);
readonly playerState = new MutableState(PLVMediaPlayerState.STATE_IDLE);
readonly bufferingSpeed = new MutableState<number>(0)
readonly businessErrorState = new MutableState<PLVMediaPlayerBusinessErrorEnum | null>(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)
}
}
@@ -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 = []
}
}
@@ -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<PLVMPMediaPlayViewState>
readonly mediaInfoViewState: State<PLVMPMediaInfoViewState>
readonly networkPoorEvent: MutableEvent<number>
readonly onChangeBitRateEvent: MutableEvent<PLVMediaBitRate>
readonly onPreparedEvent: MutableEvent<PLVMediaPlayerOnPreparedEvent>
readonly onAutoContinueEvent: MutableEvent<PLVMediaPlayerAutoContinueEvent>
readonly onInfoEvent: MutableEvent<PLVMediaPlayerOnInfoEvent>
readonly onCompleteEvent: MutableEvent<PLVMediaPlayerOnCompletedEvent>
readonly playerState: MutableState<PLVMediaPlayerState>
onScreenshot: (() => Promise<image.PixelMap>) | 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
}
}
@@ -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
}
})
}
}
@@ -0,0 +1,126 @@
import {
extendArray,
LifecycleAwareDependComponent,
MutableObserver,
PLVMediaPlayerOnInfoEvent,
seconds
} from '@polyvharmony/media-player-sdk'
import {PLVMPMediaRepo} from "../../model/PLVMPMediaRepo";
// <editor-fold defaultstate="collapsed" desc="常量">
// 计时: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;
// </editor-fold>
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
}
@@ -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
}
}
@@ -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)
}
}
}
@@ -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 = []
}
}
@@ -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"
}
@@ -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[] = []
}
@@ -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)
))
@@ -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<PLVMPMediaControllerViewState>(new PLVMPMediaControllerViewState())
// 亮度更新事件,范围 0.0 ~ 1.0
readonly brightnessUpdateEvent = new MutableEvent<number>()
// 音量更新事件,范围 0 ~ 100
readonly volumeUpdateEvent = new MutableEvent<number>()
readonly businessErrorState = new MutableState<PLVMediaPlayerBusinessErrorEnum | null>(null)
readonly playCompleteState = new MutableState<boolean>(false)
onDestroy() {
MutableSource.disposeAll(this)
}
}
@@ -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
}
}
@@ -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<PLVMPMediaControllerViewState>
// 亮度更新事件,范围 0.0 ~ 1.0
readonly brightnessUpdateEvent: MutableEvent<number>
// 音量更新事件,范围 0 ~ 100
readonly volumeUpdateEvent: MutableEvent<number>
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 = []
}
}
@@ -0,0 +1,13 @@
import {UpdateMediaStopOverlayUseCase} from "./UpdateMediaStopOverlayUseCase";
export class PLVMPMediaControllerUseCases {
readonly updateMediaStopOverlayUseCase: UpdateMediaStopOverlayUseCase
constructor(
updateMediaStopOverlayUseCase: UpdateMediaStopOverlayUseCase
) {
this.updateMediaStopOverlayUseCase = updateMediaStopOverlayUseCase
}
}
@@ -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 = []
}
}
@@ -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
}
}
@@ -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())
@@ -0,0 +1,15 @@
import {MutableState, OnBackPressHandler} from '@polyvharmony/media-player-sdk';
import {PLVComponentLifecycle} from "../../../utils/PLVComponentLifecycle";
export class PLVMPPageControlViewModel {
readonly currentItemIndex: MutableState<number> = new MutableState(0)
readonly pageLifecycle: PLVComponentLifecycle = new PLVComponentLifecycle()
readonly onBackPressHandler = new OnBackPressHandler()
navPathStack?: any | undefined = undefined
onCurrentItemChanged(currentIndex: number) {
this.currentItemIndex.value = currentIndex
}
}
@@ -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)
))
@@ -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<PLVMPProgressImageData | null> {
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
}
}
@@ -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
}
}
@@ -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<PLVMPProgressImageData | null> {
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<ArrayBuffer | null> {
const response = await http.createHttp().request(url)
if (response.responseCode !== http.ResponseCode.OK) {
return null
}
return response.result as ArrayBuffer
}
}
@@ -0,0 +1,14 @@
export class PLVMPProgressImageData {
readonly url: string
readonly buffer: ArrayBuffer
constructor(
url: string,
buffer: ArrayBuffer
) {
this.url = url
this.buffer = buffer
}
}
@@ -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<image.PixelMap | null> = 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<void> {
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
}
}
}
@@ -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<image.PixelMap | null> {
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
}
}
}
}
@@ -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[] = []
// <editor-fold defaultstate="collapsed" desc="ids">
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()
// </editor-fold>
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 = []
}
}
@@ -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[] = []
// <editor-fold defaultstate="collapsed" desc="ids">
private readonly plv_media_player_audio_mode_cover_layout_background: string = createId()
private readonly plv_media_player_audio_mode_cover_layout_content: string = createId()
// </editor-fold>
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 = []
}
}
@@ -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 = []
}
}
@@ -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 = []
}
}
@@ -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 = []
}
}
@@ -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[] = []
// <editor-fold defaultstate="collapsed" desc="ids">
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()
// </editor-fold>
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 = []
}
}
@@ -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 = []
}
}
@@ -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
}
}
@@ -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 = []
}
}
@@ -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[] = []
// <editor-fold defaultstate="collapsed" desc="ids">
private readonly plv_media_player_controller_gradient_mask_top: string = createId()
private readonly plv_media_player_controller_gradient_mask_bottom: string = createId()
// </editor-fold>
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 = []
}
}
@@ -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
}
}
@@ -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<void> {
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<void> {
const lastWindow = await window.getLastWindow(this.context)
lastWindow.off('windowEvent', this.windowEventObserver)
MutableObserver.disposeAll(this.observers)
this.observers = []
}
}
@@ -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 = []
}
}
@@ -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 = []
}
}
@@ -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 = []
}
}
@@ -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 = []
}
}
@@ -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 = []
}
}
@@ -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"))
}
}
@@ -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[] = []
// <editor-fold defaultstate="collapsed" desc="ids">
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()
// </editor-fold>
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 = []
}
}
@@ -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[] = []
// <editor-fold defaultstate="collapsed" desc="ids">
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()
// </editor-fold>
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 = []
}
}
@@ -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 = []
}
}
@@ -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[] = []
// <editor-fold defaultstate="collapsed" desc="ids">
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()
// </editor-fold>
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 = []
}
}
@@ -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[] = []
// <editor-fold defaultstate="collapsed" desc="ids">
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()
// </editor-fold>
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 = []
}
}
@@ -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 = []
}
}
@@ -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 = []
}
}
@@ -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 = []
}
}
@@ -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 = []
}
}
@@ -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 = []
}
}
@@ -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 = []
}
}
@@ -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<PixelMap | null> {
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)
}
}
@@ -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 = []
}
}
@@ -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 = []
}
}
@@ -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[] = []
// <editor-fold defaultstate="collapsed" desc="ids">
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()
// </editor-fold>
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 = []
}
}
@@ -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 = []
}
}
@@ -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 = []
}
}
@@ -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 = []
}
}
@@ -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 = []
}
}
@@ -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 = []
}
}
@@ -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
}
@@ -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<PLVMarqueePlayingState> = 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
}
@@ -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
}
}

Some files were not shown because too many files have changed in this diff Show More