feat(#23): now-playing アプリの PID を NowPlayingInfo まで伝搬#294
Conversation
スペクトラムアナライザ (#23) の CoreAudio process tap が音源プロセスを 特定できるよう、MediaRemote helper で MRMediaRemoteGetNowPlayingApplicationPID を任意解決し、 Entity → DataSource → Handler の 5 モジュールを通して pid を伝搬する。 - helper はシンボル欠落時に pid なしへ縮退(helper 全体は死なない) - PID 0(セッション所有アプリなし)は nil に正規化 - lyra track の JSON 出力に pid フィールドが追加される
📝 WalkthroughWalkthroughAdds an optional ChangesPID propagation
Estimated code review effort: 2 (Simple) | ~12 minutes Sequence Diagram(s)sequenceDiagram
participant Timer
participant fetchAndPrint
participant GetPIDFn
participant printInfo
participant stdout
Timer->>fetchAndPrint: event (.trackChange/.tick)
fetchAndPrint->>GetPIDFn: resolve pid via dlsym
GetPIDFn-->>fetchAndPrint: pid or nil (0 mapped to nil)
fetchAndPrint->>printInfo: event, pid
printInfo->>stdout: JSON payload with pid field
Related issues: None specified. Related PRs: None specified. Suggested labels: enhancement Suggested reviewers: GeneralD 🐰 A hop, a skip, a pid appended, 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR prepares the now-playing pipeline for the upcoming spectrum analyzer work (#23) by propagating the now-playing application PID from the MediaRemote helper output through the DataSource and Entity layers into NowPlayingInfo (surfaced by lyra track output).
Changes:
- Extend
media-remote-helper.swiftto (optionally) resolveMRMediaRemoteGetNowPlayingApplicationPID, normalize PID 0 tonil, and emit"pid"in the helper JSON payload. - Decode the
"pid"field inMediaRemoteDataSourceImpl, addpid: Int?toNowPlaying/NowPlayingInfo, and pass it throughTrackHandlerImplfor both non-lyrics and lyrics paths. - Add unit tests covering PID decoding and PID passthrough, and bump the app version.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
Sources/MediaRemoteDataSource/Resources/media-remote-helper.swift |
Optionally fetches now-playing app PID and includes it in helper JSON output (PID 0 → nil). |
Sources/MediaRemoteDataSource/MediaRemoteDataSourceImpl.swift |
Decodes "pid" from the helper payload into NowPlaying. |
Sources/Entity/NowPlaying.swift |
Adds pid: Int? to the internal now-playing entity. |
Sources/TrackHandler/TrackHandlerImpl.swift |
Passes pid through into NowPlayingInfo on both code paths. |
Sources/Entity/NowPlayingInfo.swift |
Adds pid: Int? to the public Codable output entity. |
Tests/MediaRemoteDataSourceTests/MediaRemoteDataSourceImplTests.swift |
Tests that PID is surfaced when present and nil when absent. |
Tests/TrackHandlerTests/TrackHandlerImplTests.swift |
Tests PID passthrough for default and lyrics queries. |
Sources/VersionHandler/Resources/version.txt |
Bumps version to 2.17.0. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
         ## 概要 now-playing アプリの音声を CoreAudio process tap で捕捉し、オーバーレイにリアルタイムのスペクトラムアナライザ(バーグラフ)を描画する。Closes #23 ## 変更内容 - **Entity**: `SpectrumConfig` / `SpectrumStyle` / `SpectrumPlacement` / `AudioSourceState` を追加。`[spectrum]` TOML セクション(bar_count / bar_color / placement / decay_rate / fft_size 等、全項目デフォルトあり・既定 disabled) - **NowPlayingRepository**: `stream()` を multicast 化(単一ポンプ + 全購読者ブロードキャスト + 直近値 replay)。MediaRemote ヘルパーのパイプは単一消費者だが、任意のレイヤーが `observeNowPlaying()` を自由に購読できる - **AudioTapDataSource**: `ProcessTapEngine`(macOS 14.4+ API、14.0 ターゲット向けに availability erasure)+ swift-atomics による SPSC ロックフリーリングバッファ。IOProc コールバックは RT-safe(アロケーション・ロック・Swift concurrency なし) - **タップ対象はプロセスサブツリー全体**: Chromium 系ブラウザは音声をヘルパープロセスが出すため、メイン pid のみのタップでは無音になる(Arc の Browser Helper で実測)。`kAudioHardwarePropertyProcessObjectList` を ppid ウォークでフィルタして全対象を `CATapDescription` に渡す - **FrequencyAnalyzer**: Hann 窓 → vDSP FFT → dB 正規化 → bar 変換の純粋モジュール(依存ゼロ) - **SpectrumUseCase / AudioCaptureRepository**: Interactor → UseCase → Repository → DataSource の層を完全に通す新モジュール。`SpectrumUseCase` がサンプル取得(Repository 経由)と FrequencyAnalyzer による周波数変換を組み合わせ、`AudioCaptureRepository` がタップ DataSource を抽象化する(層飛ばしゼロ) - **SpectrumInteractor**: `PlaybackUseCase.observeNowPlaying()` を単一の `for await` で直接消費(Interactor 間依存なし)。直列消費により pause/play 連打でもタップ生成/破棄が交錯せず、pid/再生状態の重複排除も自前で行う。排他は `OSAllocatedUnfairLock(state:)`。キャプチャ操作と magnitudes 取得は `SpectrumUseCase` へ委譲 - **SpectrumPresenter / SpectrumView**: DisplayLink tick で指数減衰マージ、`binHeights()` は読み取り専用(Canvas 描画中の @published 変更なし)。#252/#258 のゼロアイドルコストパターン(条件付き include + `TimelineView(paused:)`) - **Info.plist**: `NSAudioCaptureUsageDescription` を追加(システムオーディオ録音 TCC) - バージョン 2.17.0 → 2.18.0(minor) ## 背景・動機 issue #23 の本体実装。前提工事の PID 伝搬は #294 で完了済み。設計判断(14.4 フロア / ブラウザはそのままタップ / swift-atomics)はセッション内で確認済み。 ## テスト計画 - [x] `swift test` 全 1027 件パス(新規: FrequencyAnalyzer / SampleRingBuffer / AudioTapDataSourceImpl / SpectrumInteractorImpl / SpectrumUseCaseImpl / AudioCaptureRepositoryImpl / SpectrumPresenter / `[spectrum]` デコード / NowPlayingRepository multicast) - [x] `make lint` パス - [x] 実機検証: launchd 経由で debug ビルドを起動し、Arc 再生中にバー描画をスクリーンショットで確認(上図) - [x] CI-safe: タップ系テストは不明 pid で TCC 手前で失敗するため CI でプロンプトは出ない ## 備考 - **既知の制限(設計判断)**: タップはプロセスツリー単位。ブラウザ再生時は全タブの音声が混ざる(README に記載) - **開発時の TCC 注意**: ターミナル起動のデーモンは TCC 責任プロセスがターミナルになり、プロンプトが出ずに無音になる。launchd(LaunchAgent)経由で起動すること(CLAUDE.md に記載) - スペクトラム機能は macOS 14.4+ が必要(14.0〜14.3 は startTap が false を返し無効化) --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
概要
スペクトラムアナライザ (#23) の前提工事として、now-playing アプリの PID を MediaRemote helper から
NowPlayingInfoまで伝搬する。CoreAudio process tap(macOS 14.2+ のAudioHardwareCreateProcessTap)は対象プロセスの PID を要求するため、このパイプラインが tap 実装の入口になる。この PR は #23 を close しない — アナライザ本体(AudioTapDataSource / SpectrumInteractor / Presenter / View / config)は後続 PR で実装する。
変更内容
media-remote-helper.swiftMRMediaRemoteGetNowPlayingApplicationPIDを 任意解決で追加。シンボル欠落時は pid なしペイロードに縮退し helper 自体は死なない。PID 0(セッション所有アプリなし)はnilに正規化MediaRemoteDataSourceImplpidフィールドをデコードEntity/NowPlayingpid: Int?追加(内部エンティティ)Entity/NowPlayingInfopid: Int?追加 —lyra trackの JSON 出力にpidが現れるTrackHandlerImpl設計メモ
MRMediaRemoteGetNowPlayingApplicationPIDは private symbol。将来の macOS で消えても helper 全体を殺さず、pid だけ欠落する graceful degradation にしたnilに正規化/usr/bin/swift -interpret経由(Apple-signed host 必須、bug: v2.13.3 で歌詞・トラック情報が表示されない (helper 事前コンパイル化の署名リグレッション #256) #261 の制約は不変)テスト
MediaRemoteDataSourceImplTests: pid ありペイロード → surfaced、pid なし →nilTrackHandlerImplTests: デフォルトクエリ/lyrics パス両方で passthrough を検証実機 JSON デモを載せていない理由
now-playing セッション不在の環境では
lyra trackが出力せず待ち続けるため、実出力を採取できなかった。変更前の brew 2.16.0 でも同一症状(コントロールテスト済み)で、この diff とは無関係の既存挙動 → #295 に起票済み。helper 単体(/usr/bin/swift <bundle>/media-remote-helper.swift)は本変更後も{"has_info":false}を即時継続出力しており、helper 側の縮退は正常。TODO(後続)
Relates to #23