Kaynağa Gözat

feat(player): add playback coordinator

onecold 4 ay önce
ebeveyn
işleme
f94a637c2e

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

@@ -0,0 +1,61 @@
+import { PlaybackStateBridge } from './PlaybackStateBridge'
+import { VideoItem } from '../viewmodel/VideoItem'
+
+export interface PlaybackRuntime {
+  playQueue: (queue: VideoItem[], startIndex: number, source: string) => Promise<void>
+  playOrPause: () => Promise<void>
+  playNext: () => Promise<void>
+  playPrevious: () => Promise<void>
+  seekTo: (value: string, source?: string) => Promise<void>
+}
+
+export class PlaybackCoordinator {
+  private static instance?: PlaybackCoordinator
+  private runtime?: PlaybackRuntime
+  private stateBridge: PlaybackStateBridge = PlaybackStateBridge.getInstance()
+
+  public static getInstance(): PlaybackCoordinator {
+    if (!PlaybackCoordinator.instance) {
+      PlaybackCoordinator.instance = new PlaybackCoordinator()
+    }
+    return PlaybackCoordinator.instance
+  }
+
+  public setRuntime(runtime: PlaybackRuntime): void {
+    this.runtime = runtime
+  }
+
+  public clearRuntime(runtime?: PlaybackRuntime): void {
+    if (!runtime || this.runtime === runtime) {
+      this.runtime = undefined
+    }
+  }
+
+  public async playQueue(queue: VideoItem[], startIndex: number, source: string): Promise<void> {
+    if (!this.runtime || queue.length <= 0) {
+      return
+    }
+    this.stateBridge.replaceQueue(queue, startIndex)
+    await this.runtime.playQueue(queue, this.stateBridge.getSnapshot().currentIndex, source)
+  }
+
+  public async playSong(song: VideoItem, source: string): Promise<void> {
+    await this.playQueue([song], 0, source)
+  }
+
+  public async playOrPause(): Promise<void> {
+    await this.runtime?.playOrPause()
+  }
+
+  public async playNext(): Promise<void> {
+    await this.runtime?.playNext()
+  }
+
+  public async playPrevious(): Promise<void> {
+    await this.runtime?.playPrevious()
+  }
+
+  public async seekTo(value: string, source?: string): Promise<void> {
+    await this.runtime?.seekTo(value, source)
+  }
+}

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

@@ -0,0 +1,68 @@
+import { describe, expect, it } from '@ohos/hypium'
+import { PlaybackCoordinator, PlaybackRuntime } from '../../../main/ets/controller/PlaybackCoordinator'
+import { PlaybackStateBridge } from '../../../main/ets/controller/PlaybackStateBridge'
+import { VideoItem } from '../../../main/ets/viewmodel/VideoItem'
+
+export default function playbackCoordinatorTest() {
+  describe('PlaybackCoordinatorTest', () => {
+    it('playQueueCallsRuntimeAndUpdatesState', 0, async () => {
+      const bridge = PlaybackStateBridge.getInstance()
+      const coordinator = PlaybackCoordinator.getInstance()
+      const calls: string[] = []
+      const songs: VideoItem[] = [
+        new VideoItem('Song A', '1', '/music/a.flac', 0, 0, '2026-04-02'),
+        new VideoItem('Song B', '2', '/music/b.flac', 0, 0, '2026-04-02')
+      ]
+
+      const runtime: PlaybackRuntime = {
+        playQueue: async (_queue, startIndex, source) => {
+          calls.push(`playQueue:${startIndex}:${source}`)
+        },
+        playOrPause: async () => {
+          calls.push('playOrPause')
+        },
+        playNext: async () => {
+          calls.push('playNext')
+        },
+        playPrevious: async () => {
+          calls.push('playPrevious')
+        },
+        seekTo: async (value, source) => {
+          calls.push(`seekTo:${value}:${source ?? ''}`)
+        }
+      }
+
+      bridge.reset()
+      coordinator.clearRuntime()
+      coordinator.setRuntime(runtime)
+
+      await coordinator.playQueue(songs, 1, 'charts-count')
+
+      const snapshot = bridge.getSnapshot()
+      expect(snapshot.currentIndex).assertEqual(1)
+      expect(snapshot.currentSong?.filePath ?? '').assertEqual('/music/b.flac')
+      expect(calls.join(',')).assertEqual('playQueue:1:charts-count')
+      coordinator.clearRuntime()
+    })
+
+    it('playSongWrapsSingleSongQueue', 0, async () => {
+      const coordinator = PlaybackCoordinator.getInstance()
+      let queueLength = 0
+
+      coordinator.clearRuntime()
+      coordinator.setRuntime({
+        playQueue: async (queue) => {
+          queueLength = queue.length
+        },
+        playOrPause: async () => {},
+        playNext: async () => {},
+        playPrevious: async () => {},
+        seekTo: async () => {}
+      })
+
+      await coordinator.playSong(new VideoItem('Song A', '1', '/music/a.flac', 0, 0, '2026-04-02'), 'playlist-detail')
+      expect(queueLength).assertEqual(1)
+      coordinator.clearRuntime()
+    })
+  })
+}