Skip to content

feat(app): add the playback core with look-ahead handover - #187

Merged
1morr merged 3 commits into
mainfrom
feat/playback-core
Sep 30, 2026
Merged

1morr merged 3 commits into
mainfrom
feat/playback-core

Conversation

@1morr

@1morr 1morr commented Sep 30, 2026

Copy link
Copy Markdown
Owner

M1 PR 10:播放核心的最小集(ADR 0018 的 M1 部分)。

做了什麼

  • PlaybackController:UI 唯一的播放入口,也是唯一會寫播放狀態的地方。
    • 播放狀態是一份 sealed 型別:Idle、Loading、Playing、Paused、Buffering、Retrying、Failed(AppError)。位置、時長、緩衝走獨立的 stream。
    • 每開始一首(包括重試、換候選、接上前瞻)都換一代,晚到的解析結果和舊來源的事件都會被丟掉。
  • AudioBackend 介面與兩個實作:Android 用 JustAudioBackend,Windows 用 MediaKitBackend,由平台層宣告用哪一個和可播格式。
    • 後端只持有目前這首加一首前瞻。
    • 共用的規則放在 backend_rules.dart:判斷一首是播完還是提前結束、前瞻清單怎麼改。這部分照搬舊專案。
    • 參考 Namida:一個介面、ExoPlayer 與 mpv 兩個實作,前瞻只留一個。
  • headers:只能經 BackendSource 交給後端,而它建構時就先過 mediaRequestHeaders,沒有其他路徑能把 Cookie 交給播放引擎。
  • 其他元件:
    • QueueModel:只在記憶體、依序播放;
    • StreamResolver:換候選一次,前瞻交接前檢查網址有沒有過期;
    • RecoveryPolicy 的 M1 部分:跳過,以及 1、3、9 秒的重試。
  • log 遮蔽:引擎的 log 與錯誤要先經過遮蔽函式才進 log。mpv 的 log 會寫出完整的簽名網址,這一點是 YouTube.js 探針發現的。
  • dev 入口 --fmp-dev-playback:只在 dev 生效,用測試插件依序播三首,也能指定任意曲目鍵。PR 12 的播放列上線後刪掉。
  • fmp_lints 的 fmp_layer_imports:just_audio、media_kit 只准出現在後端目錄,並有雙向測試。

驗證

  • format、dart analyze --fatal-infos、flutter analyze 都零問題;flutter test 600 個通過;fmp_lints 兩種路徑模式都是 88/88;哨兵通過。
  • 真後端契約(integration_test/audio_backend_contract_test.dart,CI 不跑):
    • Windows:10/10;
    • Android 模擬器:連續 5 次都是 10/10。
  • 實機:
    • Windows:測試插件三首連播,兩次交接都由前瞻接上,估計間隔 49–50 ms。
    • Android 模擬器:估計間隔 -206 ms 與 30 ms。dumpsys audio 顯示整段只有一次 requestAudioFocus,沒有 abandonAudioFocus,換歌時不放音訊焦點。
    • Windows 用 B 站插件真實播放一首:3 個 API 請求;開流只帶 Referer 和 User-Agent,送出請求後 941 ms 有聲音;log 裡沒有 CDN 網址。
    • 模擬器是用 -no-audio 啟動的,所以 Android 端沒有實際聽到聲音。
  • opus 審查修了 5 個缺陷,每個都有修正前會紅的測試:
    • Android 載入中按暫停,會永遠卡在 Loading:just_audio 修改清單期間的 ready 事件被丟掉;
    • Windows 換來源時,舊檔案的事件被算到新來源上;
    • 解析中或等重試時 seek 會被忽略;
    • dispose 之後晚到的解析仍會開流;
    • 契約案例失敗時沒有釋放播放器,拖累後面的案例。

和計畫不同的地方

  • Linux、macOS、iOS 照 ADR 0009 §決定 4 宣告「沒有播放能力」。
  • ADR 0018 的兩條 import 限制延後到路由器與 PlaybackSession 出現時。
  • 被取代的解析只丟掉結果,不取消網路請求。
  • 後續待辦記在 M1 implement.md,包括 release 版缺 INTERNET 權限(PR 13)、Unsupported 的提示用語(PR 12)、前瞻開不起來的契約案例(M2)。

Review 指南

  • 先看 lib/playback/backends/audio_backend.dart 的 dartdoc:兩個引擎的差異都寫在那裡。
  • 再看 playback_controller.dart 的代與前瞻處理。
  • 最後看兩個後端的 _opening、_wantPlaying 和 _edit。

@1morr
1morr merged commit f557342 into main Sep 30, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant