Преглед на файлове

refactor(player): make local music a playback runtime

onecold преди 4 месеца
родител
ревизия
2595c56a66
променени са 2 файла, в които са добавени 83 реда и са изтрити 54 реда
  1. 53 54
      entry/src/main/ets/view/LocalMusic.ets
  2. 30 0
      entry/src/ohosTest/ets/test/PlaybackCoordinator.test.ets

+ 53 - 54
entry/src/main/ets/view/LocalMusic.ets

@@ -119,7 +119,9 @@ import { deviceInfo } from '@kit.BasicServicesKit';
 import '../common/network/RemoteCacheRegistry';
 import { shouldWrapLocalMusicMenuInScroll } from '../common/util/LocalMusicMenuScrollHelper';
 import { ServerLogUtil } from '../common/util/ServerLogUtil';
-import { MusicPlaybackController, MusicPlaybackControllerActions } from '../controller/MusicPlaybackController';
+import { MusicPlaybackController } from '../controller/MusicPlaybackController';
+import { PlaybackCoordinator, PlaybackRuntime } from '../controller/PlaybackCoordinator'
+import { PlaybackStateBridge } from '../controller/PlaybackStateBridge'
 import { lyricService, SongData } from '../common/service/LyricService';
 import { shouldResolveRemoteLyricOnPlay, tryResolveRemotePlaybackLyric } from '../common/util/RemotePlaybackLyricUtil';
 import { resolvePlayerDismissMorphTarget } from '../common/util/PlayerDismissHelper';
@@ -146,7 +148,6 @@ import {
 } from '../common/util/RemotePlayerUtil';
 import {
   PlaylistPlayRequest,
-  consumePendingPlaylistPlay,
   clearPendingPlaylistPlay
 } from '../common/util/PlaylistPlayRequestStore';
 import { resolvePlaylistDisplayCount } from '../common/util/PlaylistPlayDispatchHelper'
@@ -485,21 +486,39 @@ export struct LocalMusic {
   private contentNode?: ComponentContent<Object> = undefined;
   private skipNextPlaylistPersist: boolean = false
   private playbackController: MusicPlaybackController = MusicPlaybackController.getInstance()
-  private playbackControllerActions: MusicPlaybackControllerActions = {
-    playOrPause: (): void => {
-      this.playOrPause()
+  private playbackCoordinator: PlaybackCoordinator = PlaybackCoordinator.getInstance()
+  private playbackStateBridge: PlaybackStateBridge = PlaybackStateBridge.getInstance()
+  private playbackRuntime: PlaybackRuntime = {
+    playQueue: async (queue: VideoItem[], startIndex: number, source: string) => {
+      const safeIndex = queue.length <= 0 ? -1 : Math.max(0, Math.min(startIndex, queue.length - 1))
+      if (safeIndex < 0) {
+        return
+      }
+      this.songList = [...queue]
+      this.currentSongList = [...queue]
+      this.sonDataSource.pushArrayData(this.songList)
+      this.curIndex = safeIndex
+      this.currentSong = queue[safeIndex]
+      AppStorage.setOrCreate('currentSong', this.currentSong)
+      await this.doPlay(queue[safeIndex], safeIndex, false, source === 'controller-open')
+      this.playbackStateBridge.replaceQueue(this.songList, this.curIndex)
+      this.playbackStateBridge.updatePlaybackState(
+        this.CONTROL_PlayStatus === PlayStatus.PLAY,
+        this.mIjkMediaPlayer.getCurrentPosition(),
+        this.getActiveDuration()
+      )
     },
-    playNext: (): Promise<void> => {
-      return this.playNext()
+    playOrPause: async () => {
+      await this.playOrPause()
     },
-    playPrevious: (): Promise<void> => {
-      return this.playPrevious()
+    playNext: async () => {
+      await this.playNext()
     },
-    setLoopMode: (): void => {
-      this.setLoopMode()
+    playPrevious: async () => {
+      await this.playPrevious()
     },
-    seekTo: (value: string, source?: string): Promise<void> => {
-      return this.seekTo(value, source)
+    seekTo: async (value: string, source?: string) => {
+      await this.seekTo(value, source ?? 'coordinator')
     }
   }
 
@@ -1046,7 +1065,7 @@ export struct LocalMusic {
   }
   // 组件生命周期
   aboutToAppear() {
-    this.playbackController.setActions(this.playbackControllerActions)
+    this.playbackCoordinator.setRuntime(this.playbackRuntime)
 
     console.info('onecold aboutToAppear sdkApiVersion = '+deviceInfo.sdkApiVersion)
     if(ArrayUtil.isNotEmpty(this.videoLocalList)&&this.modeType==0){
@@ -1110,44 +1129,6 @@ export struct LocalMusic {
       }
     });
 
-    // 监听歌单播放请求事件
-    let eventPlaylistPlay: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_PLAY }
-    emitter.on(eventPlaylistPlay, async (eventData: emitter.EventData) => {
-      const data = eventData.data as Record<string, Object>
-
-      if (!data) {
-        Logger.error('heanup eventPlaylistPlay: eventData.data is undefined or null')
-        return
-      }
-
-      // 检查歌单播放数据结构
-      if (data.playlistId && data.songFilePaths && data.startIndex !== undefined) {
-        const playlistData = new PlaylistPlayRequest(
-          data.playlistId as string,
-          data.playlistName as string,
-          data.songCount as number,
-          data.startIndex as number,
-          data.songFilePaths as string[],
-          data.isJump as boolean,
-          data.playType as number
-        )
-
-        await this.dispatchPlaylistPlayRequest(playlistData)
-        return
-      }
-
-      Logger.error('heanup eventPlaylistPlay: 接收到无效的数据结构')
-    });
-    Promise.resolve().then(async (): Promise<void> => {
-      const pendingPlaylistRequest = consumePendingPlaylistPlay();
-      if (!pendingPlaylistRequest) {
-        return;
-      }
-      Logger.info(TAG,
-        `检测到待处理播放请求,准备补偿执行: ${pendingPlaylistRequest.playlistName}, startIndex=${pendingPlaylistRequest.startIndex}`);
-      await this.dispatchPlaylistPlayRequest(pendingPlaylistRequest);
-    });
-
     let eventRefreshSort: emitter.InnerEvent = { eventId: EventConstants.EVENT_PLAYLIST_REFRESH_SORT }
     // 监听广播事件(通用设置配置更新)
     emitter.on(eventRefreshSort, (eventData: emitter.EventData) => {
@@ -2293,7 +2274,7 @@ export struct LocalMusic {
   // 组件消失生命周期
   aboutToDisappear() {
     console.info('LifeCycleComponent aboutToDisappear');
-    this.playbackController.clearActions(this.playbackControllerActions)
+    this.playbackCoordinator.clearRuntime(this.playbackRuntime)
     if (this.alphaBetHideTimer) {
       clearTimeout(this.alphaBetHideTimer);
       this.alphaBetHideTimer = 0;
@@ -2334,7 +2315,6 @@ export struct LocalMusic {
     emitter.off(EventConstants.EVENT_SETTING_UPDATE);
     emitter.off(EventConstants.EVENT_WEBDAV_METADATA_UPDATED);
     emitter.off(EventConstants.EVENT_EQUALIZER_CHANGED);
-    emitter.off(EventConstants.EVENT_PLAYLIST_PLAY);
     emitter.off(EventConstants.EVENT_PLAYLIST_REFRESH_SORT);
     emitter.off(EventConstants.EVENT_PLAY_QUEUE_REFRESH);
     emitter.off(EventConstants.EVENT_OPEN_LOCAL_SPECIAL_LIST);
@@ -17685,6 +17665,15 @@ export struct LocalMusic {
 
     }
 
+    try {
+      this.playbackStateBridge.updatePlaybackState(
+        this.CONTROL_PlayStatus === PlayStatus.PLAY,
+        position,
+        duration
+      )
+    } catch (error) {
+      // setProgress 频率较高,这里仅做兜底避免影响主流程
+    }
   }
 
   private startProgressTask() {
@@ -17779,6 +17768,7 @@ export struct LocalMusic {
     this.curIndex = index
     this.currentSong = this.songList[this.curIndex]
     this.applyCurrentSongMetadata(this.currentSong)
+    this.playbackStateBridge.replaceQueue(this.songList, this.curIndex)
   }
 
   private ensureSongQueued(song: VideoItem): number {
@@ -18955,6 +18945,15 @@ export struct LocalMusic {
     } catch (error) {
       Logger.error('heanup 发送播放状态变化事件失败: ' + error)
     }
+    try {
+      this.playbackStateBridge.updatePlaybackState(
+        this.CONTROL_PlayStatus === PlayStatus.PLAY,
+        this.getActivePlaybackPositionMs(),
+        this.getActiveDuration()
+      )
+    } catch (error) {
+      Logger.warn(TAG, `回写 PlaybackStateBridge 失败: ${error}`)
+    }
   }
 
   private setPlaybackStateChangeListener(): void {

+ 30 - 0
entry/src/ohosTest/ets/test/PlaybackCoordinator.test.ets

@@ -106,6 +106,36 @@ export default function playbackCoordinatorTest() {
       expect(playOrPauseCount).assertEqual(1)
     })
 
+    it('coordinatorTransportControlsUseRegisteredRuntimeOnly', 0, async () => {
+      const coordinator = PlaybackCoordinator.getInstance()
+      const calls: string[] = []
+
+      coordinator.clearRuntime()
+      coordinator.setRuntime({
+        playQueue: async () => {},
+        playOrPause: async () => {
+          calls.push('playOrPause')
+        },
+        playNext: async () => {
+          calls.push('playNext')
+        },
+        playPrevious: async () => {
+          calls.push('playPrevious')
+        },
+        seekTo: async (value, source) => {
+          calls.push(`seekTo:${value}:${source ?? ''}`)
+        }
+      })
+
+      await coordinator.playOrPause()
+      await coordinator.playNext()
+      await coordinator.playPrevious()
+      await coordinator.seekTo('1200', 'slider')
+
+      expect(calls.join(',')).assertEqual('playOrPause,playNext,playPrevious,seekTo:1200:slider')
+      coordinator.clearRuntime()
+    })
+
     it('playQueueWithoutRuntimeKeepsBridgeState', 0, async () => {
       const coordinator = PlaybackCoordinator.getInstance()
       const bridge = PlaybackStateBridge.getInstance()