|
|
@@ -135,12 +135,16 @@ import { PlaybackSnapshot as PlaybackStateBridgeSnapshot, PlaybackStateBridge }
|
|
|
import { consumePendingPlaybackActivation } from '../playback/PlaybackActivationStore';
|
|
|
import { peekPendingPlaybackActivation } from '../playback/PlaybackActivationStore';
|
|
|
import { PlaybackSnapshotStore } from '../playback/PlaybackSnapshotStore'
|
|
|
-import { PlaybackSnapshot as HostPlaybackSnapshot } from '../playback/model/PlaybackSnapshot'
|
|
|
+import {
|
|
|
+ PlaybackSnapshot as HostPlaybackSnapshot,
|
|
|
+ PlaybackSnapshotSong as HostPlaybackSnapshotSong
|
|
|
+} from '../playback/model/PlaybackSnapshot'
|
|
|
import { lyricService, SongData } from '../common/service/LyricService';
|
|
|
import { shouldResolveRemoteLyricOnPlay, tryResolveRemotePlaybackLyric } from '../common/util/RemotePlaybackLyricUtil';
|
|
|
import { resolvePlayerDismissMorphTarget } from '../common/util/PlayerDismissHelper';
|
|
|
import { resolvePlayerLyricUpdateTargets } from '../common/util/PlayerLyricUpdateTargetsHelper';
|
|
|
import { isBaiduStreamingPlaybackUrl, resolveSeekGuardDecision } from '../common/util/PlaybackSeekGuard';
|
|
|
+import { PlayerPage } from './player/PlayerPage';
|
|
|
import {
|
|
|
cloneVideoItem,
|
|
|
preloadNextSongIfNeeded,
|
|
|
@@ -237,6 +241,7 @@ import { LyricParser } from '../lyric/parse/LyricParser';
|
|
|
import { Lyric } from '../lyric/bean/Lyric';
|
|
|
|
|
|
const TAG = 'LocalMusic';
|
|
|
+const KILLCARD_TRACE = '[KILLCARD_TRACE]';
|
|
|
const HICAR_LOG_TAG = 'HiCarWindow';
|
|
|
const PREF_PLAY_COVER_SCALE_GUIDE_SHOWN = 'play_cover_scale_guide_show';
|
|
|
|
|
|
@@ -672,6 +677,7 @@ export struct LocalMusic {
|
|
|
private isHostSnapshotDisplayOnly: boolean = false
|
|
|
private playQueueScope: 'view' | 'history' | 'fav' | 'playlist' | 'single' | 'find_paged_local' = 'view'
|
|
|
private isHydratingPlayList: boolean = false
|
|
|
+ private suppressPlaybackCardStatePublish: boolean = false
|
|
|
private preserveExistingQueueOnNextPlay: boolean = false
|
|
|
private nextPlayQueueScope: 'view' | 'history' | 'fav' | 'playlist' | 'single' | 'find_paged_local' = 'view'
|
|
|
private findPagedLocalQueuePageIndex: number = 0
|
|
|
@@ -838,7 +844,7 @@ export struct LocalMusic {
|
|
|
// @Consume currentSong: VideoItem | undefined;
|
|
|
@StorageLink('currentSong') @Watch('onHomeBgCurrentSongChange') currentSong: VideoItem | undefined = undefined;
|
|
|
// @StorageLink('isPlay') isPlay: boolean = false;
|
|
|
- @StorageLink('isShowPlay') isShowPlay: boolean = false;
|
|
|
+ @Consume isShowPlay: boolean;
|
|
|
@State isShowCoverScaleGuide: boolean = false
|
|
|
@StorageLink('songList') songList: Array<VideoItem> = [];
|
|
|
@State translateY: number = 0;
|
|
|
@@ -1069,12 +1075,19 @@ export struct LocalMusic {
|
|
|
|
|
|
private onPlaybackHostOpenRequestChange(): void {
|
|
|
if (this.playbackHostOpenRequestToken === this.lastHandledPlaybackHostOpenRequestToken) {
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} onPlaybackHostOpenRequestChange skip sameToken token=${this.playbackHostOpenRequestToken}`)
|
|
|
return
|
|
|
}
|
|
|
this.lastHandledPlaybackHostOpenRequestToken = this.playbackHostOpenRequestToken
|
|
|
Logger.info(TAG,
|
|
|
`[MiniState] onPlaybackHostOpenRequestChange token=${this.playbackHostOpenRequestToken}, ` +
|
|
|
`isShowPlay=${this.isShowPlay}, songListLength=${this.songList.length}, currentSong=${this.currentSong?.name ?? ''}`)
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} onPlaybackHostOpenRequestChange enter token=${this.playbackHostOpenRequestToken}, ` +
|
|
|
+ `isShowPlay=${this.isShowPlay}, songListLength=${this.songList.length}, ` +
|
|
|
+ `currentSong=${this.currentSong?.name ?? ''}, controlStatus=${this.CONTROL_PlayStatus}, ` +
|
|
|
+ `videoUrl=${this.videoUrl}, hostDisplayOnly=${this.isHostSnapshotDisplayOnly}`)
|
|
|
if (!this.isShowPlay) {
|
|
|
this.setShowPlayTrue()
|
|
|
}
|
|
|
@@ -1112,9 +1125,15 @@ export struct LocalMusic {
|
|
|
}
|
|
|
// 组件生命周期
|
|
|
aboutToAppear() {
|
|
|
+ this.suppressPlaybackCardStatePublish = false
|
|
|
setPlaybackRuntimeReady(true)
|
|
|
this.playbackSnapshotHydrated = this.hydrateFromPlaybackSnapshot()
|
|
|
this.syncPlaybackUiStateFromControlStatus()
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} aboutToAppear hydrated=${this.playbackSnapshotHydrated}, ` +
|
|
|
+ `songListLength=${this.songList.length}, currentSong=${this.currentSong?.name ?? ''}, ` +
|
|
|
+ `controlStatus=${this.CONTROL_PlayStatus}, isShowPlay=${this.isShowPlay}, ` +
|
|
|
+ `hostDisplayOnly=${this.isHostSnapshotDisplayOnly}`)
|
|
|
this.onPlaybackHostOpenRequestChange()
|
|
|
this.onPlaybackHostPlaylistRequestChange()
|
|
|
|
|
|
@@ -1267,6 +1286,7 @@ export struct LocalMusic {
|
|
|
|
|
|
// 同步更新 AppStorage
|
|
|
AppStorage.setOrCreate('currentSong', this.currentSong);
|
|
|
+ this.syncDetachedPlayerState();
|
|
|
} else {
|
|
|
Logger.info(TAG, `LocalMusic: 跳过非当前播放歌曲的元数据更新: ${payload.filePath}, 当前: ${this.currentSong?.filePath}`);
|
|
|
}
|
|
|
@@ -2337,6 +2357,11 @@ export struct LocalMusic {
|
|
|
// 组件消失生命周期
|
|
|
aboutToDisappear() {
|
|
|
console.info('LifeCycleComponent aboutToDisappear');
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} aboutToDisappear enter controlStatus=${this.CONTROL_PlayStatus}, ` +
|
|
|
+ `isPlaying=${this.isPlaying}, songListLength=${this.songList.length}, ` +
|
|
|
+ `currentSong=${this.currentSong?.name ?? ''}, hostDisplayOnly=${this.isHostSnapshotDisplayOnly}, ` +
|
|
|
+ `suppressPublish=${this.suppressPlaybackCardStatePublish}`)
|
|
|
setPlaybackRuntimeReady(false)
|
|
|
this.playbackSnapshotHydrated = false
|
|
|
this.pendingInternalLoopModeReport = -1
|
|
|
@@ -2389,7 +2414,17 @@ export struct LocalMusic {
|
|
|
this.audioInterruptResumePending = false;
|
|
|
this.mIjkMediaPlayer.setScreenOnWhilePlaying(false);
|
|
|
if (this.CONTROL_PlayStatus != PlayStatus.INIT && !this.isHostSnapshotDisplayOnly) {
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} aboutToDisappear persist-and-stop reason=control-active, ` +
|
|
|
+ `controlStatus=${this.CONTROL_PlayStatus}, currentSong=${this.currentSong?.name ?? ''}`)
|
|
|
+ this.persistLocalPlaybackRecoverySnapshot('aboutToDisappear')
|
|
|
+ this.notifyPlaybackCardStateChanged()
|
|
|
+ this.suppressPlaybackCardStatePublish = true
|
|
|
this.stop();
|
|
|
+ } else {
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} aboutToDisappear skip persist-stop controlStatus=${this.CONTROL_PlayStatus}, ` +
|
|
|
+ `hostDisplayOnly=${this.isHostSnapshotDisplayOnly}`)
|
|
|
}
|
|
|
this.isHostSnapshotDisplayOnly = false;
|
|
|
this.mIjkMediaPlayer.off('audioInterrupt');
|
|
|
@@ -2887,21 +2922,36 @@ export struct LocalMusic {
|
|
|
|
|
|
private hydrateFromPlaybackSnapshot(): boolean {
|
|
|
if (this.playbackSnapshotHydrated) {
|
|
|
+ Logger.info(TAG, `${KILLCARD_TRACE} hydrateFromPlaybackSnapshot skip alreadyHydrated=true`)
|
|
|
return true
|
|
|
}
|
|
|
const snapshot = this.playbackSnapshotStore.read()
|
|
|
if (!snapshot) {
|
|
|
+ Logger.info(TAG, `${KILLCARD_TRACE} hydrateFromPlaybackSnapshot skip snapshot-empty`)
|
|
|
return false
|
|
|
}
|
|
|
const pendingActivation = peekPendingPlaybackActivation()
|
|
|
- const shouldHydrateAsPlaying = shouldHydrateSnapshotAsPlaying(snapshot.isPlaying, pendingActivation)
|
|
|
+ const hostDisplayPlaying = isPlaybackControlPlaying(
|
|
|
+ (AppStorage.get('CONTROL_PlayStatus') as number | undefined) ?? PlayStatus.INIT
|
|
|
+ )
|
|
|
+ const shouldHydrateAsPlaying = hostDisplayPlaying || shouldHydrateSnapshotAsPlaying(snapshot.isPlaying, pendingActivation)
|
|
|
Logger.info(TAG,
|
|
|
`[MiniState] hydrate snapshot song=${snapshot.currentSongName}, index=${snapshot.currentIndex}, ` +
|
|
|
`isPlaying=${snapshot.isPlaying}, shouldResume=${snapshot.shouldResumeWhenActivated}, ` +
|
|
|
- `pendingAction=${pendingActivation?.actionType ?? ''}, displayPlaying=${shouldHydrateAsPlaying}, ` +
|
|
|
+ `pendingAction=${pendingActivation?.actionType ?? ''}, hostDisplayPlaying=${hostDisplayPlaying}, ` +
|
|
|
+ `displayPlaying=${shouldHydrateAsPlaying}, ` +
|
|
|
`position=${snapshot.positionMs}, duration=${snapshot.durationMs}`)
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} hydrateFromPlaybackSnapshot snapshot song=${snapshot.currentSongName}, ` +
|
|
|
+ `index=${snapshot.currentIndex}, isPlaying=${snapshot.isPlaying}, ` +
|
|
|
+ `shouldResume=${snapshot.shouldResumeWhenActivated}, pendingAction=${pendingActivation?.actionType ?? ''}, ` +
|
|
|
+ `hostDisplayPlaying=${hostDisplayPlaying}, displayPlaying=${shouldHydrateAsPlaying}, ` +
|
|
|
+ `queueLength=${snapshot.queue.length}, position=${snapshot.positionMs}, duration=${snapshot.durationMs}`)
|
|
|
const recovered = this.playbackSnapshotStore.recoverQueueState(snapshot)
|
|
|
if (ArrayUtil.isEmpty(recovered.queue) || recovered.currentSong === undefined) {
|
|
|
+ Logger.warn(TAG,
|
|
|
+ `${KILLCARD_TRACE} hydrateFromPlaybackSnapshot recover-failed queueLength=${recovered.queue.length}, ` +
|
|
|
+ `currentIndex=${recovered.currentIndex}, hasCurrentSong=${recovered.currentSong !== undefined}`)
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
@@ -2947,11 +2997,19 @@ export struct LocalMusic {
|
|
|
AppStorage.setOrCreate('musicPlayType', this.playType)
|
|
|
this.playbackSnapshotHydrated = true
|
|
|
this.isHostSnapshotDisplayOnly = true
|
|
|
+ if (pendingActivation) {
|
|
|
+ consumePendingPlaybackActivation()
|
|
|
+ }
|
|
|
Logger.info(TAG,
|
|
|
`从后台宿主快照恢复页面状态 song=${this.currentSong.name}, index=${this.curIndex}, ` +
|
|
|
`isPlaying=${shouldHydrateAsPlaying}, rawSnapshotPlaying=${snapshot.isPlaying}, ` +
|
|
|
`shouldResume=${snapshot.shouldResumeWhenActivated}, pendingAction=${pendingActivation?.actionType ?? ''}, ` +
|
|
|
`position=${snapshot.positionMs}`)
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} hydrateFromPlaybackSnapshot applied song=${this.currentSong.name}, index=${this.curIndex}, ` +
|
|
|
+ `displayPlaying=${shouldHydrateAsPlaying}, rawSnapshotPlaying=${snapshot.isPlaying}, ` +
|
|
|
+ `controlStatus=${this.CONTROL_PlayStatus}, progress=${this.progressValue}, ` +
|
|
|
+ `isShowPlay=${this.isShowPlay}, hostDisplayOnly=${this.isHostSnapshotDisplayOnly}`)
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
@@ -2976,6 +3034,11 @@ export struct LocalMusic {
|
|
|
`[MiniState] refreshPlaybackBarFromSnapshot status=${this.CONTROL_PlayStatus}, isPlaying=${displayPlaying}, ` +
|
|
|
`pendingAction=${pendingActivation?.actionType ?? ''}, progress=${this.progressValue}, ` +
|
|
|
`song=${snapshot.currentSongName}, position=${snapshot.positionMs}, duration=${snapshot.durationMs}`)
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} refreshPlaybackBarFromSnapshot status=${this.CONTROL_PlayStatus}, ` +
|
|
|
+ `displayPlaying=${displayPlaying}, pendingAction=${pendingActivation?.actionType ?? ''}, ` +
|
|
|
+ `progress=${this.progressValue}, song=${snapshot.currentSongName}, ` +
|
|
|
+ `position=${snapshot.positionMs}, duration=${snapshot.durationMs}`)
|
|
|
}
|
|
|
|
|
|
private syncLegacyPageSessionMetadata(song: VideoItem | undefined, durationMs: number): void {
|
|
|
@@ -4079,12 +4142,12 @@ export struct LocalMusic {
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
- .bindContentCover($$this.isShowPlay, this.MusicPlayBuilder(), {
|
|
|
- modalTransition: ModalTransition.DEFAULT,
|
|
|
- onWillDisappear: () => {
|
|
|
- this.setShowPlayFalse()
|
|
|
- },
|
|
|
- })
|
|
|
+ // .bindContentCover($$this.isShowPlay, this.MusicPlayBuilder(), {
|
|
|
+ // modalTransition: ModalTransition.DEFAULT,
|
|
|
+ // onWillDisappear: () => {
|
|
|
+ // this.setShowPlayFalse()
|
|
|
+ // },
|
|
|
+ // })
|
|
|
}
|
|
|
.alignContent(Alignment.Bottom)
|
|
|
.width('100%')
|
|
|
@@ -9459,12 +9522,18 @@ export struct LocalMusic {
|
|
|
}
|
|
|
|
|
|
setShowPlayTrue(){
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} setShowPlayTrue before isShowPlay=${this.isShowPlay}, ` +
|
|
|
+ `songListLength=${this.songList.length}, currentSong=${this.currentSong?.name ?? ''}`)
|
|
|
this.getUIContext()?.animateTo({
|
|
|
duration: 500,
|
|
|
curve: Curve.Friction
|
|
|
}, () => {
|
|
|
this.isShowPlay = true;
|
|
|
this.showCoverScaleGuideIfNeeded()
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} setShowPlayTrue applied isShowPlay=${this.isShowPlay}, ` +
|
|
|
+ `currentSong=${this.currentSong?.name ?? ''}, videoUrl=${this.videoUrl}`)
|
|
|
});
|
|
|
}
|
|
|
|
|
|
@@ -9482,13 +9551,21 @@ export struct LocalMusic {
|
|
|
Logger.info(TAG,
|
|
|
`[MiniState] showPlayerView songListLength=${this.songList.length}, currentSong=${this.currentSong?.name ?? ''}, ` +
|
|
|
`videoUrl=${this.videoUrl}, isShowPlay=${this.isShowPlay}`)
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} showPlayerView enter songListLength=${this.songList.length}, ` +
|
|
|
+ `currentSong=${this.currentSong?.name ?? ''}, videoUrl=${this.videoUrl}, ` +
|
|
|
+ `isShowPlay=${this.isShowPlay}, controlStatus=${this.CONTROL_PlayStatus}`)
|
|
|
console.info('onecold showPlayerView this.songList'+ JSON.stringify(this.songList))
|
|
|
if (ArrayUtil.isNotEmpty(this.songList)) {
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} showPlayerView ready queueLength=${this.songList.length}, ` +
|
|
|
+ `currentSong=${this.currentSong?.name ?? ''}`)
|
|
|
// this.setShowPlayTrue()
|
|
|
let lyricPath = this.videoUrl.substring(0, this.videoUrl.lastIndexOf('.')) + '.lrc';
|
|
|
this.initLyric(lyricPath);
|
|
|
this.startAutoHide();
|
|
|
} else {
|
|
|
+ Logger.warn(TAG, `${KILLCARD_TRACE} showPlayerView blocked empty-queue`)
|
|
|
ToastUtil.showToast('当前播放列表为空,请先导入音乐。');
|
|
|
}
|
|
|
}
|
|
|
@@ -12710,126 +12787,100 @@ export struct LocalMusic {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- @Builder
|
|
|
- MusicPlayBuilder() {
|
|
|
- Column() {
|
|
|
- this.IjkMusicPlayerView()
|
|
|
- }
|
|
|
- .transition(TransitionEffect.asymmetric(
|
|
|
- TransitionEffect.opacity(1),
|
|
|
- TransitionEffect.OPACITY
|
|
|
- ))
|
|
|
- .visualEffect(deviceInfo.sdkApiVersion>=20&&this.bgController?
|
|
|
- new hdsEffect.HdsEffectBuilder()
|
|
|
- .shaderEffect({
|
|
|
- effectType: hdsEffect.EffectType.UV_BACKGROUND_FLOW_LIGHT,
|
|
|
- animation: {
|
|
|
- duration: 10000,
|
|
|
- iterations: -1,
|
|
|
- autoPlay: true,
|
|
|
- onFinish: ()=> {
|
|
|
- console.info('Succeeded in finishing');
|
|
|
- }
|
|
|
- },
|
|
|
- controller: this.bgController,
|
|
|
- })
|
|
|
- .buildEffect():null)
|
|
|
- .height('100%')
|
|
|
- .width('100%')
|
|
|
- .onDisAppear(() => {
|
|
|
- this.translateY = 0;
|
|
|
+ private handlePlayerPagePanUpdate(event?: GestureEvent): void {
|
|
|
+ if (!event) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const offsetY = event.offsetY ?? 0
|
|
|
+ const isSwipeUp = offsetY < 0
|
|
|
+
|
|
|
+ if (isSwipeUp) {
|
|
|
+ this.translateY = 0
|
|
|
+ this.scaleValueImage = 1
|
|
|
+ this.scaleValueText = 1
|
|
|
this.playDragUiOpacity = 1
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (this.isGeKongPlayNext) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.translateY = offsetY
|
|
|
+
|
|
|
+ this.getUIContext().animateTo({
|
|
|
+ duration: 500,
|
|
|
+ curve: Curve.Sharp
|
|
|
+ }, () => {
|
|
|
+ this.scaleValueImage = Math.min(1, Math.max(0.5, 1 - this.translateY / 880))
|
|
|
+ console.info('onecold scaleValueImage:', this.scaleValueImage)
|
|
|
+ this.scaleValueText = Math.min(1, Math.max(0.88, 1 - this.translateY / 2000))
|
|
|
+ this.playDragUiOpacity = Math.min(1, Math.max(0, 1 - this.translateY / 300))
|
|
|
})
|
|
|
- .translate({ y: this.translateY })
|
|
|
- .gesture(
|
|
|
- PanGesture(this.panOption)
|
|
|
- .onActionUpdate((event?: GestureEvent) => {
|
|
|
- if (event) {
|
|
|
- // 判断滑动方向
|
|
|
- const offsetY = event.offsetY ?? 0;
|
|
|
- const isSwipeUp = offsetY < 0;
|
|
|
-
|
|
|
- if (isSwipeUp) {
|
|
|
- // 向上滑动:不应用位移动画,保持 translateY = 0
|
|
|
- this.translateY = 0;
|
|
|
- this.scaleValueImage = 1
|
|
|
- this.scaleValueText = 1
|
|
|
- this.playDragUiOpacity = 1
|
|
|
- } else {
|
|
|
- if(this.isGeKongPlayNext){
|
|
|
- return
|
|
|
- }
|
|
|
- // 向下滑动:应用位移动画
|
|
|
- this.translateY = offsetY;
|
|
|
+ }
|
|
|
|
|
|
- // 向下滑动时的缩放效果
|
|
|
- this.getUIContext().animateTo({
|
|
|
- duration: 500,
|
|
|
- curve: Curve.Sharp
|
|
|
- }, () => {
|
|
|
- this.scaleValueImage = Math.min(1, Math.max(0.5, 1 - this.translateY / 880));
|
|
|
- console.info('onecold scaleValueImage:', this.scaleValueImage)
|
|
|
- this.scaleValueText = Math.min(1, Math.max(0.88, 1 - this.translateY / 2000));
|
|
|
- this.playDragUiOpacity = Math.min(1, Math.max(0, 1 - this.translateY / 300));
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
- })
|
|
|
- .onActionEnd((event?: GestureEvent) => {
|
|
|
- // 判断滑动方向
|
|
|
- const offsetY = event?.offsetY ?? 0;
|
|
|
- const isSwipeUp = offsetY < 0;
|
|
|
- const minDistance = 300; // 最小滑动距离
|
|
|
- const minVelocity = 500; // 最小滑动速度
|
|
|
- const velocity = event?.velocityY ?? 0;
|
|
|
-
|
|
|
- if (isSwipeUp) {
|
|
|
- // 向上滑动:切换下一首
|
|
|
- const shouldSwitch = this.isPlayPageSwipeNext &&
|
|
|
- (Math.abs(offsetY) > minDistance || Math.abs(velocity) > minVelocity);
|
|
|
-
|
|
|
- if (shouldSwitch) {
|
|
|
- // 直接切换下一首,无需动画(因为界面没有移动)
|
|
|
- this.playbackController.playNext().catch(() => {
|
|
|
- Logger.error('heanup MusicPlayBuilder', '向上滑动切换下一首失败');
|
|
|
- });
|
|
|
- }
|
|
|
- } else {
|
|
|
- //这里加个变量,是否开启隔空手势播放下一首
|
|
|
- if(this.isGeKongPlayNext){
|
|
|
- void this.playbackController.playNext()
|
|
|
- return
|
|
|
- }
|
|
|
- // 向下滑动:关闭播放页
|
|
|
- const shouldClose = this.translateY > minDistance || velocity > minVelocity;
|
|
|
-
|
|
|
- if (shouldClose) {
|
|
|
- // 添加关闭动画
|
|
|
- this.getUIContext().animateTo({
|
|
|
- duration: 600,
|
|
|
- curve: Curve.Friction
|
|
|
- }, () => {
|
|
|
- this.isShowPlay = false;
|
|
|
- this.scaleValueImage = 1
|
|
|
- this.scaleValueText = 1
|
|
|
- this.playDragUiOpacity = 1
|
|
|
- this.translateY = 0;
|
|
|
- })
|
|
|
- } else {
|
|
|
- // 回弹动画
|
|
|
- this.getUIContext().animateTo({
|
|
|
- duration: 600,
|
|
|
- curve: Curve.Friction
|
|
|
- }, () => {
|
|
|
- this.scaleValueImage = 1
|
|
|
- this.scaleValueText = 1
|
|
|
- this.playDragUiOpacity = 1
|
|
|
- this.translateY = 0;
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
+ private handlePlayerPagePanEnd(event?: GestureEvent): void {
|
|
|
+ const offsetY = event?.offsetY ?? 0
|
|
|
+ const isSwipeUp = offsetY < 0
|
|
|
+ const minDistance = 300
|
|
|
+ const minVelocity = 500
|
|
|
+ const velocity = event?.velocityY ?? 0
|
|
|
+
|
|
|
+ if (isSwipeUp) {
|
|
|
+ const shouldSwitch = this.isPlayPageSwipeNext &&
|
|
|
+ (Math.abs(offsetY) > minDistance || Math.abs(velocity) > minVelocity)
|
|
|
+
|
|
|
+ if (shouldSwitch) {
|
|
|
+ this.playbackController.playNext().catch(() => {
|
|
|
+ Logger.error('heanup MusicPlayBuilder', '向上滑动切换下一首失败')
|
|
|
})
|
|
|
- )
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (this.isGeKongPlayNext) {
|
|
|
+ void this.playbackController.playNext()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const shouldClose = this.translateY > minDistance || velocity > minVelocity
|
|
|
+
|
|
|
+ this.getUIContext().animateTo({
|
|
|
+ duration: 600,
|
|
|
+ curve: Curve.Friction
|
|
|
+ }, () => {
|
|
|
+ if (shouldClose) {
|
|
|
+ this.isShowPlay = false
|
|
|
+ }
|
|
|
+ this.scaleValueImage = 1
|
|
|
+ this.scaleValueText = 1
|
|
|
+ this.playDragUiOpacity = 1
|
|
|
+ this.translateY = 0
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ private cleanupMusiPlayerViewState(): void {
|
|
|
+ this.translateY = 0
|
|
|
+ this.playDragUiOpacity = 1
|
|
|
+ }
|
|
|
+
|
|
|
+ @Builder
|
|
|
+ MusicPlayBuilder() {
|
|
|
+ PlayerPage({
|
|
|
+ translateY: this.translateY,
|
|
|
+ enableBackgroundEffect: true,
|
|
|
+ showBackgroundEffect: deviceInfo.sdkApiVersion >= 20,
|
|
|
+ bgController: this.bgController,
|
|
|
+ panOptions: this.panOption,
|
|
|
+ onPanUpdate: (event?: GestureEvent): void => {
|
|
|
+ this.handlePlayerPagePanUpdate(event)
|
|
|
+ },
|
|
|
+ onPanEnd: (event?: GestureEvent): void => {
|
|
|
+ this.handlePlayerPagePanEnd(event)
|
|
|
+ },
|
|
|
+ onDisappearCleanup: (): void => {
|
|
|
+ this.cleanupMusiPlayerViewState()
|
|
|
+ },
|
|
|
+ contentBuilder: () => {
|
|
|
+ this.IjkMusicPlayerView()
|
|
|
+ }
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
@StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointTypeEnum.MD; //一多界面适配
|
|
|
@@ -13805,11 +13856,29 @@ export struct LocalMusic {
|
|
|
}
|
|
|
this.animationState = isPlaying ? AnimationStatus.Running : AnimationStatus.Paused
|
|
|
AppStorage.setOrCreate('animationState', this.animationState)
|
|
|
+ this.syncDetachedPlayerState()
|
|
|
Logger.info(TAG,
|
|
|
`[MiniState] syncPlaybackUiStateFromControlStatus status=${this.CONTROL_PlayStatus}, isPlaying=${this.isPlaying}, ` +
|
|
|
`hostDisplayOnly=${this.isHostSnapshotDisplayOnly}, currentSong=${this.currentSong?.name ?? ''}`)
|
|
|
}
|
|
|
|
|
|
+ private syncDetachedPlayerState(positionMs?: number, durationMs?: number): void {
|
|
|
+ const safePositionMs = positionMs !== undefined ? Math.max(0, positionMs) : Math.max(0, this.getActivePlaybackPositionMs())
|
|
|
+ const safeDurationMs = durationMs !== undefined ? Math.max(0, durationMs) : Math.max(0, this.getActiveDuration())
|
|
|
+ AppStorage.setOrCreate('songList', this.songList)
|
|
|
+ AppStorage.setOrCreate('currIndex', this.curIndex)
|
|
|
+ AppStorage.setOrCreate('currentSong', this.currentSong)
|
|
|
+ AppStorage.setOrCreate('CONTROL_PlayStatus', this.CONTROL_PlayStatus)
|
|
|
+ AppStorage.setOrCreate('progressValue', this.progressValue)
|
|
|
+ AppStorage.setOrCreate('playbackPositionMs', safePositionMs)
|
|
|
+ AppStorage.setOrCreate('playbackDurationMs', safeDurationMs)
|
|
|
+ AppStorage.setOrCreate('playbackIsFavorite', this.currentSong?.isFav === 1)
|
|
|
+ AppStorage.setOrCreate('cover', this.currentSong?.pixelMapPath ?? this.cover ?? '')
|
|
|
+ AppStorage.setOrCreate('musicPlayType', this.playType)
|
|
|
+ AppStorage.setOrCreate('currentSongName', this.currentSong?.name ?? '')
|
|
|
+ AppStorage.setOrCreate('currentSongArtist', this.currentSong?.artist ?? '')
|
|
|
+ }
|
|
|
+
|
|
|
//封面旋转动画
|
|
|
animationRoFun() {
|
|
|
if (this.isPlaying && !this.isCoverRectangle && this.isPageVisible) {
|
|
|
@@ -17823,7 +17892,16 @@ export struct LocalMusic {
|
|
|
}
|
|
|
|
|
|
private notifyPlaybackCardStateChanged(): void {
|
|
|
+ if (this.suppressPlaybackCardStatePublish) {
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} notifyPlaybackCardStateChanged skip suppressed=true, ` +
|
|
|
+ `currentSong=${this.currentSong?.name ?? ''}, controlStatus=${this.CONTROL_PlayStatus}`)
|
|
|
+ return
|
|
|
+ }
|
|
|
if (!MusicCardManager.shouldPagePublishPlaybackState(this.isHostSnapshotDisplayOnly)) {
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} notifyPlaybackCardStateChanged skip hostDisplayOnly=${this.isHostSnapshotDisplayOnly}, ` +
|
|
|
+ `currentSong=${this.currentSong?.name ?? ''}, controlStatus=${this.CONTROL_PlayStatus}`)
|
|
|
return
|
|
|
}
|
|
|
try {
|
|
|
@@ -17836,10 +17914,15 @@ export struct LocalMusic {
|
|
|
options.lyricText = this.lyricContent
|
|
|
options.coverPath = this.currentSong?.pixelMapPath ?? this.cover ?? ''
|
|
|
options.coverImageName = ''
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} notifyPlaybackCardStateChanged publish song=${this.currentSong?.name ?? ''}, ` +
|
|
|
+ `status=${this.CONTROL_PlayStatus}, isPlaying=${options.isPlaying}, ` +
|
|
|
+ `position=${options.positionMs}, duration=${options.durationMs}, cover=${options.coverPath}`)
|
|
|
const snapshot = MusicCardManager.getInstance().buildSnapshotFromPlaybackState(options)
|
|
|
MusicCardManager.getInstance().notifyPlaybackStateChanged(context, snapshot)
|
|
|
} catch (error) {
|
|
|
Logger.warn(TAG, `notifyPlaybackCardStateChanged failed: ${error}`)
|
|
|
+ Logger.warn(TAG, `${KILLCARD_TRACE} notifyPlaybackCardStateChanged failed error=${error}`)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -18018,6 +18101,7 @@ export struct LocalMusic {
|
|
|
} catch (_error) {
|
|
|
// 卡片刷新不能影响主播放流程
|
|
|
}
|
|
|
+ this.syncDetachedPlayerState(position, duration)
|
|
|
}
|
|
|
|
|
|
private startProgressTask() {
|
|
|
@@ -18042,6 +18126,7 @@ export struct LocalMusic {
|
|
|
this.currentTime = this.stringForTime(duration)
|
|
|
this.progressValue = this.PROGRESS_MAX_VALUE
|
|
|
this.slideEnable = false
|
|
|
+ this.syncDetachedPlayerState(duration, duration)
|
|
|
this.stop()
|
|
|
}
|
|
|
|
|
|
@@ -18114,6 +18199,7 @@ export struct LocalMusic {
|
|
|
this.currentSong = this.songList[this.curIndex]
|
|
|
this.applyCurrentSongMetadata(this.currentSong)
|
|
|
this.playbackStateBridge.replaceQueue(this.songList, this.curIndex)
|
|
|
+ this.syncDetachedPlayerState(0, this.getSongDurationMs(this.currentSong))
|
|
|
}
|
|
|
|
|
|
private ensureSongQueued(song: VideoItem): number {
|
|
|
@@ -18136,6 +18222,7 @@ export struct LocalMusic {
|
|
|
this.stop()
|
|
|
this.songList[this.curIndex] = song
|
|
|
this.currentSong = this.songList[this.curIndex]
|
|
|
+ this.syncDetachedPlayerState(0, this.getSongDurationMs(this.currentSong))
|
|
|
}
|
|
|
|
|
|
private applyCastTrackDurationState(durationMs: number): void {
|
|
|
@@ -18146,6 +18233,7 @@ export struct LocalMusic {
|
|
|
this.durationTime = Math.floor(durationMs / 1000)
|
|
|
this.durationStringTime = secondToTime(this.durationTime)
|
|
|
this.totalTime = this.stringForTime(durationMs)
|
|
|
+ this.syncDetachedPlayerState(0, durationMs)
|
|
|
this.syncLegacyPageSessionMetadata(this.currentSong, durationMs)
|
|
|
}
|
|
|
|
|
|
@@ -19280,6 +19368,10 @@ export struct LocalMusic {
|
|
|
|
|
|
public setIsPlaying(isPlayer: boolean) {
|
|
|
this.isPlaying = isPlayer;
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} setIsPlaying isPlayer=${isPlayer}, controlStatus=${this.CONTROL_PlayStatus}, ` +
|
|
|
+ `currentSong=${this.currentSong?.name ?? ''}, currentIndex=${this.curIndex}, ` +
|
|
|
+ `hostDisplayOnly=${this.isHostSnapshotDisplayOnly}`)
|
|
|
|
|
|
// 发送播放状态变化事件给歌单详情页面
|
|
|
try {
|
|
|
@@ -19306,9 +19398,78 @@ export struct LocalMusic {
|
|
|
} catch (error) {
|
|
|
Logger.warn(TAG, `回写 PlaybackStateBridge 失败: ${error}`)
|
|
|
}
|
|
|
+ this.syncDetachedPlayerState()
|
|
|
+ this.persistLocalPlaybackRecoverySnapshot('setIsPlaying')
|
|
|
this.notifyPlaybackCardStateChanged()
|
|
|
}
|
|
|
|
|
|
+ private persistLocalPlaybackRecoverySnapshot(reason: string): void {
|
|
|
+ if (this.isHostSnapshotDisplayOnly) {
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} persistLocalPlaybackRecoverySnapshot skip hostDisplayOnly=true reason=${reason}`)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (ArrayUtil.isEmpty(this.songList) || !this.currentSong) {
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} persistLocalPlaybackRecoverySnapshot skip missing-queue reason=${reason}, ` +
|
|
|
+ `songListLength=${this.songList.length}, hasCurrentSong=${this.currentSong !== undefined}`)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ const snapshot = this.buildLocalPlaybackRecoverySnapshot()
|
|
|
+ this.playbackSnapshotStore.write(snapshot)
|
|
|
+ Logger.info(TAG,
|
|
|
+ `[MiniState] persistLocalPlaybackRecoverySnapshot reason=${reason}, song=${snapshot.currentSongName}, ` +
|
|
|
+ `index=${snapshot.currentIndex}, isPlaying=${snapshot.isPlaying}, position=${snapshot.positionMs}, ` +
|
|
|
+ `duration=${snapshot.durationMs}, queueLength=${snapshot.queue.length}`)
|
|
|
+ Logger.info(TAG,
|
|
|
+ `${KILLCARD_TRACE} persistLocalPlaybackRecoverySnapshot reason=${reason}, ` +
|
|
|
+ `song=${snapshot.currentSongName}, index=${snapshot.currentIndex}, isPlaying=${snapshot.isPlaying}, ` +
|
|
|
+ `position=${snapshot.positionMs}, duration=${snapshot.durationMs}, queueLength=${snapshot.queue.length}, ` +
|
|
|
+ `shouldResume=${snapshot.shouldResumeWhenActivated}`)
|
|
|
+ } catch (error) {
|
|
|
+ Logger.warn(TAG, `[MiniState] persistLocalPlaybackRecoverySnapshot failed reason=${reason}, error=${error}`)
|
|
|
+ Logger.warn(TAG, `${KILLCARD_TRACE} persistLocalPlaybackRecoverySnapshot failed reason=${reason}, error=${error}`)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private buildLocalPlaybackRecoverySnapshot(): HostPlaybackSnapshot {
|
|
|
+ const queue: HostPlaybackSnapshotSong[] = this.songList.map((item: VideoItem): HostPlaybackSnapshotSong => {
|
|
|
+ return {
|
|
|
+ filePath: item.filePath,
|
|
|
+ id: item.id,
|
|
|
+ type: item.type,
|
|
|
+ name: item.name,
|
|
|
+ isFav: item.isFav,
|
|
|
+ artist: item.artist,
|
|
|
+ album: item.album,
|
|
|
+ duration: item.duration,
|
|
|
+ pixelMapPath: item.pixelMapPath,
|
|
|
+ lyricContent: item.lyricContent,
|
|
|
+ remote_rel_path: item.remote_rel_path,
|
|
|
+ webdav_account_id: item.webdav_account_id
|
|
|
+ }
|
|
|
+ })
|
|
|
+ const safeIndex = this.curIndex >= 0 && this.curIndex < this.songList.length ? this.curIndex :
|
|
|
+ (this.songList.length > 0 ? 0 : -1)
|
|
|
+ const snapshotIsPlaying = this.CONTROL_PlayStatus === PlayStatus.PLAY
|
|
|
+ return {
|
|
|
+ queue,
|
|
|
+ currentIndex: safeIndex,
|
|
|
+ currentSongKey: this.currentSong?.filePath ?? '',
|
|
|
+ currentSongName: this.currentSong?.name ?? '',
|
|
|
+ currentArtist: this.currentSong?.artist,
|
|
|
+ isPlaying: snapshotIsPlaying,
|
|
|
+ positionMs: Math.max(0, this.getActivePlaybackPositionMs()),
|
|
|
+ durationMs: this.getActiveDuration(),
|
|
|
+ cover: this.currentSong?.pixelMapPath ?? this.cover ?? '',
|
|
|
+ playType: this.playType,
|
|
|
+ playlistContext: 'page_runtime',
|
|
|
+ updatedAt: Date.now(),
|
|
|
+ shouldResumeWhenActivated: this.CONTROL_PlayStatus !== PlayStatus.INIT
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private setPlaybackStateChangeListener(): void {
|
|
|
// 注意: HarmonyOS 的投播控制器可能不返回 state 字段
|
|
|
// 我们需要通过其他方式来判断播放状态
|
|
|
@@ -19730,6 +19891,7 @@ export struct LocalMusic {
|
|
|
this.isCurrentTime = false;
|
|
|
const lyricPosition = position + this.timeOffset * 1000;
|
|
|
this.updateLyricPosition(lyricPosition);
|
|
|
+ this.syncDetachedPlayerState(position, duration)
|
|
|
}
|
|
|
/**
|
|
|
* Gesture method onActionUpdate.
|
|
|
@@ -19839,6 +20001,7 @@ export struct LocalMusic {
|
|
|
this.mDestroyPage = true;
|
|
|
this.CONTROL_PlayStatus = PlayStatus.PAUSE;
|
|
|
this.syncLegacyPageSessionState(false)
|
|
|
+ this.persistLocalPlaybackRecoverySnapshot('pause')
|
|
|
this.playChange()
|
|
|
if (this.pipController) {
|
|
|
this.pipController.updatePiPControlStatus(PiPWindow.PiPControlType.VIDEO_PLAY_PAUSE,
|