Эх сурвалжийг харах

音乐卡片和本地音乐走同样的播放路线,解决双播问题

onecold 4 сар өмнө
parent
commit
cc42c2d344
52 өөрчлөгдсөн 1304 нэмэгдсэн , 462 устгасан
  1. 0 1
      entry/src/main/ets/common/player/MusicCardActionConstants.ets
  2. 11 0
      entry/src/main/ets/common/player/MusicCardActionRouteHelper.ets
  3. 4 0
      entry/src/main/ets/common/player/MusicCardManager.ets
  4. 1 1
      entry/src/main/ets/common/player/MusicCardSnapshotStore.ets
  5. 5 0
      entry/src/main/ets/common/player/PlaybackControlStateHelper.ets
  6. 1 1
      entry/src/main/ets/common/util/LyricUtil.ets
  7. 3 0
      entry/src/main/ets/common/util/MiniPlayerHostViewHelper.ets
  8. 14 0
      entry/src/main/ets/common/util/MiniPlayerMirrorStateHelper.ets
  9. 27 0
      entry/src/main/ets/common/util/PlayerLyricUpdateTargetsHelper.ets
  10. 3 0
      entry/src/main/ets/common/util/PlayerPageOpenDispatchHelper.ets
  11. 52 0
      entry/src/main/ets/controller/AvSessionController.ets
  12. 42 3
      entry/src/main/ets/controller/MusicPlaybackController.ets
  13. 10 0
      entry/src/main/ets/controller/PlaybackCoordinator.ets
  14. 59 17
      entry/src/main/ets/entryability/EntryAbility.ets
  15. 0 0
      entry/src/main/ets/lyric/LyricController.ets
  16. 0 0
      entry/src/main/ets/lyric/LyricHelper.ets
  17. 0 0
      entry/src/main/ets/lyric/bean/Lyric.ts
  18. 0 0
      entry/src/main/ets/lyric/bean/LyricLine.ts
  19. 0 0
      entry/src/main/ets/lyric/bean/LyricLineMeasureResult.ets
  20. 0 0
      entry/src/main/ets/lyric/bean/LyricLineWrapper.ts
  21. 0 0
      entry/src/main/ets/lyric/bean/LyricWord.ts
  22. 0 0
      entry/src/main/ets/lyric/extensions/ListAdapter.ets
  23. 4 1
      entry/src/main/ets/lyric/parse/FileParser.ets
  24. 0 0
      entry/src/main/ets/lyric/parse/LyricParser.ts
  25. 0 0
      entry/src/main/ets/lyric/view/CcLyricView.ets
  26. 0 29
      entry/src/main/ets/lyric/view/LyricView2.ets
  27. 87 7
      entry/src/main/ets/pages/NewIndex.ets
  28. 176 7
      entry/src/main/ets/playback/BackgroundAudioPlaybackHost.ets
  29. 137 0
      entry/src/main/ets/playback/BackgroundAudioPlaybackHostHelper.ets
  30. 15 0
      entry/src/main/ets/playback/PlaybackHostState.ets
  31. 19 0
      entry/src/main/ets/playback/PlaybackRestoreCoordinator.ets
  32. 14 0
      entry/src/main/ets/playback/PlaybackSnapshotHydrationHelper.ets
  33. 16 4
      entry/src/main/ets/playback/PlaybackSnapshotStore.ets
  34. 1 0
      entry/src/main/ets/playback/model/PlaybackSnapshot.ets
  35. 7 3
      entry/src/main/ets/view/FindView.ets
  36. 376 170
      entry/src/main/ets/view/LocalMusic.ets
  37. 3 2
      entry/src/main/ets/view/MiniPlayerBar.ets
  38. 2 1
      entry/src/main/ets/view/PipLyricTextBuilder.ets
  39. 2 1
      entry/src/main/ets/view/player/PlayerControls.ets
  40. 13 1
      entry/src/main/ets/view/spectrum/SpectrumView.ets
  41. 25 0
      entry/src/main/ets/widget/common/MusicPlayerWidgetHelper.ets
  42. 7 7
      entry/src/main/ets/widget/pages/MusicPlayerWidgetCard.ets
  43. 7 7
      entry/src/main/ets/widget/pages/MusicPlayerWidgetDynamicLyricCard.ets
  44. 7 7
      entry/src/main/ets/widget/pages/MusicPlayerWidgetLyricCard.ets
  45. 7 7
      entry/src/main/ets/widget/pages/MusicPlayerWidgetMiniCard.ets
  46. 7 7
      entry/src/main/ets/widget/pages/MusicPlayerWidgetWideCard.ets
  47. 0 17
      lib/Index.ets
  48. 0 6
      lib/hvigorfile.ts
  49. 0 30
      lib/src/main/ets/extensions/Extension.ts
  50. 0 7
      lib/src/main/ets/parse/IParser.ts
  51. 0 13
      lib/src/main/resources/base/media/cclyric_play.svg
  52. 140 105
      oh_modules/.ohpm/lock.json5

+ 0 - 1
entry/src/main/ets/common/player/MusicCardActionConstants.ets

@@ -13,5 +13,4 @@ export class MusicCardActionConstants {
   static readonly ACTION_SEEK_TO: string = 'seek_to'
 
   static readonly PENDING_ACTION_STORAGE_KEY: string = 'musicCardPendingAction'
-  static readonly PENDING_OPEN_STORAGE_KEY: string = 'musicCardPendingOpenPlayer'
 }

+ 11 - 0
entry/src/main/ets/common/player/MusicCardActionRouteHelper.ets

@@ -0,0 +1,11 @@
+import { MusicCardActionConstants } from './MusicCardActionConstants'
+
+export function shouldIgnoreWidgetWantAction(action: string, source: string): boolean {
+  if (source !== MusicCardActionConstants.ACTION_SOURCE_WIDGET) {
+    return false
+  }
+  return action === MusicCardActionConstants.ACTION_PLAY_PAUSE
+    || action === MusicCardActionConstants.ACTION_PREVIOUS
+    || action === MusicCardActionConstants.ACTION_NEXT
+    || action === MusicCardActionConstants.ACTION_SEEK_TO
+}

+ 4 - 0
entry/src/main/ets/common/player/MusicCardManager.ets

@@ -81,6 +81,10 @@ export class MusicCardManager {
     return MusicCardManager.instance
   }
 
+  static shouldPagePublishPlaybackState(isHostSnapshotDisplayOnly: boolean): boolean {
+    return !isHostSnapshotDisplayOnly
+  }
+
   static buildSnapshotForTest(
     song: VideoItem | undefined,
     isPlaying: boolean,

+ 1 - 1
entry/src/main/ets/common/player/MusicCardSnapshotStore.ets

@@ -97,7 +97,7 @@ export class MusicCardSnapshotStore {
     try {
       const payload = JSON.stringify(snapshot ?? createEmptyMusicCardSnapshot())
       preferences.putSync(SNAPSHOT_KEY, payload)
-      preferences.flush()
+      preferences.flushSync()
       return true
     } catch (error) {
       Logger.error(TAG, `writeSnapshot failed: ${formatError(error as Object)}`)

+ 5 - 0
entry/src/main/ets/common/player/PlaybackControlStateHelper.ets

@@ -0,0 +1,5 @@
+import { PlayStatus } from '../PlayStatus'
+
+export function isPlaybackControlPlaying(status: number): boolean {
+  return status === PlayStatus.PLAY
+}

+ 1 - 1
entry/src/main/ets/common/util/LyricUtil.ets

@@ -1,7 +1,7 @@
 import fs from '@ohos.file.fs';
 import common from '@ohos.app.ability.common';
 import fileIo from '@ohos.file.fs';
-import { LyricWord } from '@seagazer/cclyric/src/main/ets/bean/LyricWord';
+import { LyricWord } from '../../lyric/bean/LyricWord';
 
 // 定义接口来描述返回值的类型
 interface ParseResult {

+ 3 - 0
entry/src/main/ets/common/util/MiniPlayerHostViewHelper.ets

@@ -0,0 +1,3 @@
+export function shouldShowMiniPlayerBar(isMultiSelect: boolean, mType: number): boolean {
+  return !(isMultiSelect || (mType > 0 && mType < 6))
+}

+ 14 - 0
entry/src/main/ets/common/util/MiniPlayerMirrorStateHelper.ets

@@ -0,0 +1,14 @@
+export class MiniPlayerMirrorState {
+  controlPlayStatus: number = 0
+  progressValue: number = 0
+  cover: string = ''
+}
+
+export function resolveMiniPlayerMirrorState(controlPlayStatus: number, progressValue: number,
+  cover: string | undefined): MiniPlayerMirrorState {
+  const mirror = new MiniPlayerMirrorState()
+  mirror.controlPlayStatus = controlPlayStatus
+  mirror.progressValue = progressValue
+  mirror.cover = cover ?? ''
+  return mirror
+}

+ 27 - 0
entry/src/main/ets/common/util/PlayerLyricUpdateTargetsHelper.ets

@@ -0,0 +1,27 @@
+export interface PlayerLyricUpdateTargets {
+  main: boolean
+  pip: boolean
+  singleLine: boolean
+}
+
+export function resolvePlayerLyricUpdateTargets(
+  isAppForeground: boolean,
+  isPageVisible: boolean,
+  showPipLyric: boolean,
+  isShowSingleLineLyric: boolean
+): PlayerLyricUpdateTargets {
+  if (!isAppForeground || !isPageVisible) {
+    return {
+      main: false,
+      pip: false,
+      singleLine: false
+    }
+  }
+
+  return {
+    // 主歌词面板需要持续跟随播放时钟,避免切回歌词页后停在旧位置。
+    main: true,
+    pip: showPipLyric,
+    singleLine: isShowSingleLineLyric
+  }
+}

+ 3 - 0
entry/src/main/ets/common/util/PlayerPageOpenDispatchHelper.ets

@@ -0,0 +1,3 @@
+export function resolvePlayerPageOpenDelayMs(mType: number): number {
+  return mType === 0 ? 0 : 32
+}

+ 52 - 0
entry/src/main/ets/controller/AvSessionController.ets

@@ -25,9 +25,24 @@ import { SettingPage } from '../pages/SettingPage';
 import { Utility } from '../common/util/Utility';
 import LyricUtil from '../common/util/LyricUtil';
 import { imagePathToPixelMap } from '../common/util/CommUtils';
+import { PlaybackSnapshot, resolvePlaybackSnapshotProgressValue } from '../playback/model/PlaybackSnapshot';
 
 const TAG = 'onecold AvSessionController';
 
+export interface SnapshotPlaybackBarState {
+  isPlaying: boolean
+  progressValue: number
+}
+
+export interface SnapshotPlaybackClockState {
+  positionMs: number
+  durationMs: number
+  currentTimeText: string
+  totalTimeText: string
+  durationTime: number
+  slideEnable: boolean
+}
+
 export class AvSessionController {
   private static instance: AvSessionController | null;
   private context: common.UIAbilityContext | undefined = undefined;
@@ -45,6 +60,43 @@ export class AvSessionController {
     return AvSessionController.instance;
   }
 
+  public static shouldPageWritePlaybackSession(isHostSnapshotDisplayOnly: boolean): boolean {
+    return !isHostSnapshotDisplayOnly
+  }
+
+  public static resolvePlaybackBarStateFromSnapshot(snapshot: PlaybackSnapshot,
+    maxValue: number): SnapshotPlaybackBarState {
+    return {
+      isPlaying: snapshot.isPlaying,
+      progressValue: resolvePlaybackSnapshotProgressValue(snapshot.positionMs, snapshot.durationMs, maxValue)
+    }
+  }
+
+  public static resolvePlaybackClockState(positionMs: number, durationMs: number): SnapshotPlaybackClockState {
+    const safeDurationMs = Math.max(0, durationMs)
+    const safePositionMs = Math.max(0, safeDurationMs > 0 ? Math.min(positionMs, safeDurationMs) : positionMs)
+    return {
+      positionMs: safePositionMs,
+      durationMs: safeDurationMs,
+      currentTimeText: AvSessionController.formatPlaybackClock(safePositionMs),
+      totalTimeText: AvSessionController.formatPlaybackClock(safeDurationMs),
+      durationTime: Math.floor(safeDurationMs / 1000),
+      slideEnable: safeDurationMs > 0
+    }
+  }
+
+  private static formatPlaybackClock(timeMs: number): string {
+    const totalSeconds = Math.max(0, Math.floor(timeMs / 1000))
+    const hours = Math.floor(totalSeconds / 3600)
+    const minutes = Math.floor((totalSeconds % 3600) / 60)
+    const seconds = totalSeconds % 60
+    if (hours > 0) {
+      return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:` +
+        `${seconds.toString().padStart(2, '0')}`
+    }
+    return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
+  }
+
   public initAvSession(isVideo:boolean) {
     if(isVideo){
       let isBgPlayOpen = PreferencesUtil.getBooleanSync(SettingPage.IS_BGPLAY_OPEN,true)

+ 42 - 3
entry/src/main/ets/controller/MusicPlaybackController.ets

@@ -1,8 +1,9 @@
 import { emitter } from '@kit.BasicServicesKit'
 import { EventConstants } from '../common/constants/EventConstants'
+import { MusicCardActionConstants } from '../common/player/MusicCardActionConstants'
 import { buildPlaylistDispatchFilePaths } from '../common/util/PlaylistPlayDispatchHelper'
 import { PlaylistPlayRequest, savePendingPlaylistPlay } from '../common/util/PlaylistPlayRequestStore'
-import { setFindPlaylist } from '../common/util/FindPlaylistStore'
+import { FindPlaylistMeta, setFindPlaylist } from '../common/util/FindPlaylistStore'
 import { PlaybackCoordinator } from './PlaybackCoordinator'
 import { VideoItem } from '../viewmodel/VideoItem'
 
@@ -17,6 +18,12 @@ export interface MusicPlaybackControllerActions {
   seekTo: (value: string, source?: string) => void | Promise<void>
 }
 
+export interface MusicPlaybackHostActions {
+  showPlayerView: () => void | Promise<void>
+  dismissPlayerView: () => void | Promise<void>
+  openPlayList: () => void | Promise<void>
+}
+
 /**
  * 内存歌单直接播放时所需的参数集合。
  */
@@ -27,6 +34,7 @@ export interface InMemoryPlaylistPlaybackOptions {
   startIndex: number
   isJump?: boolean
   playType?: number
+  findPlaylistMeta?: FindPlaylistMeta
 }
 
 /**
@@ -100,6 +108,7 @@ export type ItemPlaybackToggleAction = 'toggle_current' | 'play_target'
 export class MusicPlaybackController {
   private static instance?: MusicPlaybackController
   private actions?: MusicPlaybackControllerActions
+  private hostActions?: MusicPlaybackHostActions
   private playbackCoordinator: PlaybackCoordinator = PlaybackCoordinator.getInstance()
 
   /**
@@ -186,6 +195,10 @@ export class MusicPlaybackController {
     return 2
   }
 
+  public static shouldOpenPlayerForMusicCardAction(action: string): boolean {
+    return action === MusicCardActionConstants.ACTION_OPEN_PLAYER
+  }
+
   /**
    * 判断 AVSession 的 setLoopMode 回调是否是由应用内部刚上报的同值模式触发。
    *
@@ -446,6 +459,16 @@ export class MusicPlaybackController {
     }
   }
 
+  public setHostActions(actions: MusicPlaybackHostActions): void {
+    this.hostActions = actions
+  }
+
+  public clearHostActions(actions?: MusicPlaybackHostActions): void {
+    if (!actions || this.hostActions === actions) {
+      this.hostActions = undefined
+    }
+  }
+
   /**
    * 直接请求协调器播放单曲,不依赖页面事件宿主。
    *
@@ -544,7 +567,11 @@ export class MusicPlaybackController {
    * @returns 模式切换完成后的 Promise。
    */
   public async setLoopMode(): Promise<void> {
-    await this.actions?.setLoopMode?.()
+    if (this.actions?.setLoopMode) {
+      await this.actions.setLoopMode()
+      return
+    }
+    await this.playbackCoordinator.setLoopMode()
   }
 
   /**
@@ -562,6 +589,18 @@ export class MusicPlaybackController {
     await this.playbackCoordinator.seekTo(value, source)
   }
 
+  public async showPlayerView(): Promise<void> {
+    await this.hostActions?.showPlayerView?.()
+  }
+
+  public async dismissPlayerView(): Promise<void> {
+    await this.hostActions?.dismissPlayerView?.()
+  }
+
+  public async openPlayList(): Promise<void> {
+    await this.hostActions?.openPlayList?.()
+  }
+
   /**
    * 通过事件总线发起一组内存歌曲的统一播放,并预先写入歌单上下文。
    *
@@ -573,7 +612,7 @@ export class MusicPlaybackController {
     }
 
     const request = MusicPlaybackController.buildInMemoryPlaylistPlayRequest(options)
-    setFindPlaylist(request.playlistId, request.playlistName, options.songs, request.startIndex)
+    setFindPlaylist(request.playlistId, request.playlistName, options.songs, request.startIndex, options.findPlaylistMeta)
     savePendingPlaylistPlay(request)
     emitter.emit({ eventId: EventConstants.EVENT_PLAYLIST_PLAY }, { data: request })
   }

+ 10 - 0
entry/src/main/ets/controller/PlaybackCoordinator.ets

@@ -9,6 +9,7 @@ export interface PlaybackRuntime {
   playOrPause: () => Promise<void>
   playNext: () => Promise<void>
   playPrevious: () => Promise<void>
+  setLoopMode?: () => Promise<void>
   seekTo: (value: string, source?: string) => Promise<void>
 }
 
@@ -145,6 +146,15 @@ export class PlaybackCoordinator {
     await this.resolveRuntime()?.playPrevious()
   }
 
+  /**
+   * 切换当前播放模式。
+   *
+   * @returns 模式切换完成后的 Promise。
+   */
+  public async setLoopMode(): Promise<void> {
+    await this.resolveRuntime()?.setLoopMode?.()
+  }
+
   /**
    * 跳转到指定播放位置。
    *

+ 59 - 17
entry/src/main/ets/entryability/EntryAbility.ets

@@ -14,7 +14,7 @@ import window from '@ohos.window';
 import { AbilityConstant, appRecovery, Want } from '@kit.AbilityKit';
 import { BusinessError, emitter } from '@kit.BasicServicesKit';
 import { rpc } from '@kit.IPCKit';
-import { AppUtil, ArrayUtil, GlobalContext, LogUtil, StrUtil } from '@pura/harmony-utils';
+import { AppUtil, ArrayUtil, GlobalContext, LogUtil, PreferencesUtil, StrUtil } from '@pura/harmony-utils';
 import { DemoConstants } from './DemoConstants';
 import { EventConstants } from '../common/constants/EventConstants';
 
@@ -31,9 +31,13 @@ import { display } from '@kit.ArkUI';
 import { ServerLogUtil } from '../common/util/ServerLogUtil';
 import { PlaylistBackupManager } from '../common/util/PlaylistBackupManager';
 import { MusicCardActionConstants } from '../common/player/MusicCardActionConstants';
+import { shouldIgnoreWidgetWantAction } from '../common/player/MusicCardActionRouteHelper';
 import { MusicPlaybackController } from '../controller/MusicPlaybackController';
 import { PlaybackCoordinator } from '../controller/PlaybackCoordinator';
 import { MusicCardFormStore } from '../common/player/MusicCardFormStore';
+import { requestPlaybackPlayerOpen } from '../playback/PlaybackHostState';
+import { PlaybackRestoreCoordinator } from '../playback/PlaybackRestoreCoordinator';
+import { BackgroundAudioPlaybackHost } from '../playback/BackgroundAudioPlaybackHost';
 
 const MUSIC_CARD_ACTION_PATTERN: RegExp = /"ttmusic_music_card_action"\s*:\s*"([^"]*)"/;
 const MUSIC_CARD_FORM_ID_STRING_PATTERN: RegExp = /"ttmusic_music_card_form_id"\s*:\s*"([^"]*)"/;
@@ -66,15 +70,17 @@ export default class EntryAbility extends UIAbility {
             const source: string = this.resolveMusicCardSourceFromText(rawText);
             const formId: string = this.resolveMusicCardFormIdFromText(rawText);
             const seekPositionMs: string = this.resolveMusicCardSeekPositionFromText(rawText);
+            Logger.info('EntryAbility',
+                `[MusicCast] call enter action=${action}, source=${source}, formId=${formId}, seek=${seekPositionMs}, raw=${rawText}`);
             this.registerMusicCardFormIdIfNeeded(formId, source);
             if (StrUtil.isEmpty(action)) {
-                Logger.warn('EntryAbility', `musicCardCallHandler skipped because action empty payload=${rawText}`);
+                Logger.warn('EntryAbility', `[MusicCast] call skipped because action empty payload=${rawText}`);
                 return new EmptyRpcParcelable();
             }
             this.dispatchMusicCardAction(action, 'call', seekPositionMs);
         } catch (error) {
             const err = error as Error;
-            Logger.error('EntryAbility', `musicCardCallHandler failed: ${err.message}`);
+            Logger.error('EntryAbility', `[MusicCast] call failed: ${err.message}`);
         }
         return new EmptyRpcParcelable();
     }
@@ -88,19 +94,19 @@ export default class EntryAbility extends UIAbility {
         }
         Logger.info('EntryAbility', `musicCardActionForward action=${action}`);
         if (action === 'play_pause') {
-            this.context.eventHub?.emit('playOrPause');
+            void MusicPlaybackController.getInstance().playOrPause();
             return;
         }
         if (action === 'prev_song') {
-            this.context.eventHub?.emit('playPrevious');
+            void MusicPlaybackController.getInstance().playPrevious();
             return;
         }
         if (action === 'next_song') {
-            this.context.eventHub?.emit('playNext');
+            void MusicPlaybackController.getInstance().playNext();
             return;
         }
         if (action === 'open_player') {
-            this.context.eventHub?.emit('showPlayerView');
+            void MusicPlaybackController.getInstance().showPlayerView();
             return;
         }
         if (action === MusicCardActionConstants.ACTION_SEEK_TO) {
@@ -169,7 +175,15 @@ export default class EntryAbility extends UIAbility {
     }
 
     async onCreate(want:Want, launchParam:AbilityConstant.LaunchParam) {
+        AppUtil.init(this.context);
         AppStorage.setOrCreate('context', this.context);
+        BackgroundAudioPlaybackHost.getInstance().setContext(this.context);
+        try {
+            PreferencesUtil.init('SP_HARMONY_UTILS_PREFERENCES');
+            Logger.info('EntryAbility', '[MusicCast] PreferencesUtil init success onCreate');
+        } catch (error) {
+            Logger.error('EntryAbility', `[MusicCast] PreferencesUtil init failed onCreate: ${(error as Error).message}`);
+        }
         this.context.eventHub?.on('musicCardActionForward', this.musicCardActionForwardHandler);
         this.registerMusicCardCallHandler();
         this.handleMusicCardActionFromWant(want);
@@ -237,6 +251,14 @@ export default class EntryAbility extends UIAbility {
     async onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
         hilog.info(0x0000, 'testTag', `onNewWant, want=${JSON.stringify(want)}`);
         super.onNewWant(want, launchParam);
+        AppUtil.init(this.context);
+        BackgroundAudioPlaybackHost.getInstance().setContext(this.context);
+        try {
+            PreferencesUtil.init('SP_HARMONY_UTILS_PREFERENCES');
+            Logger.info('EntryAbility', '[MusicCast] PreferencesUtil init success onNewWant');
+        } catch (error) {
+            Logger.error('EntryAbility', `[MusicCast] PreferencesUtil init failed onNewWant: ${(error as Error).message}`);
+        }
         this.handleMusicCardActionFromWant(want);
         this.loadDoWant(want)
         await this.handleParam(want)
@@ -258,54 +280,71 @@ export default class EntryAbility extends UIAbility {
         if (StrUtil.isEmpty(action)) {
             return;
         }
+        if (shouldIgnoreWidgetWantAction(action, source)) {
+            Logger.info('EntryAbility',
+                `[MusicCast] want ignored because call will handle action=${action}, source=${source}, formId=${formId}`);
+            return;
+        }
+        Logger.info('EntryAbility',
+            `[MusicCast] want enter action=${action}, source=${source}, formId=${formId}, seek=${seekPositionMs}`);
         this.dispatchMusicCardAction(action, 'want', seekPositionMs);
     }
 
     private dispatchMusicCardAction(action: string, source: string, seekPositionMs: string = ''): void {
-        Logger.info('EntryAbility', `dispatch music card action=${action}, source=${source}, seek=${seekPositionMs}`);
+        Logger.info('EntryAbility', `[MusicCast] dispatch action=${action}, source=${source}, seek=${seekPositionMs}`);
         const playbackController: MusicPlaybackController = MusicPlaybackController.getInstance();
         const playbackCoordinator: PlaybackCoordinator = PlaybackCoordinator.getInstance();
+        const backgroundHost = BackgroundAudioPlaybackHost.getInstance();
         if (action === MusicCardActionConstants.ACTION_PLAY_PAUSE) {
             if (playbackCoordinator.hasRuntime()) {
+                Logger.info('EntryAbility', '[MusicCast] dispatch -> runtime playOrPause');
                 void playbackController.playOrPause();
             } else {
-                AppStorage.setOrCreate(MusicCardActionConstants.PENDING_ACTION_STORAGE_KEY, action);
+                Logger.info('EntryAbility', '[MusicCast] dispatch -> background host playOrPause');
+                void backgroundHost.handleControlAction(action);
             }
             return;
         }
         if (action === MusicCardActionConstants.ACTION_PREVIOUS) {
             if (playbackCoordinator.hasRuntime()) {
+                Logger.info('EntryAbility', '[MusicCast] dispatch -> runtime playPrevious');
                 void playbackController.playPrevious();
             } else {
-                AppStorage.setOrCreate(MusicCardActionConstants.PENDING_ACTION_STORAGE_KEY, action);
+                Logger.info('EntryAbility', '[MusicCast] dispatch -> background host playPrevious');
+                void backgroundHost.handleControlAction(action);
             }
             return;
         }
         if (action === MusicCardActionConstants.ACTION_NEXT) {
             if (playbackCoordinator.hasRuntime()) {
+                Logger.info('EntryAbility', '[MusicCast] dispatch -> runtime playNext');
                 void playbackController.playNext();
             } else {
-                AppStorage.setOrCreate(MusicCardActionConstants.PENDING_ACTION_STORAGE_KEY, action);
+                Logger.info('EntryAbility', '[MusicCast] dispatch -> background host playNext');
+                void backgroundHost.handleControlAction(action);
             }
             return;
         }
         if (action === MusicCardActionConstants.ACTION_OPEN_PLAYER) {
+            Logger.info('EntryAbility', '[MusicCast] dispatch -> routeToMusicPlayerPage');
             this.routeToMusicPlayerPage();
             return;
         }
         if (action === MusicCardActionConstants.ACTION_SEEK_TO) {
             if (!playbackCoordinator.hasRuntime()) {
-                Logger.warn('EntryAbility', `music card seek ignored because runtime missing, source=${source}`);
+                Logger.info('EntryAbility', `[MusicCast] dispatch -> background host seekTo=${seekPositionMs}`);
+                void backgroundHost.handleControlAction(action, seekPositionMs);
                 return;
             }
             if (StrUtil.isEmpty(seekPositionMs)) {
-                Logger.warn('EntryAbility', `music card seek ignored because seek empty, source=${source}`);
+                Logger.warn('EntryAbility', `[MusicCast] seek ignored because seek empty, source=${source}`);
                 return;
             }
+            Logger.info('EntryAbility', `[MusicCast] dispatch -> runtime seekTo=${seekPositionMs}`);
             void playbackController.seekTo(seekPositionMs, 'music-card-widget');
             return;
         }
-        Logger.warn('EntryAbility', `music card action ignored unsupported action=${action}, source=${source}`);
+        Logger.warn('EntryAbility', `[MusicCast] action ignored unsupported action=${action}, source=${source}`);
     }
 
     private registerMusicCardCallHandler(): void {
@@ -389,11 +428,14 @@ export default class EntryAbility extends UIAbility {
     }
 
     private routeToMusicPlayerPage(): void {
-        if (PlaybackCoordinator.getInstance().hasRuntime()) {
-            this.context.eventHub?.emit('showPlayerView');
+        const openRoute = PlaybackRestoreCoordinator.resolveMusicCardOpenRoute(
+            PlaybackCoordinator.getInstance().hasRuntime()
+        );
+        if (openRoute === 'show_player_now') {
+            void MusicPlaybackController.getInstance().showPlayerView();
             return;
         }
-        AppStorage.setOrCreate(MusicCardActionConstants.PENDING_OPEN_STORAGE_KEY, true);
+        requestPlaybackPlayerOpen();
     }
 
     private handleWeChatCallIfNeed(want: Want) {

+ 0 - 0
lib/src/main/ets/LyricController.ets → entry/src/main/ets/lyric/LyricController.ets


+ 0 - 0
lib/src/main/ets/LyricHelper.ets → entry/src/main/ets/lyric/LyricHelper.ets


+ 0 - 0
lib/src/main/ets/bean/Lyric.ts → entry/src/main/ets/lyric/bean/Lyric.ts


+ 0 - 0
lib/src/main/ets/bean/LyricLine.ts → entry/src/main/ets/lyric/bean/LyricLine.ts


+ 0 - 0
lib/src/main/ets/bean/LyricLineMeasureResult.ets → entry/src/main/ets/lyric/bean/LyricLineMeasureResult.ets


+ 0 - 0
lib/src/main/ets/bean/LyricLineWrapper.ts → entry/src/main/ets/lyric/bean/LyricLineWrapper.ts


+ 0 - 0
lib/src/main/ets/bean/LyricWord.ts → entry/src/main/ets/lyric/bean/LyricWord.ts


+ 0 - 0
lib/src/main/ets/extensions/ListAdapter.ets → entry/src/main/ets/lyric/extensions/ListAdapter.ets


+ 4 - 1
lib/src/main/ets/parse/FileParser.ets → entry/src/main/ets/lyric/parse/FileParser.ets

@@ -1,6 +1,9 @@
-import { IParser, Lyric, LyricParser, LyricLine } from '@seagazer/cclyric';
+
 import fs from '@ohos.file.fs'
+import { Lyric } from '../bean/Lyric';
 import { printD, printW } from '../extensions/Extension';
+import { IParser } from './IParser';
+import { LyricParser } from './LyricParser';
 
 /**
  * Author: seagazer

+ 0 - 0
lib/src/main/ets/parse/LyricParser.ts → entry/src/main/ets/lyric/parse/LyricParser.ts


+ 0 - 0
lib/src/main/ets/view/CcLyricView.ets → entry/src/main/ets/lyric/view/CcLyricView.ets


+ 0 - 29
lib/src/main/ets/view/LyricView2.ets → entry/src/main/ets/lyric/view/LyricView2.ets

@@ -686,35 +686,6 @@ export struct LyricView2 {
     }
 
 
-    @Builder
-    SeekLine() {
-        Row() {
-            Image($r('app.media.cclyric_play'))
-                .width(24)
-                .height(24)
-                .fillColor(this.seekUIColor)
-                .objectFit(ImageFit.Fill)
-                .clickEffect({ level: ClickEffectLevel.MIDDLE })
-                .onClick(() => {
-                    if (this.seekIndex != -1 && this.seekIndex != this.currentIndex) {
-                        this.handleSeekAction()
-                    }
-                })
-            Stack()
-                .height(1)
-                .layoutWeight(1)
-                .backgroundColor(this.seekLineColor)
-                .margin({ left: 8, right: 8 })
-            Text(this.scrollDurationText)
-                .fontSize(this.textSize)
-                .fontColor(this.seekUIColor)
-        }
-        .visibility(this.enableSeek && this.isUserTouching ? Visibility.Visible : Visibility.Hidden)
-        .width('100%')
-        .height('100%')
-        .hitTestBehavior(HitTestMode.Transparent)
-        .transition(TransitionEffect.OPACITY.animation({ duration: this.animDuration }))
-    }
 
     build() {
         Stack() {

+ 87 - 7
entry/src/main/ets/pages/NewIndex.ets

@@ -66,6 +66,9 @@ import { MiniPlayerBar } from '../view/MiniPlayerBar';
 import {
   resolveMiniPlayerMorphTarget,
 } from '../common/util/PlayerDismissHelper';
+import { shouldShowMiniPlayerBar } from '../common/util/MiniPlayerHostViewHelper';
+import { resolvePlayerPageOpenDelayMs } from '../common/util/PlayerPageOpenDispatchHelper';
+import { resolveMiniPlayerMirrorState } from '../common/util/MiniPlayerMirrorStateHelper';
 import {
   MiniPlayerOrbTapAction,
   resolveMiniPlayerOrbTapAction,
@@ -104,9 +107,9 @@ struct NewIndex {
     new hdsEffect.ShaderEffectController():undefined;
   @State defalut_home_type:number = 0//首页默认类型 可支持首页 媒体库 歌单 网盘
   @State isDetailView: boolean = false; // 是否在艺术家/专辑详情视图
-  @Provide @Watch('syncMiniPlayerVisibility') isShowPlay: boolean = false;
-  @Provide CONTROL_PlayStatus: number = PlayStatus.INIT;
-  @Provide progressValue: number = 0;
+  @StorageLink('isShowPlay') @Watch('syncMiniPlayerVisibility') isShowPlay: boolean = false;
+  @Provide @Watch('onMiniPlayerControlStatusChanged') CONTROL_PlayStatus: number = PlayStatus.INIT;
+  @Provide @Watch('onMiniPlayerProgressChanged') progressValue: number = 0;
   @State isShowPrecious: boolean = false //播控条显示上一首按钮
   @State bottomBarHeight: number = 64;
   // 迷你播放条的整体形变状态,既用于出现/消失,也用于 full bar <-> orb 切换。
@@ -130,9 +133,15 @@ struct NewIndex {
   @State isMiniPlayerOrbMode: boolean = false;
   @State isMiniPlayerMounted: boolean = true;
   @StorageProp('bottomSafeHeight') bottomSafeHeight: number = 0;
-  @Provide cover: string | undefined = '';
+  @Provide @Watch('onMiniPlayerCoverChanged') cover: string | undefined = '';
   // @Provide currentSong: VideoItem | undefined = undefined;
   @StorageLink('currentSong') currentSong: VideoItem | undefined = undefined;
+  @StorageLink('CONTROL_PlayStatus') @Watch('onHostMiniPlayerControlStatusChanged')
+  hostControlPlayStatus: number = PlayStatus.INIT;
+  @StorageLink('progressValue') @Watch('onHostMiniPlayerProgressChanged')
+  hostProgressValue: number = 0;
+  @StorageLink('cover') @Watch('onHostMiniPlayerCoverChanged')
+  hostCover: string | undefined = '';
   @StorageLink('playbackRuntimeReady') @Watch('syncPlaybackRuntimeBinding')
   playbackRuntimeReady: boolean = false;
   @Provide @Watch('syncMiniPlayerVisibility') isMultiSelect: boolean = false;
@@ -235,8 +244,10 @@ struct NewIndex {
   private readonly miniPlayerModeBounceDuration: number = 220;
   private readonly miniPlayerOrbDoubleTapWindowMs: number = 240;
   private isMiniPlayerModeTransitioning: boolean = false;
+  private pendingPlayerPageOpenTimer: number = -1;
   private readonly playbackHostActions: MusicPlaybackHostActions = {
     showPlayerView: (): void => {
+      LogUtil.info(TAG, '[MiniState] hostActions.showPlayerView -> requestPlaybackPlayerOpen')
       requestPlaybackPlayerOpen()
     },
     dismissPlayerView: (): void => {
@@ -247,6 +258,49 @@ struct NewIndex {
     }
   }
 
+  private logMiniPlayerState(stage: string): void {
+    LogUtil.info(TAG,
+      `[MiniState] ${stage} localStatus=${this.CONTROL_PlayStatus}, hostStatus=${this.hostControlPlayStatus}, ` +
+      `localProgress=${this.progressValue}, hostProgress=${this.hostProgressValue}, ` +
+      `localCover=${this.cover ?? ''}, hostCover=${this.hostCover ?? ''}, ` +
+      `song=${this.currentSong?.name ?? ''}`)
+  }
+
+  private syncMiniPlayerStateFromHost(reason: string): void {
+    const mirror = resolveMiniPlayerMirrorState(this.hostControlPlayStatus, this.hostProgressValue, this.hostCover)
+    this.CONTROL_PlayStatus = mirror.controlPlayStatus
+    this.progressValue = mirror.progressValue
+    this.cover = mirror.cover
+    this.logMiniPlayerState(`mirrored from host: ${reason}`)
+  }
+
+  private onMiniPlayerControlStatusChanged(): void {
+    this.logMiniPlayerState('local status changed')
+  }
+
+  private onMiniPlayerProgressChanged(): void {
+    this.logMiniPlayerState('local progress changed')
+  }
+
+  private onMiniPlayerCoverChanged(): void {
+    this.logMiniPlayerState('local cover changed')
+  }
+
+  private onHostMiniPlayerControlStatusChanged(): void {
+    this.logMiniPlayerState('host status changed')
+    this.syncMiniPlayerStateFromHost('status')
+  }
+
+  private onHostMiniPlayerProgressChanged(): void {
+    this.logMiniPlayerState('host progress changed')
+    this.syncMiniPlayerStateFromHost('progress')
+  }
+
+  private onHostMiniPlayerCoverChanged(): void {
+    this.logMiniPlayerState('host cover changed')
+    this.syncMiniPlayerStateFromHost('cover')
+  }
+
   private syncPlaybackRuntimeBinding(): void {
     this.playbackCoordinator.bindDefaultRuntime(BackgroundAudioPlaybackHost.getInstance().getRuntime())
     const runtime = getRegisteredPlaybackRuntime()
@@ -394,6 +448,8 @@ struct NewIndex {
 
   async aboutToAppear() {
     ensurePlaybackHostStorageDefaults()
+    this.syncMiniPlayerStateFromHost('aboutToAppear')
+    this.logMiniPlayerState('aboutToAppear after defaults')
     this.playbackController.setHostActions(this.playbackHostActions)
     this.syncPlaybackRuntimeBinding()
     this.defalut_home_type = PreferencesUtil.getNumberSync('defalut_home_type', 0)
@@ -666,6 +722,10 @@ struct NewIndex {
   }
 
   private clearMiniPlayerAnimationTimer(): void {
+    if (this.pendingPlayerPageOpenTimer >= 0) {
+      clearTimeout(this.pendingPlayerPageOpenTimer)
+      this.pendingPlayerPageOpenTimer = -1
+    }
     if (this.miniPlayerAnimationTimer >= 0) {
       clearTimeout(this.miniPlayerAnimationTimer)
       this.miniPlayerAnimationTimer = -1
@@ -854,7 +914,7 @@ struct NewIndex {
   }
 
   private shouldShowMiniPlayer(): boolean {
-    return !(this.isMultiSelect || (this.mType > 0 && this.mType < 6) || this.isShowPlay)
+    return shouldShowMiniPlayerBar(this.isMultiSelect, this.mType)
   }
 
   // 同步播放条挂载与显示状态。
@@ -1255,10 +1315,30 @@ struct NewIndex {
   }
 
   setShowPlayTrue(){
-    if (this.isShowPlay || this.isMiniPlayerModeTransitioning) {
+    if (this.isMiniPlayerModeTransitioning) {
+      return
+    }
+    const openDelayMs = resolvePlayerPageOpenDelayMs(this.mType)
+    if (this.mType !== 0) {
+      LogUtil.info(TAG, `[MiniState] setShowPlayTrue switch host page mType=${this.mType} -> 0`)
+      this.mType = 0
+      this.tabSelectedIndexes = [0]
+    }
+    if (this.pendingPlayerPageOpenTimer >= 0) {
+      clearTimeout(this.pendingPlayerPageOpenTimer)
+      this.pendingPlayerPageOpenTimer = -1
+    }
+    if (openDelayMs <= 0) {
+      LogUtil.info(TAG, '[MiniState] setShowPlayTrue dispatch open immediately')
+      void this.playbackController.showPlayerView()
       return
     }
-    void this.playbackController.showPlayerView()
+    LogUtil.info(TAG, `[MiniState] setShowPlayTrue defer open delayMs=${openDelayMs}`)
+    this.pendingPlayerPageOpenTimer = setTimeout(() => {
+      this.pendingPlayerPageOpenTimer = -1
+      LogUtil.info(TAG, '[MiniState] deferred open -> showPlayerView')
+      void this.playbackController.showPlayerView()
+    }, openDelayMs)
   }
 
   // 右侧圆球内部内容:封面、暗罩、白色环形进度和中心播放状态指示。

+ 176 - 7
entry/src/main/ets/playback/BackgroundAudioPlaybackHost.ets

@@ -1,7 +1,8 @@
-import { common, Context } from '@kit.AbilityKit'
+import { common, Context, wantAgent, Want } from '@kit.AbilityKit'
 import { BusinessError } from '@kit.BasicServicesKit'
 import { avSession } from '@kit.AVSessionKit'
-import { PreferencesUtil } from '@pura/harmony-utils'
+import { image } from '@kit.ImageKit'
+import { FileUtil, ImageUtil, PreferencesUtil, StrUtil } from '@pura/harmony-utils'
 import {
   DeviceChangeReason,
   InterruptEvent,
@@ -16,9 +17,11 @@ import {
 } from '../common/IjkPlayerListenerImpls'
 import { PlayStatus } from '../common/PlayStatus'
 import { MusicCardActionConstants } from '../common/player/MusicCardActionConstants'
+import { isPlaybackControlPlaying } from '../common/player/PlaybackControlStateHelper'
 import { MusicCardManager, MusicCardPlaybackStateOptions } from '../common/player/MusicCardManager'
 import { BackgroundTaskManager } from '../common/util/BackgroundTaskManager'
 import Logger from '../common/util/Logger'
+import { imagePathToPixelMap } from '../common/util/CommUtils'
 import { setVideoUrlForSong } from '../common/util/RemotePlayerUtil'
 import { MusicPlaybackController } from '../controller/MusicPlaybackController'
 import { PlaybackRuntime } from '../controller/PlaybackCoordinator'
@@ -59,6 +62,7 @@ export class BackgroundAudioPlaybackHost {
   private playbackStartVerifyToken: number = 0
   private playbackSession: avSession.AVSession | undefined = undefined
   private creatingPlaybackSession: boolean = false
+  private playbackSessionCallbacksRegistered: boolean = false
   private snapshotStore: PlaybackSnapshotWriter = new PlaybackSnapshotStore()
   private readonly runtime: PlaybackRuntime = {
     playQueue: async (queue: VideoItem[], startIndex: number, source: string, playType?: number): Promise<void> => {
@@ -73,6 +77,9 @@ export class BackgroundAudioPlaybackHost {
     playPrevious: async (): Promise<void> => {
       await this.playPrevious()
     },
+    setLoopMode: async (): Promise<void> => {
+      await this.setLoopMode()
+    },
     seekTo: async (value: string): Promise<void> => {
       await this.seekTo(value)
     }
@@ -133,6 +140,29 @@ export class BackgroundAudioPlaybackHost {
     await this.handleControlAction(MusicCardActionConstants.ACTION_SEEK_TO, value)
   }
 
+  public async setLoopMode(): Promise<void> {
+    this.restorePersistedQueueIfNeeded()
+    const nextMode = MusicPlaybackController.resolveNextLoopMode(this.playType)
+    this.playType = nextMode.playType
+    PreferencesUtil.putSync('musicPlayType', this.playType)
+    this.persistCurrentQueue()
+    this.syncHostStorage(this.isPlaying ? PlayStatus.PLAY : PlayStatus.PAUSE)
+    this.syncPlaybackSessionState(this.isPlaying ? PlayStatus.PLAY : PlayStatus.PAUSE)
+    this.persistPlaybackSnapshot(this.isPlaying)
+    Logger.info(TAG, `[MusicCast] setLoopMode playType=${this.playType}, toast=${nextMode.toastText}`)
+  }
+
+  public syncCurrentSongFavoriteState(isFavorite: boolean): void {
+    this.applyCurrentSongFavoriteState(isFavorite)
+  }
+
+  public getSpectrumData(): number[] {
+    if (!this.player) {
+      return []
+    }
+    return this.player.getSpectrumData()
+  }
+
   public async handleControlAction(action: string, seekPositionMs: string = ''): Promise<boolean> {
     try {
       Logger.info(TAG,
@@ -473,7 +503,7 @@ export class BackgroundAudioPlaybackHost {
       this.currentSong.lyricContent,
       this.currentSong.pixelMapPath
     )
-    AppStorage.setOrCreate('progressValue', progressValue)
+    this.syncPlaybackDisplayState(progressValue, positionMs, durationMs)
     this.syncPlaybackSessionState(this.isPlaying ? PlayStatus.PLAY : PlayStatus.PAUSE)
     this.persistPlaybackSnapshot(this.isPlaying)
   }
@@ -500,6 +530,8 @@ export class BackgroundAudioPlaybackHost {
         const err = error as BusinessError
         Logger.error(TAG, `[MusicCast] playbackSession activate failed code=${err.code}, message=${err.message}`)
       }
+      await this.setPlaybackSessionLaunchAbility(abilityContext, session)
+      this.registerPlaybackSessionCallbacks(session)
       await this.syncPlaybackSessionMetadata()
       this.syncPlaybackSessionState(this.isPlaying ? PlayStatus.PLAY : PlayStatus.PAUSE)
     } catch (error) {
@@ -510,15 +542,94 @@ export class BackgroundAudioPlaybackHost {
     }
   }
 
+  private async setPlaybackSessionLaunchAbility(abilityContext: common.UIAbilityContext,
+    session: avSession.AVSession): Promise<void> {
+    try {
+      const want = new Want()
+      want.bundleName = abilityContext.abilityInfo.bundleName
+      want.abilityName = abilityContext.abilityInfo.name
+      const wantAgentInfo: wantAgent.WantAgentInfo = {
+        wants: [want],
+        operationType: wantAgent.OperationType.START_ABILITIES,
+        requestCode: 0,
+        wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
+      }
+      const agent = await wantAgent.getWantAgent(wantAgentInfo)
+      await session.setLaunchAbility(agent)
+      Logger.info(TAG, '[MusicCast] playbackSession launchAbility attached')
+    } catch (error) {
+      const err = error as BusinessError
+      Logger.error(TAG, `[MusicCast] playbackSession launchAbility failed code=${err.code}, message=${err.message}`)
+    }
+  }
+
+  private registerPlaybackSessionCallbacks(session: avSession.AVSession): void {
+    if (this.playbackSessionCallbacksRegistered) {
+      return
+    }
+    session.on('play', () => {
+      void this.handlePlaybackSessionPlay()
+    })
+    session.on('pause', () => {
+      this.handlePlaybackSessionPause()
+    })
+    session.on('stop', () => {
+      this.handlePlaybackSessionStop()
+    })
+    session.on('playNext', () => {
+      void this.playNext()
+    })
+    session.on('playPrevious', () => {
+      void this.playPrevious()
+    })
+    session.on('seek', (time: number) => {
+      void this.seekTo(`${time}`)
+    })
+    session.on('setLoopMode', (_mode: number) => {
+      void this.setLoopMode()
+    })
+    session.on('toggleFavorite', (_assetId: string) => {
+      this.toggleCurrentSongFavoriteState()
+    })
+    this.playbackSessionCallbacksRegistered = true
+  }
+
+  private async handlePlaybackSessionPlay(): Promise<void> {
+    this.restorePersistedQueueIfNeeded()
+    if (this.isPrepared && !this.isPlaying) {
+      this.resume()
+      return
+    }
+    if (this.currentIndex >= 0) {
+      await this.playIndex(this.currentIndex)
+      return
+    }
+    if (this.queue.length > 0) {
+      await this.playIndex(0)
+    }
+  }
+
+  private handlePlaybackSessionPause(): void {
+    if (this.isPlaying) {
+      this.pause()
+    }
+  }
+
+  private handlePlaybackSessionStop(): void {
+    this.stop()
+  }
+
   private async syncPlaybackSessionMetadata(): Promise<void> {
     if (!this.playbackSession || !this.currentSong) {
       return
     }
     try {
+      const mediaImage = await this.resolvePlaybackSessionMediaImage()
       let metadata: avSession.AVMetadata = {
         assetId: this.currentSong.filePath ?? this.currentSong.name ?? '',
         title: this.currentSong.name,
         artist: this.currentSong.artist,
+        mediaImage: mediaImage,
         duration: this.resolveDurationMs()
       }
       await this.playbackSession.setAVMetadata(metadata)
@@ -530,6 +641,22 @@ export class BackgroundAudioPlaybackHost {
     }
   }
 
+  private async resolvePlaybackSessionMediaImage(): Promise<image.PixelMap | string> {
+    const coverPath = this.currentSong?.pixelMapPath ?? ''
+    if (StrUtil.isNotEmpty(coverPath)) {
+      if (coverPath.startsWith('http://') || coverPath.startsWith('https://')) {
+        return coverPath
+      }
+      try {
+        const localCoverPath = coverPath.startsWith('file://') ? FileUtil.getFilePath(coverPath) : coverPath
+        return await imagePathToPixelMap(localCoverPath)
+      } catch (error) {
+        Logger.warn(TAG, `[MusicCast] cover pixelMap failed path=${coverPath}, message=${(error as Error).message}`)
+      }
+    }
+    return await ImageUtil.getPixelMapFromMedia($r('app.media.alt'))
+  }
+
   private syncPlaybackSessionState(status: number): void {
     if (!this.playbackSession || !this.player) {
       return
@@ -544,6 +671,8 @@ export class BackgroundAudioPlaybackHost {
     }
     let playbackState: avSession.AVPlaybackState = {
       state: state,
+      loopMode: MusicPlaybackController.resolveAvSessionLoopMode(this.playType),
+      isFavorite: this.currentSong?.isFav === 1,
       position: {
         elapsedTime: Math.max(0, this.player.getCurrentPosition()),
         updateTime: Date.now()
@@ -604,7 +733,12 @@ export class BackgroundAudioPlaybackHost {
   }
 
   private persistPlaybackSnapshot(isPlaying: boolean): void {
-    this.snapshotStore.write(this.buildPlaybackSnapshot(isPlaying))
+    const snapshot = this.buildPlaybackSnapshot(isPlaying)
+    Logger.info(TAG,
+      `[MiniState] persistPlaybackSnapshot song=${snapshot.currentSongName}, index=${snapshot.currentIndex}, ` +
+      `isPlaying=${snapshot.isPlaying}, shouldResume=${snapshot.shouldResumeWhenActivated}, ` +
+      `position=${snapshot.positionMs}, duration=${snapshot.durationMs}`)
+    this.snapshotStore.write(snapshot)
   }
 
   private buildPlaybackSnapshot(isPlaying: boolean): PlaybackSnapshot {
@@ -615,6 +749,7 @@ export class BackgroundAudioPlaybackHost {
           id: item.id,
           type: item.type,
           name: item.name,
+          isFav: item.isFav,
           artist: item.artist,
           album: item.album,
           duration: item.duration,
@@ -671,19 +806,25 @@ export class BackgroundAudioPlaybackHost {
     PreferencesUtil.putSync('LastMusicInfo', this.currentSong)
     PreferencesUtil.putSync('LastMusicList', this.queue)
     PreferencesUtil.putSync('LastPlayModeType', this.playType)
+    PreferencesUtil.putSync('musicPlayType', this.playType)
     Logger.info(TAG,
       `[MusicCast] persist queueLength=${this.queue.length}, currentIndex=${this.currentIndex}, ` +
       `currentSong=${this.currentSong?.name ?? ''}, path=${this.currentSong?.filePath ?? ''}, playType=${this.playType}`)
   }
 
   private syncHostStorage(status: PlayStatus): void {
+    const isPlaying = isPlaybackControlPlaying(status)
+    const positionMs = this.player ? Math.max(0, this.player.getCurrentPosition()) : 0
+    const durationMs = this.resolveDurationMs()
+    const progressValue = resolvePlaybackSnapshotProgressValue(positionMs, durationMs, 100)
     AppStorage.setOrCreate('CONTROL_PlayStatus', status)
+    AppStorage.setOrCreate('isPlaying', isPlaying)
+    AppStorage.setOrCreate('animationState', isPlaying ? AnimationStatus.Running : AnimationStatus.Paused)
+    AppStorage.setOrCreate('musicPlayType', this.playType)
     AppStorage.setOrCreate('songList', this.queue)
     AppStorage.setOrCreate('currIndex', this.currentIndex)
     AppStorage.setOrCreate('currentSong', this.currentSong)
-    AppStorage.setOrCreate('progressValue',
-      resolvePlaybackSnapshotProgressValue(this.player ? Math.max(0, this.player.getCurrentPosition()) : 0,
-        this.resolveDurationMs(), 100))
+    this.syncPlaybackDisplayState(progressValue, positionMs, durationMs)
     AppStorage.setOrCreate('cover', this.currentSong?.pixelMapPath ?? '')
     AppStorage.setOrCreate('currentSongName', this.currentSong?.name ?? '')
     AppStorage.setOrCreate('currentSongArtist', this.currentSong?.artist ?? '')
@@ -691,4 +832,32 @@ export class BackgroundAudioPlaybackHost {
       `[MusicCast] syncHostStorage status=${status}, currentSong=${this.currentSong?.name ?? ''}, ` +
       `cover=${this.currentSong?.pixelMapPath ?? ''}`)
   }
+
+  private syncPlaybackDisplayState(progressValue: number, positionMs: number, durationMs: number): void {
+    AppStorage.setOrCreate('progressValue', progressValue)
+    AppStorage.setOrCreate('playbackPositionMs', positionMs)
+    AppStorage.setOrCreate('playbackDurationMs', durationMs)
+    AppStorage.setOrCreate('playbackIsFavorite', this.currentSong?.isFav === 1)
+  }
+
+  private toggleCurrentSongFavoriteState(): void {
+    const nextFavorite = this.currentSong?.isFav !== 1
+    this.applyCurrentSongFavoriteState(nextFavorite)
+  }
+
+  private applyCurrentSongFavoriteState(isFavorite: boolean): void {
+    if (!this.currentSong) {
+      return
+    }
+    const nextFavorite = isFavorite ? 1 : 0
+    this.currentSong.isFav = nextFavorite
+    if (this.currentIndex >= 0 && this.currentIndex < this.queue.length) {
+      this.queue[this.currentIndex].isFav = nextFavorite
+    }
+    this.persistCurrentQueue()
+    this.syncHostStorage(this.isPlaying ? PlayStatus.PLAY : PlayStatus.PAUSE)
+    this.syncPlaybackSessionState(this.isPlaying ? PlayStatus.PLAY : PlayStatus.PAUSE)
+    this.persistPlaybackSnapshot(this.isPlaying)
+    Logger.info(TAG, `[MusicCast] favorite updated song=${this.currentSong.name}, isFavorite=${isFavorite}`)
+  }
 }

+ 137 - 0
entry/src/main/ets/playback/BackgroundAudioPlaybackHostHelper.ets

@@ -0,0 +1,137 @@
+import { MusicCardActionConstants } from '../common/player/MusicCardActionConstants'
+import { MusicPlaybackController } from '../controller/MusicPlaybackController'
+import { VideoItem } from '../viewmodel/VideoItem'
+
+export enum BackgroundAudioControlKind {
+  NOOP = 'noop',
+  PAUSE = 'pause',
+  STOP = 'stop',
+  PLAY_INDEX = 'play_index'
+}
+
+export class BackgroundAudioRecoveredQueue {
+  queue: VideoItem[] = []
+  currentIndex: number = -1
+  playType: number = 0
+  currentSong: VideoItem | undefined = undefined
+}
+
+export class BackgroundAudioPersistedState {
+  queue: VideoItem[] = []
+  currentSong: VideoItem | undefined = undefined
+  playType: number = 0
+  errorMessage: string | undefined = undefined
+}
+
+export class BackgroundAudioControlDecision {
+  kind: BackgroundAudioControlKind = BackgroundAudioControlKind.NOOP
+  targetIndex: number = -1
+}
+
+export class BackgroundAudioPlaybackHostHelper {
+  public static readPersistedState(
+    readQueue: () => VideoItem[] | undefined,
+    readCurrentSong: () => VideoItem | undefined,
+    readPlayType: () => number
+  ): BackgroundAudioPersistedState {
+    const state = new BackgroundAudioPersistedState()
+    try {
+      state.queue = readQueue() ?? []
+      state.currentSong = readCurrentSong()
+      state.playType = readPlayType()
+    } catch (error) {
+      state.queue = []
+      state.currentSong = undefined
+      state.playType = 0
+      state.errorMessage = (error as Error).message
+    }
+    return state
+  }
+
+  public static restorePersistedQueue(
+    persistedQueue: VideoItem[] | undefined,
+    persistedCurrentSong: VideoItem | undefined,
+    persistedPlayType: number
+  ): BackgroundAudioRecoveredQueue {
+    const restored = new BackgroundAudioRecoveredQueue()
+    const queue = persistedQueue ? persistedQueue.slice() : []
+    restored.playType = persistedPlayType
+
+    if (persistedCurrentSong) {
+      const existingIndex = queue.findIndex((item: VideoItem): boolean => {
+        return BackgroundAudioPlaybackHostHelper.isSameSong(item, persistedCurrentSong)
+      })
+      if (existingIndex >= 0) {
+        restored.queue = queue
+        restored.currentIndex = existingIndex
+        restored.currentSong = queue[existingIndex]
+        return restored
+      }
+      queue.push(persistedCurrentSong)
+      restored.queue = queue
+      restored.currentIndex = queue.length - 1
+      restored.currentSong = persistedCurrentSong
+      return restored
+    }
+
+    restored.queue = queue
+    if (queue.length > 0) {
+      restored.currentIndex = 0
+      restored.currentSong = queue[0]
+    }
+    return restored
+  }
+
+  public static resolveControlDecision(
+    action: string,
+    currentIndex: number,
+    queueLength: number,
+    isPlaying: boolean,
+    playType: number
+  ): BackgroundAudioControlDecision {
+    const decision = new BackgroundAudioControlDecision()
+    if (queueLength <= 0) {
+      return decision
+    }
+
+    const safeCurrentIndex = currentIndex >= 0 && currentIndex < queueLength ? currentIndex : 0
+    if (action === MusicCardActionConstants.ACTION_PLAY_PAUSE) {
+      if (isPlaying) {
+        decision.kind = BackgroundAudioControlKind.PAUSE
+        return decision
+      }
+      decision.kind = BackgroundAudioControlKind.PLAY_INDEX
+      decision.targetIndex = safeCurrentIndex
+      return decision
+    }
+
+    if (action === MusicCardActionConstants.ACTION_NEXT) {
+      const nextAction = MusicPlaybackController.resolveNextPlaybackAction(playType, safeCurrentIndex, queueLength)
+      if (nextAction === 'stop_at_end') {
+        decision.kind = BackgroundAudioControlKind.STOP
+        return decision
+      }
+      decision.kind = BackgroundAudioControlKind.PLAY_INDEX
+      decision.targetIndex = MusicPlaybackController.resolveNextQueueIndex(safeCurrentIndex, queueLength).nextIndex
+      return decision
+    }
+
+    if (action === MusicCardActionConstants.ACTION_PREVIOUS) {
+      decision.kind = BackgroundAudioControlKind.PLAY_INDEX
+      decision.targetIndex = MusicPlaybackController.resolvePreviousQueueIndex(safeCurrentIndex, queueLength).nextIndex
+      return decision
+    }
+
+    return decision
+  }
+
+  public static isSameSong(left: VideoItem | undefined, right: VideoItem | undefined): boolean {
+    if (!left || !right) {
+      return false
+    }
+    if (left.filePath !== '' && right.filePath !== '' && left.filePath === right.filePath) {
+      return true
+    }
+    return left.id !== '' && right.id !== '' && left.id === right.id
+  }
+}

+ 15 - 0
entry/src/main/ets/playback/PlaybackHostState.ets

@@ -16,9 +16,24 @@ export function ensurePlaybackHostStorageDefaults(): void {
   if (AppStorage.get('CONTROL_PlayStatus') === undefined) {
     AppStorage.setOrCreate('CONTROL_PlayStatus', PlayStatus.INIT)
   }
+  if (AppStorage.get('isPlaying') === undefined) {
+    AppStorage.setOrCreate('isPlaying', false)
+  }
+  if (AppStorage.get('animationState') === undefined) {
+    AppStorage.setOrCreate('animationState', AnimationStatus.Paused)
+  }
   if (AppStorage.get('progressValue') === undefined) {
     AppStorage.setOrCreate('progressValue', 0)
   }
+  if (AppStorage.get('playbackPositionMs') === undefined) {
+    AppStorage.setOrCreate('playbackPositionMs', 0)
+  }
+  if (AppStorage.get('playbackDurationMs') === undefined) {
+    AppStorage.setOrCreate('playbackDurationMs', 0)
+  }
+  if (AppStorage.get('playbackIsFavorite') === undefined) {
+    AppStorage.setOrCreate('playbackIsFavorite', false)
+  }
   if (AppStorage.get('cover') === undefined) {
     AppStorage.setOrCreate('cover', '')
   }

+ 19 - 0
entry/src/main/ets/playback/PlaybackRestoreCoordinator.ets

@@ -1,3 +1,6 @@
+import { MusicCardActionConstants } from '../common/player/MusicCardActionConstants'
+import { PlaybackActivationActionType } from './model/PlaybackSnapshot'
+
 export type MusicCardOpenRoute = 'show_player_now' | 'request_host_open'
 
 export class PlaybackRestoreCoordinator {
@@ -7,4 +10,20 @@ export class PlaybackRestoreCoordinator {
     }
     return 'request_host_open'
   }
+
+  public static resolveMusicCardActivationAction(action: string): PlaybackActivationActionType | undefined {
+    if (action === MusicCardActionConstants.ACTION_PLAY_PAUSE) {
+      return 'resume_play'
+    }
+    if (action === MusicCardActionConstants.ACTION_PREVIOUS) {
+      return 'play_previous'
+    }
+    if (action === MusicCardActionConstants.ACTION_NEXT) {
+      return 'play_next'
+    }
+    if (action === MusicCardActionConstants.ACTION_OPEN_PLAYER) {
+      return 'open_player'
+    }
+    return undefined
+  }
 }

+ 14 - 0
entry/src/main/ets/playback/PlaybackSnapshotHydrationHelper.ets

@@ -0,0 +1,14 @@
+import { PendingPlaybackActivation } from './model/PlaybackSnapshot'
+
+export function shouldHydrateSnapshotAsPlaying(snapshotIsPlaying: boolean,
+  pendingActivation: PendingPlaybackActivation | undefined): boolean {
+  if (!snapshotIsPlaying) {
+    return false
+  }
+  if (!pendingActivation) {
+    return false
+  }
+  return pendingActivation.actionType === 'resume_play'
+    || pendingActivation.actionType === 'play_previous'
+    || pendingActivation.actionType === 'play_next'
+}

+ 16 - 4
entry/src/main/ets/playback/PlaybackSnapshotStore.ets

@@ -1,8 +1,10 @@
 import { PreferencesUtil } from '@pura/harmony-utils'
 import { VideoItem } from '../viewmodel/VideoItem'
+import Logger from '../common/util/Logger'
 import { PlaybackSnapshot, PlaybackSnapshotSong } from './model/PlaybackSnapshot'
 
 export const PLAYBACK_SNAPSHOT_PREFERENCES_KEY = 'playback.host.snapshot'
+const TAG = 'PlaybackSnapshotStore'
 
 export interface RecoveredPlaybackSnapshotState {
   queue: VideoItem[]
@@ -12,6 +14,10 @@ export interface RecoveredPlaybackSnapshotState {
 
 export class PlaybackSnapshotStore {
   public write(snapshot: PlaybackSnapshot): void {
+    Logger.info(TAG,
+      `[MiniState] snapshot write song=${snapshot.currentSongName}, index=${snapshot.currentIndex}, ` +
+      `isPlaying=${snapshot.isPlaying}, shouldResume=${snapshot.shouldResumeWhenActivated}, ` +
+      `position=${snapshot.positionMs}, duration=${snapshot.durationMs}, updatedAt=${snapshot.updatedAt}`)
     PreferencesUtil.putSync(PLAYBACK_SNAPSHOT_PREFERENCES_KEY, JSON.stringify(snapshot))
   }
 
@@ -26,7 +32,12 @@ export class PlaybackSnapshotStore {
         this.clear()
         return undefined
       }
-      return parsedSnapshot
+      const snapshot = parsedSnapshot as PlaybackSnapshot
+      Logger.info(TAG,
+        `[MiniState] snapshot read song=${snapshot.currentSongName}, index=${snapshot.currentIndex}, ` +
+        `isPlaying=${snapshot.isPlaying}, shouldResume=${snapshot.shouldResumeWhenActivated}, ` +
+        `position=${snapshot.positionMs}, duration=${snapshot.durationMs}, updatedAt=${snapshot.updatedAt}`)
+      return snapshot
     } catch (_error) {
       this.clear()
       return undefined
@@ -53,7 +64,7 @@ export class PlaybackSnapshotStore {
       if (!this.isValidSnapshotSong(snapshotSong)) {
         continue
       }
-      queue.push(this.buildVideoItem(snapshotSong))
+      queue.push(this.buildVideoItem(snapshotSong as PlaybackSnapshotSong))
     }
     const currentIndex = this.resolveCurrentIndex(snapshot, queue)
     const currentSong = currentIndex >= 0 ? queue[currentIndex] : undefined
@@ -78,7 +89,7 @@ export class PlaybackSnapshotStore {
     return Math.max(0, Math.min(requestedIndex, queue.length - 1))
   }
 
-  private isValidSnapshot(snapshot: Object | null): snapshot is PlaybackSnapshot {
+  private isValidSnapshot(snapshot: Object | null): boolean {
     if (!snapshot || Array.isArray(snapshot)) {
       return false
     }
@@ -96,7 +107,7 @@ export class PlaybackSnapshotStore {
       typeof candidate.shouldResumeWhenActivated === 'boolean'
   }
 
-  private isValidSnapshotSong(snapshotSong: Object | null): snapshotSong is PlaybackSnapshotSong {
+  private isValidSnapshotSong(snapshotSong: Object | null): boolean {
     if (!snapshotSong || Array.isArray(snapshotSong)) {
       return false
     }
@@ -108,6 +119,7 @@ export class PlaybackSnapshotStore {
 
   private buildVideoItem(song: PlaybackSnapshotSong): VideoItem {
     const item = new VideoItem(song.name, song.id ?? '', song.filePath, song.type, 0, '')
+    item.isFav = song.isFav ?? 0
     item.artist = song.artist
     item.album = song.album
     item.duration = song.duration

+ 1 - 0
entry/src/main/ets/playback/model/PlaybackSnapshot.ets

@@ -3,6 +3,7 @@ export interface PlaybackSnapshotSong {
   id?: string
   type: number
   name: string
+  isFav?: number
   artist?: string
   album?: string
   duration?: string

+ 7 - 3
entry/src/main/ets/view/FindView.ets

@@ -1170,9 +1170,13 @@ export struct FindView {
       // 发现页队列统一走内存仓库,避免事件负载里重复塞大数组。
       setFindPlaylist(playlistId, playlistName, songs, safeIndex, meta)
     }
-    void this.playbackController.playQueue(songs.slice(), safeIndex, 'find-view', playType).catch((error: Error) => {
-      Logger.error(TAG, `发现页播放失败: ${error.message}`)
-      ToastUtil.showToast('播放失败')
+    this.playbackController.playInMemoryPlaylist({
+      playlistId,
+      playlistName,
+      songs: songs.slice(),
+      startIndex: safeIndex,
+      playType,
+      findPlaylistMeta: meta
     })
   }
 

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 376 - 170
entry/src/main/ets/view/LocalMusic.ets


+ 3 - 2
entry/src/main/ets/view/MiniPlayerBar.ets

@@ -5,6 +5,7 @@ import { hdsEffect } from '@kit.UIDesignKit'
 import { VideoItem } from '../viewmodel/VideoItem'
 import { PlayStatus } from '../common/PlayStatus'
 import { CommonConstants } from '../common/constants/CommonConstants'
+import { isPlaybackControlPlaying } from '../common/player/PlaybackControlStateHelper'
 import { PointLightContentButton } from './PointLight/PointLightContentButton'
 import { PlayingIndicator } from './PlayingIndicator'
 
@@ -101,7 +102,7 @@ export struct MiniPlayerBar {
         .height('100%')
         .style({ strokeWidth: 3 })
 
-      if (this.controlPlayStatus === PlayStatus.PLAY) {
+      if (isPlaybackControlPlaying(this.controlPlayStatus)) {
         PlayingIndicator({
           isActive: true,
           indicatorSize: Math.max(16, Math.floor(orbSize * 0.34)),
@@ -204,7 +205,7 @@ export struct MiniPlayerBar {
           .color(this.themeColor)
           .height(38)
           .aspectRatio(CommonConstants.ASPECT_RATIO)
-        Image(this.controlPlayStatus === PlayStatus.PLAY ? $r('app.media.hm_pause') : $r('app.media.hm_play2'))
+        Image(isPlaybackControlPlaying(this.controlPlayStatus) ? $r('app.media.hm_pause') : $r('app.media.hm_play2'))
           .height(36)
           .width(36)
           .fillColor(Color.White)

+ 2 - 1
entry/src/main/ets/view/PipLyricTextBuilder.ets

@@ -1,5 +1,6 @@
 import { NodeController, BuilderNode, FrameNode, UIContext } from "@kit.ArkUI";
-import { LyricController, LyricView2 } from "@seagazer/cclyric";
+import { LyricController } from "../lyric/LyricController";
+import { LyricView2 } from "../lyric/view/LyricView2";
 
 class Params {
   pipBg: string = '';

+ 2 - 1
entry/src/main/ets/view/player/PlayerControls.ets

@@ -1,4 +1,5 @@
 import { PlayStatus } from '../../common/PlayStatus'
+import { isPlaybackControlPlaying } from '../../common/player/PlaybackControlStateHelper'
 import { PointLightButton } from '../PointLight/PointLightButton'
 import { PointLightDefaultButton } from '../PointLight/PointLightDeFaultButton'
 
@@ -93,7 +94,7 @@ export struct PlayerControls {
   private PlayOrPauseButtonContent() {
     Stack() {
       // 播放暂停按钮单独保留加载圈,兼容远程歌曲缓冲场景。
-      Image(this.controlPlayStatus === PlayStatus.PLAY
+      Image(isPlaybackControlPlaying(this.controlPlayStatus)
         ? (this.isCircleBtn ? $r('app.media.hm_pause') : $r('app.media.ic_public_play'))
         : (this.isCircleBtn ? $r('app.media.hm_play2') : $r('app.media.ic_public_pause')))
         .width(this.isLandscape ? 38 : 40)

+ 13 - 1
entry/src/main/ets/view/spectrum/SpectrumView.ets

@@ -1,6 +1,7 @@
 import { IjkMediaPlayer } from '@ohos/ijkplayer'
 import { PreferencesUtil } from '@pura/harmony-utils'
 import { FFTAnalyzer } from '../../common/util/FFTAnalyzer'
+import { BackgroundAudioPlaybackHost } from '../../playback/BackgroundAudioPlaybackHost'
 import { SpectrumRenderer } from './SpectrumRenderer'
 import {
   SpectrumEffectDefinition,
@@ -17,6 +18,15 @@ const TAG: string = 'heanup-SpectrumView'
 const BAND_COUNT: number = 53 // 频段数量
 const PREFERENCE_SELECTED_EFFECT: string = 'selectedSpectrumEffectId'
 
+export function resolvePreferredSpectrumData(hostData?: number[],
+  legacyData?: number[]): number[] {
+  const resolvedHostData = hostData ?? []
+  if (resolvedHostData.length > 0) {
+    return resolvedHostData
+  }
+  return legacyData ?? []
+}
+
 @Component
 export struct SpectrumView {
   @Prop @Watch('onPlayingChange') isPlaying: boolean = false
@@ -164,7 +174,9 @@ export struct SpectrumView {
     }
 
     const player: IjkMediaPlayer = IjkMediaPlayer.getInstance()
-    const pcmData: number[] = player.getSpectrumData()
+    const hostSpectrumData = BackgroundAudioPlaybackHost.getInstance().getSpectrumData()
+    const legacySpectrumData = player.getSpectrumData()
+    const pcmData: number[] = resolvePreferredSpectrumData(hostSpectrumData, legacySpectrumData)
     if (pcmData.length === 0) {
       return
     }

+ 25 - 0
entry/src/main/ets/widget/common/MusicPlayerWidgetHelper.ets

@@ -1,5 +1,13 @@
+import { MusicCardActionConstants } from '../../common/player/MusicCardActionConstants'
 import { resolveMusicCardLyricMetaText } from '../../common/player/MusicCardSnapshot'
 
+export class MusicCardWidgetActionParams {
+  ttmusic_music_card_form_id: string = ''
+  ttmusic_music_card_action: string = ''
+  ttmusic_music_card_source: string = MusicCardActionConstants.ACTION_SOURCE_WIDGET
+  method?: string = undefined
+}
+
 export function resolveMusicCardWidgetCoverSource(
   coverImageName: string,
   coverPath: string,
@@ -60,3 +68,20 @@ export function formatMusicCardWidgetTime(timeMs: number): string {
   }
   return `${pad(minutes)}:${pad(seconds)}`
 }
+
+export function shouldDispatchMusicCardActionByCall(action: string): boolean {
+  return action === MusicCardActionConstants.ACTION_PLAY_PAUSE
+    || action === MusicCardActionConstants.ACTION_PREVIOUS
+    || action === MusicCardActionConstants.ACTION_NEXT
+    || action === MusicCardActionConstants.ACTION_SEEK_TO
+}
+
+export function buildMusicCardWidgetActionParams(formId: string, action: string): MusicCardWidgetActionParams {
+  const params = new MusicCardWidgetActionParams()
+  params.ttmusic_music_card_form_id = formId
+  params.ttmusic_music_card_action = action
+  if (shouldDispatchMusicCardActionByCall(action)) {
+    params.method = MusicCardActionConstants.CALL_METHOD_HANDLE_ACTION
+  }
+  return params
+}

+ 7 - 7
entry/src/main/ets/widget/pages/MusicPlayerWidgetCard.ets

@@ -1,8 +1,10 @@
 import { MusicCardActionConstants } from '../../common/player/MusicCardActionConstants'
 import Logger from '../../common/util/Logger'
 import {
+  buildMusicCardWidgetActionParams,
   clampMusicCardWidgetProgress,
-  resolveMusicCardWidgetCoverSource
+  resolveMusicCardWidgetCoverSource,
+  shouldDispatchMusicCardActionByCall
 } from '../common/MusicPlayerWidgetHelper'
 
 const cardStorage: LocalStorage = new LocalStorage()
@@ -42,14 +44,12 @@ struct MusicPlayerWidgetCard {
 
   private postControlAction(action: string): void {
     Logger.info(TAG, `musicCard small postControlAction formId=${this.formId}, action=${action}`)
+    const useCallDispatch = shouldDispatchMusicCardActionByCall(action)
+    const params = buildMusicCardWidgetActionParams(this.formId, action)
     postCardAction(this, {
-      action: 'router',
+      action: useCallDispatch ? 'call' : 'router',
       abilityName: ENTRY_ABILITY_NAME,
-      params: {
-        ttmusic_music_card_form_id: this.formId,
-        ttmusic_music_card_action: action,
-        ttmusic_music_card_source: MusicCardActionConstants.ACTION_SOURCE_WIDGET
-      }
+      params
     })
   }
 

+ 7 - 7
entry/src/main/ets/widget/pages/MusicPlayerWidgetDynamicLyricCard.ets

@@ -1,9 +1,11 @@
 import { MusicCardActionConstants } from '../../common/player/MusicCardActionConstants'
 import Logger from '../../common/util/Logger'
 import {
+  buildMusicCardWidgetActionParams,
   clampMusicCardWidgetProgress,
   formatMusicCardWidgetTime,
-  resolveMusicCardWidgetCoverSource
+  resolveMusicCardWidgetCoverSource,
+  shouldDispatchMusicCardActionByCall
 } from '../common/MusicPlayerWidgetHelper'
 
 const dynamicLyricCardStorage: LocalStorage = new LocalStorage()
@@ -61,14 +63,12 @@ struct MusicPlayerWidgetDynamicLyricCard {
 
   private postControlAction(action: string): void {
     Logger.info(TAG, `musicCard dynamic lyric postControlAction formId=${this.formId}, action=${action}`)
+    const useCallDispatch = shouldDispatchMusicCardActionByCall(action)
+    const params = buildMusicCardWidgetActionParams(this.formId, action)
     postCardAction(this, {
-      action: 'router',
+      action: useCallDispatch ? 'call' : 'router',
       abilityName: ENTRY_ABILITY_NAME,
-      params: {
-        ttmusic_music_card_form_id: this.formId,
-        ttmusic_music_card_action: action,
-        ttmusic_music_card_source: MusicCardActionConstants.ACTION_SOURCE_WIDGET
-      }
+      params
     })
   }
 

+ 7 - 7
entry/src/main/ets/widget/pages/MusicPlayerWidgetLyricCard.ets

@@ -1,11 +1,13 @@
 import { MusicCardActionConstants } from '../../common/player/MusicCardActionConstants'
 import Logger from '../../common/util/Logger'
 import {
+  buildMusicCardWidgetActionParams,
   clampMusicCardWidgetProgress,
   formatMusicCardWidgetTime,
   resolveMusicCardWidgetCoverSource,
   resolveMusicCardWidgetLyricMeta,
-  resolveMusicCardWidgetNextLyric
+  resolveMusicCardWidgetNextLyric,
+  shouldDispatchMusicCardActionByCall
 } from '../common/MusicPlayerWidgetHelper'
 
 const lyricCardStorage: LocalStorage = new LocalStorage()
@@ -55,14 +57,12 @@ struct MusicPlayerWidgetLyricCard {
 
   private postControlAction(action: string): void {
     Logger.info(TAG, `musicCard lyric postControlAction formId=${this.formId}, action=${action}`)
+    const useCallDispatch = shouldDispatchMusicCardActionByCall(action)
+    const params = buildMusicCardWidgetActionParams(this.formId, action)
     postCardAction(this, {
-      action: 'router',
+      action: useCallDispatch ? 'call' : 'router',
       abilityName: ENTRY_ABILITY_NAME,
-      params: {
-        ttmusic_music_card_form_id: this.formId,
-        ttmusic_music_card_action: action,
-        ttmusic_music_card_source: MusicCardActionConstants.ACTION_SOURCE_WIDGET
-      }
+      params
     })
   }
 

+ 7 - 7
entry/src/main/ets/widget/pages/MusicPlayerWidgetMiniCard.ets

@@ -1,8 +1,10 @@
 import { MusicCardActionConstants } from '../../common/player/MusicCardActionConstants'
 import Logger from '../../common/util/Logger'
 import {
+  buildMusicCardWidgetActionParams,
   clampMusicCardWidgetProgress,
-  resolveMusicCardWidgetCoverSource
+  resolveMusicCardWidgetCoverSource,
+  shouldDispatchMusicCardActionByCall
 } from '../common/MusicPlayerWidgetHelper'
 
 const cardStorage: LocalStorage = new LocalStorage()
@@ -51,14 +53,12 @@ struct u {
 
   private postControlAction(action: string): void {
     Logger.info(TAG, `musicCard small postControlAction formId=${this.formId}, action=${action}`)
+    const useCallDispatch = shouldDispatchMusicCardActionByCall(action)
+    const params = buildMusicCardWidgetActionParams(this.formId, action)
     postCardAction(this, {
-      action: 'router',
+      action: useCallDispatch ? 'call' : 'router',
       abilityName: ENTRY_ABILITY_NAME,
-      params: {
-        ttmusic_music_card_form_id: this.formId,
-        ttmusic_music_card_action: action,
-        ttmusic_music_card_source: MusicCardActionConstants.ACTION_SOURCE_WIDGET
-      }
+      params
     })
   }
 

+ 7 - 7
entry/src/main/ets/widget/pages/MusicPlayerWidgetWideCard.ets

@@ -1,9 +1,11 @@
 import { MusicCardActionConstants } from '../../common/player/MusicCardActionConstants'
 import Logger from '../../common/util/Logger'
 import {
+  buildMusicCardWidgetActionParams,
   clampMusicCardWidgetProgress,
   formatMusicCardWidgetTime,
-  resolveMusicCardWidgetCoverSource
+  resolveMusicCardWidgetCoverSource,
+  shouldDispatchMusicCardActionByCall
 } from '../common/MusicPlayerWidgetHelper'
 
 const wideCardStorage: LocalStorage = new LocalStorage()
@@ -49,14 +51,12 @@ struct MusicPlayerWidgetWideCard {
 
   private postControlAction(action: string): void {
     Logger.info(TAG, `musicCard wide postControlAction formId=${this.formId}, action=${action}`)
+    const useCallDispatch = shouldDispatchMusicCardActionByCall(action)
+    const params = buildMusicCardWidgetActionParams(this.formId, action)
     postCardAction(this, {
-      action: 'router',
+      action: useCallDispatch ? 'call' : 'router',
       abilityName: ENTRY_ABILITY_NAME,
-      params: {
-        ttmusic_music_card_form_id: this.formId,
-        ttmusic_music_card_action: action,
-        ttmusic_music_card_source: MusicCardActionConstants.ACTION_SOURCE_WIDGET
-      }
+      params
     })
   }
 

+ 0 - 17
lib/Index.ets

@@ -1,17 +0,0 @@
-export { IParser } from './src/main/ets/parse/IParser';
-
-export { Lyric } from './src/main/ets/bean/Lyric'
-
-export { LyricLine } from './src/main/ets/bean/LyricLine'
-
-export { LyricParser } from './src/main/ets/parse/LyricParser'
-
-export { FileParser } from './src/main/ets/parse/FileParser'
-
-
-export { LyricView2 } from './src/main/ets/view/LyricView2'
-
-export { LyricController } from './src/main/ets/LyricController'
-
-export { LyricHelper } from './src/main/ets/LyricHelper'
-

+ 0 - 6
lib/hvigorfile.ts

@@ -1,6 +0,0 @@
-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. */
-}

+ 0 - 30
lib/src/main/ets/extensions/Extension.ts

@@ -1,30 +0,0 @@
-import hilog from '@ohos.hilog';
-
-const TAG = "LyricView"
-
-export function printD(msg: string) {
-    hilog.debug(0, TAG, msg)
-}
-
-export function printW(msg: string) {
-    hilog.warn(0, TAG, msg)
-}
-
-export function duration2text(duration: number): string {
-    let seconds = Math.round(duration / 1000)
-    let minute = Math.floor(seconds / 60)
-    let second = seconds - minute * 60
-    let s1 = minute.toString()
-    let s2 = second >= 10 ? second.toString() : "0" + second
-    return s1 + ":" + s2
-}
-
-export function seconds2text(seconds: number) {
-    let minute = Math.floor(seconds / 60)
-    let second = seconds - minute * 60
-    if (second < 10) {
-        return minute + ":0" + second
-    } else {
-        return minute + ":" + second
-    }
-}

+ 0 - 7
lib/src/main/ets/parse/IParser.ts

@@ -1,7 +0,0 @@
-import { Lyric } from '../bean/Lyric';
-
-export interface IParser {
-
-    parse(source: any): Lyric
-
-}

+ 0 - 13
lib/src/main/resources/base/media/cclyric_play.svg

@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <title>Public/ic_public_play</title>
-    <defs>
-        <path d="M15.9178985,6.64969378 L22.2241113,17.6299232 C22.9117482,18.8272204 22.4985869,20.3552615 21.3012896,21.0428985 C20.9224145,21.2604957 20.4931281,21.375 20.0562128,21.375 L7.44378717,21.375 C6.06307529,21.375 4.94378717,20.2557119 4.94378717,18.875 C4.94378717,18.4380847 5.05829147,18.0087984 5.27588871,17.6299232 L11.5821015,6.64969378 C12.2697385,5.45239653 13.7977796,5.03923521 14.9950768,5.72687214 C15.3789642,5.94734799 15.6974226,6.26580643 15.9178985,6.64969378 Z" id="path-1"></path>
-    </defs>
-    <g id="Public/ic_public_play" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <mask id="mask-2" fill="white">
-            <use xlink:href="#path-1"></use>
-        </mask>
-        <use id="多边形备份-5" fill="#000000" transform="translate(13.750000, 12.125000) rotate(-270.000000) translate(-13.750000, -12.125000) " xlink:href="#path-1"></use>
-    </g>
-</svg>

+ 140 - 105
oh_modules/.ohpm/lock.json5

@@ -15,30 +15,10 @@
           "specifier": "^1.2.4",
           "version": "1.2.4"
         },
-        "@pura/harmony-dialog": {
-          "specifier": "^1.0.6",
-          "version": "1.0.6"
-        },
-        "@ohos/lottie": {
-          "specifier": "^2.0.19",
-          "version": "2.0.19"
-        },
-        "@ohos/pulltorefresh": {
-          "specifier": "^2.1.1",
-          "version": "2.1.1"
-        },
         "@chinalike/popup": {
           "specifier": "^0.0.7",
           "version": "0.0.7"
         },
-        "@seagazer/cclyric": {
-          "specifier": "file:lib",
-          "version": "file:lib"
-        },
-        "@changwei/chardet": {
-          "specifier": "^1.0.0",
-          "version": "1.0.0"
-        },
         "@cashier_alipay/cashiersdk": {
           "specifier": "^15.8.32",
           "version": "15.8.32"
@@ -47,10 +27,6 @@
           "specifier": "^1.0.4",
           "version": "1.0.4"
         },
-        "@sgaolei/lrc_parser": {
-          "specifier": "^1.0.0",
-          "version": "1.0.0"
-        },
         "@ohos/pinyin4js": {
           "specifier": "^2.0.2",
           "version": "2.0.2"
@@ -58,6 +34,42 @@
         "@simplepeng/spider-man": {
           "specifier": "^1.0.1",
           "version": "1.0.1"
+        },
+        "@ohos/juniversalchardet": {
+          "specifier": "^2.0.2",
+          "version": "2.0.2"
+        },
+        "@sj/ffmpeg": {
+          "specifier": "^1.2.5",
+          "version": "1.2.5"
+        },
+        "@ohos/lottie": {
+          "specifier": "^2.0.23",
+          "version": "2.0.23"
+        },
+        "@mcui/mccharts": {
+          "specifier": "^2.8.9",
+          "version": "2.8.9"
+        },
+        "@nutpi/chinese_transverter": {
+          "specifier": "^1.0.4",
+          "version": "1.0.4"
+        },
+        "@abner/track": {
+          "specifier": "^1.0.2",
+          "version": "1.0.2"
+        },
+        "@liuzhosoft/ftp4h": {
+          "specifier": "^2.0.0",
+          "version": "2.0.0"
+        },
+        "@webabcd/harmony-httpserver": {
+          "specifier": "^1.0.0",
+          "version": "1.0.4"
+        },
+        "@pura/harmony-dialog": {
+          "specifier": "^1.1.8",
+          "version": "1.1.8"
         }
       },
       "devDependencies": {},
@@ -71,10 +83,6 @@
           "specifier": "file:ijkplayer",
           "version": "file:ijkplayer"
         },
-        "libentry.so": {
-          "specifier": "file:entry/src/main/cpp/types/libentry",
-          "version": "file:entry/src/main/cpp/types/libentry"
-        },
         "@tencent/wechat_open_sdk": {
           "specifier": "1.0.0",
           "version": "1.0.0"
@@ -95,37 +103,25 @@
       },
       "dynamicDependencies": {},
       "maskedByOverrideDependencyMap": false
-    },
-    "lib": {
-      "name": "lib",
-      "dependencies": {},
-      "devDependencies": {},
-      "dynamicDependencies": {},
-      "maskedByOverrideDependencyMap": false
     }
   },
   "packages": {
-    "@types/libijkplayer_audio_napi.so@file:ijkplayer/src/main/cpp/types/ijkplayer_audio_napi": {
-      "storePath": "ijkplayer/src/main/cpp/types/ijkplayer_audio_napi",
-      "dependencies": {},
-      "dynamicDependencies": {},
-      "dev": true,
-      "dynamic": false,
-      "maskedByOverrideDependencyMap": false
-    },
     "@ohos/ijkplayer@file:ijkplayer": {
       "storePath": "ijkplayer",
       "dependencies": {},
+      "devDependencies": {
+        "@types/libijkplayer_audio_napi.so": "ijkplayer/src/main/cpp/types/ijkplayer_audio_napi"
+      },
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "libentry.so@file:entry/src/main/cpp/types/libentry": {
-      "storePath": "entry/src/main/cpp/types/libentry",
+    "@types/libijkplayer_audio_napi.so@file:ijkplayer/src/main/cpp/types/ijkplayer_audio_napi": {
+      "storePath": "ijkplayer/src/main/cpp/types/ijkplayer_audio_napi",
       "dependencies": {},
       "dynamicDependencies": {},
-      "dev": false,
+      "dev": true,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
@@ -149,167 +145,206 @@
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "class-transformer@0.5.1": {
-      "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
-      "storePath": "oh_modules/.ohpm/class-transformer@0.5.1",
+    "@chinalike/popup@0.0.7": {
+      "integrity": "sha512-LgehWpAQJMw02T+tV5RpzOxPbT7Fm5D5ejKVn7tU+tNRYr517kKAspdhtGRVG382C6BSAtcy8p/1eITGH+uzjQ==",
+      "storePath": "oh_modules/.ohpm/@chinalike+popup@0.0.7",
       "dependencies": {},
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "@pura/harmony-dialog@1.0.6": {
-      "integrity": "sha512-n6TS5LOLc4qaL84XHq711MXULyQhbwnS7fUvjtWYFiSzeobdZFHc0x6X9ZRKPg3+GjQFXe5VIuc2UtQrNWvsUg==",
-      "storePath": "oh_modules/.ohpm/@pura+harmony-dialog@1.0.6",
-      "dependencies": {
-        "@pura/spinkit": "1.0.4"
-      },
+    "@keke/color-picker@1.0.4": {
+      "integrity": "sha512-0cH0Ix3EaoqK2nMxcAZAAyzK2G2oPo6A2KzFpUAgmqI9+Ap04Qy2Z+ALdBgdkmvwhCgJxEKGH8txxudmBfppMw==",
+      "storePath": "oh_modules/.ohpm/@keke+color-picker@1.0.4",
+      "dependencies": {},
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "@pura/spinkit@1.0.4": {
-      "integrity": "sha512-yI6tlx7s9B9lEsIvdEz7mhW602X/BwdnHWo6TRIHzGfZm0WPdoTpW8FfqNlwNbpDMGnVDKQ2YI10HRYeJDy+Wg==",
-      "storePath": "oh_modules/.ohpm/@pura+spinkit@1.0.4",
+    "@ohos/pinyin4js@2.0.2": {
+      "integrity": "sha512-f4xq1XZ/eF6JLnl9yVgXGQzvkTKsLjI58T+NZbF+uBigSivppkxtrhideVyfmdYna7sTCPQnINOvjww3jWyJww==",
+      "storePath": "oh_modules/.ohpm/@ohos+pinyin4js@2.0.2",
       "dependencies": {},
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "@ohos/lottie@2.0.19": {
-      "integrity": "sha512-6YGMeuHCdJ4PrJGPj3ycJob61TYbEcgk4GugbcuTGS6eJHJpDUuTja9lPhDmD6RL/etm3i2j4IVd+QCpfI6TWA==",
-      "storePath": "oh_modules/.ohpm/@ohos+lottie@2.0.19",
+    "@simplepeng/spider-man@1.0.1": {
+      "integrity": "sha512-rulclyolBPbYUzEav8crOr5WDuQs+zVWpLtefE2ei0YmBIbVUrpJN8mDRPNI+5kHZdkbYufKWyM/IzbVB/KoKg==",
+      "storePath": "oh_modules/.ohpm/@simplepeng+spider-man@1.0.1",
       "dependencies": {},
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "@ohos/pulltorefresh@2.1.1": {
-      "integrity": "sha512-YcXPaR29CRHsoaz+nhO190HP7ZXC8tAfanP0yMe/oKsEsZwk2RSIvaRfAHTlMbnQ3QxMlb1QSDougVcIMRrmWQ==",
-      "storePath": "oh_modules/.ohpm/@ohos+pulltorefresh@2.1.1",
+    "@ohos/juniversalchardet@2.0.2": {
+      "integrity": "sha512-EQiclxDnum59j75rI/t5CIOw6nyzbUB0p1hdaRPOJ+FxH7w7nT8tbga08ZwIkuFLzlAO2TtpkgDGAvkAsF3lDw==",
+      "storePath": "oh_modules/.ohpm/@ohos+juniversalchardet@2.0.2",
       "dependencies": {},
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "@chinalike/popup@0.0.7": {
-      "integrity": "sha512-LgehWpAQJMw02T+tV5RpzOxPbT7Fm5D5ejKVn7tU+tNRYr517kKAspdhtGRVG382C6BSAtcy8p/1eITGH+uzjQ==",
-      "storePath": "oh_modules/.ohpm/@chinalike+popup@0.0.7",
+    "@ohos/lottie@2.0.23": {
+      "integrity": "sha512-SiVmsyllXLcvSrh98qsXtjSoVaxy1bm8sGZzmz94DZOjdQ2zSCnTzY627EevkCCFxAA5HIbqFPUWokhc8bp/3Q==",
+      "storePath": "oh_modules/.ohpm/@ohos+lottie@2.0.23",
       "dependencies": {},
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "@seagazer/cclyric@file:lib": {
-      "storePath": "lib",
+    "@mcui/mccharts@2.8.9": {
+      "integrity": "sha512-mdpzc6TlYlR/xV+OH3u0Jq5s1UeND8EKQuyXUdtjPMozffrbitPWJcp5N4Fji9v1xs8d/WP62OKnbp79iWMtHA==",
+      "storePath": "oh_modules/.ohpm/@mcui+mccharts@2.8.9",
       "dependencies": {},
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "@changwei/chardet@1.0.0": {
-      "integrity": "sha512-rLvLtFDQnYlgEaKL7PAKtaaYompnIQ1esK8c3wHpQYKKMdKqCIpwUwn9XmdACMQE7KoehJogpDzG/G4tBPykqA==",
-      "storePath": "oh_modules/.ohpm/@changwei+chardet@1.0.0",
+    "@nutpi/chinese_transverter@1.0.4": {
+      "integrity": "sha512-34IXm4ZPcUfTBQoI1BacdWOTSBw27AX/Ni22LW2ijM7hwBKEES7M+4ohUfkDuDNhZcZqOpwbkbn3OBWHdsZNKQ==",
+      "storePath": "oh_modules/.ohpm/@nutpi+chinese_transverter@1.0.4",
       "dependencies": {},
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "@cashier_alipay/cashiersdk@15.8.32": {
-      "integrity": "sha512-04tCRKK6XW4pcilD2z+hcHRsBArvY/71SPEnwmxpTcmiHKkW9ggPTbHFLKsKALlWXG8u8tOXJic2D2qzdKm74g==",
-      "storePath": "oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.32",
-      "dependencies": {
-        "pako": "2.1.0",
-        "@alipay/blueshieldsdk": "oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.32/oh_modules/@cashier_alipay/cashiersdk/lib/blueshieldsdk-1.0.29.har",
-        "@taobao-ohos/utdid_sdk": "oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.32/oh_modules/@cashier_alipay/cashiersdk/lib/utdid_sdk-1.0.9.har"
-      },
+    "@abner/track@1.0.2": {
+      "integrity": "sha512-BXZ2rsr2a+TZrychsD0lVw34oPixnSMgy/KnCEFTM5aJJuL/bAlczsG/7AskNRbNOH+ckOL1Z6PIA+w65ji1Iw==",
+      "storePath": "oh_modules/.ohpm/@abner+track@1.0.2",
+      "dependencies": {},
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "pako@2.1.0": {
-      "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
-      "storePath": "oh_modules/.ohpm/pako@2.1.0",
+    "@liuzhosoft/ftp4h@2.0.0": {
+      "integrity": "sha512-BzXNsSeYZ2WOSxKy6pognc34vLJtXhXMLDxg8IritGZwOX7S6RP1KF2YXJlVaGkumi1+Z1nSWiFjzSjEoD3v1g==",
+      "storePath": "oh_modules/.ohpm/@liuzhosoft+ftp4h@2.0.0",
       "dependencies": {},
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "@alipay/blueshieldsdk@file:oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.32/oh_modules/@cashier_alipay/cashiersdk/lib/blueshieldsdk-1.0.29.har": {
-      "storePath": "oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=",
+    "@webabcd/harmony-httpserver@1.0.4": {
+      "integrity": "sha512-ZJHe+JAG6eMdQYYomVoYZzuG7HsWA4YxBfpgAQfIkH2Hraj5TsTLOWDfTmPmbjn0jhLgrTk7qYqnDdly0Svgpg==",
+      "storePath": "oh_modules/.ohpm/@webabcd+harmony-httpserver@1.0.4",
       "dependencies": {
-        "libblueshield.so": "oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=/oh_modules/@alipay/blueshieldsdk/src/main/cpp/types/libblueshield"
+        "@webabcd/harmony-utils": "1.0.1"
       },
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "libblueshield.so@file:oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=/oh_modules/@alipay/blueshieldsdk/src/main/cpp/types/libblueshield": {
-      "storePath": "oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=/oh_modules/@alipay/blueshieldsdk/src/main/cpp/types/libblueshield",
+    "@pura/harmony-dialog@1.1.8": {
+      "integrity": "sha512-IG2BJti8LBvJvkAD7yLwk6F2D66BGYPt6mae2yqAB9hMU5HnOTslWJbrReN9qPQl+ssIjSahMC7aJexEFYm5Dw==",
+      "storePath": "oh_modules/.ohpm/@pura+harmony-dialog@1.1.8",
+      "dependencies": {
+        "@pura/spinkit": "1.0.6"
+      },
+      "dynamicDependencies": {},
+      "dev": false,
+      "dynamic": false,
+      "maskedByOverrideDependencyMap": false
+    },
+    "class-transformer@0.5.1": {
+      "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
+      "storePath": "oh_modules/.ohpm/class-transformer@0.5.1",
       "dependencies": {},
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "@taobao-ohos/utdid_sdk@file:oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.32/oh_modules/@cashier_alipay/cashiersdk/lib/utdid_sdk-1.0.9.har": {
-      "storePath": "oh_modules/.ohpm/@taobao-ohos+utdid_sdk@sxn5blz8wnmyyhhjqdjifot4kh4asf+8aeifzj4aaey=",
+    "@webabcd/harmony-utils@1.0.1": {
+      "integrity": "sha512-q3UlXjtIPkiqG2gMCbF4Pajh4qaTIoT0Tv7x9dO1swz4BoDZj/j0JirNktLuSXxeNOezOtpljodWTfrBc+XeYQ==",
+      "storePath": "oh_modules/.ohpm/@webabcd+harmony-utils@1.0.1",
       "dependencies": {},
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "@keke/color-picker@1.0.4": {
-      "integrity": "sha512-0cH0Ix3EaoqK2nMxcAZAAyzK2G2oPo6A2KzFpUAgmqI9+Ap04Qy2Z+ALdBgdkmvwhCgJxEKGH8txxudmBfppMw==",
-      "storePath": "oh_modules/.ohpm/@keke+color-picker@1.0.4",
+    "@pura/spinkit@1.0.6": {
+      "integrity": "sha512-CwrAo/YiNAVwIT7q4exsFF8xdoXGTFsj/FfSaFC/m6LbdNbmPrYYYUk9VaZPNxRxjKBHOt6Gf9PkfSzHPdUdow==",
+      "storePath": "oh_modules/.ohpm/@pura+spinkit@1.0.6",
       "dependencies": {},
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "@sgaolei/lrc_parser@1.0.0": {
-      "integrity": "sha512-JnkArqOidM0qN+Daoxka+PvMq4Eqyv39/oZ/4vHTYlU5ssCTUqJ0GWF7ti9ZQTrDbg89Shx6hjODVXhs3IjgAg==",
-      "storePath": "oh_modules/.ohpm/@sgaolei+lrc_parser@1.0.0",
+    "@sj/ffmpeg@1.2.5": {
+      "integrity": "sha512-IshbyU5FaIMYkEpXiyBtCQfBWbaBUHz1nt04XiVjbxiC+AjGJ1bHUrFMQINe8YIPGRPwcKrafXPxSnkidRXMRQ==",
+      "storePath": "oh_modules/.ohpm/@sj+ffmpeg@1.2.5",
+      "dependencies": {
+        "libffmpeg.so": "oh_modules/.ohpm/@sj+ffmpeg@1.2.5/oh_modules/@sj/ffmpeg/src/main/cpp/types/libffmpeg"
+      },
+      "dynamicDependencies": {},
+      "dev": false,
+      "dynamic": false,
+      "maskedByOverrideDependencyMap": false
+    },
+    "@cashier_alipay/cashiersdk@15.8.32": {
+      "integrity": "sha512-04tCRKK6XW4pcilD2z+hcHRsBArvY/71SPEnwmxpTcmiHKkW9ggPTbHFLKsKALlWXG8u8tOXJic2D2qzdKm74g==",
+      "storePath": "oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.32",
       "dependencies": {
-        "liblrcparser.so": "oh_modules/.ohpm/@sgaolei+lrc_parser@1.0.0/oh_modules/@sgaolei/lrc_parser/src/main/cpp/types/liblrcparser"
+        "pako": "2.1.0",
+        "@alipay/blueshieldsdk": "oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.32/oh_modules/@cashier_alipay/cashiersdk/lib/blueshieldsdk-1.0.29.har",
+        "@taobao-ohos/utdid_sdk": "oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.32/oh_modules/@cashier_alipay/cashiersdk/lib/utdid_sdk-1.0.9.har"
       },
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "liblrcparser.so@file:oh_modules/.ohpm/@sgaolei+lrc_parser@1.0.0/oh_modules/@sgaolei/lrc_parser/src/main/cpp/types/liblrcparser": {
-      "storePath": "oh_modules/.ohpm/@sgaolei+lrc_parser@1.0.0/oh_modules/@sgaolei/lrc_parser/src/main/cpp/types/liblrcparser",
+    "pako@2.1.0": {
+      "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
+      "storePath": "oh_modules/.ohpm/pako@2.1.0",
       "dependencies": {},
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "@ohos/pinyin4js@2.0.2": {
-      "integrity": "sha512-f4xq1XZ/eF6JLnl9yVgXGQzvkTKsLjI58T+NZbF+uBigSivppkxtrhideVyfmdYna7sTCPQnINOvjww3jWyJww==",
-      "storePath": "oh_modules/.ohpm/@ohos+pinyin4js@2.0.2",
+    "@taobao-ohos/utdid_sdk@file:oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.32/oh_modules/@cashier_alipay/cashiersdk/lib/utdid_sdk-1.0.9.har": {
+      "storePath": "oh_modules/.ohpm/@taobao-ohos+utdid_sdk@sxn5blz8wnmyyhhjqdjifot4kh4asf+8aeifzj4aaey=",
       "dependencies": {},
       "dynamicDependencies": {},
       "dev": false,
       "dynamic": false,
       "maskedByOverrideDependencyMap": false
     },
-    "@simplepeng/spider-man@1.0.1": {
-      "integrity": "sha512-rulclyolBPbYUzEav8crOr5WDuQs+zVWpLtefE2ei0YmBIbVUrpJN8mDRPNI+5kHZdkbYufKWyM/IzbVB/KoKg==",
-      "storePath": "oh_modules/.ohpm/@simplepeng+spider-man@1.0.1",
+    "@alipay/blueshieldsdk@file:oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.32/oh_modules/@cashier_alipay/cashiersdk/lib/blueshieldsdk-1.0.29.har": {
+      "storePath": "oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=",
+      "dependencies": {
+        "libblueshield.so": "oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=/oh_modules/@alipay/blueshieldsdk/src/main/cpp/types/libblueshield"
+      },
+      "dynamicDependencies": {},
+      "dev": false,
+      "dynamic": false,
+      "maskedByOverrideDependencyMap": false
+    },
+    "libffmpeg.so@file:oh_modules/.ohpm/@sj+ffmpeg@1.2.5/oh_modules/@sj/ffmpeg/src/main/cpp/types/libffmpeg": {
+      "storePath": "oh_modules/.ohpm/@sj+ffmpeg@1.2.5/oh_modules/@sj/ffmpeg/src/main/cpp/types/libffmpeg",
+      "dependencies": {},
+      "dynamicDependencies": {},
+      "dev": false,
+      "dynamic": false,
+      "maskedByOverrideDependencyMap": false
+    },
+    "libblueshield.so@file:oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=/oh_modules/@alipay/blueshieldsdk/src/main/cpp/types/libblueshield": {
+      "storePath": "oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=/oh_modules/@alipay/blueshieldsdk/src/main/cpp/types/libblueshield",
       "dependencies": {},
       "dynamicDependencies": {},
       "dev": false,

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно