Recording:
- stopRecording() now only sets stopRequested=true, no longer cancels task
- Task exits polling loop naturally → merges TS segments → exports MP4
- System share sheet + Photos save shown on completion
- Added HEAD request to detect HLS Content-Type for non-.m3u8 URLs
- Faster exit when stopRequested && segments exist
Playback:
- Copy imported files to app Documents sandbox (security-scoped URLs expire after defer)
- Remove Referer header that blocks some streams; use iPhone UA only
- AVURLAsset options only for http/https schemes
Media Library UI (iOS):
- Replace NavigationSplitView with VStack + horizontal category chips
- Search bar at top, swipe actions for queue/delete
- scrollDismissesKeyboard(.interactively) on list
- Tap gesture sends resignFirstResponder to dismiss keyboard
- Compact rows with context menu, inline play button
- Close button in navigation bar
- presentationDetents(.medium) for tag/playlist sheets
- Poll m3u8 every 2s to download new segments (supports live HLS)
- For non-HLS URLs, download entire file
- Accumulate TS segments incrementally, merge on stop
- Remux TS→MP4 via AVAssetExportSession
- Show system share sheet + save to Photos on completion
- Proper cancellation support via Task.cancel()
- build-ios.sh: 设置正确 Team ID (LR8YT5M53U), 添加 GENERATE_INFOPLIST_FILE
- MiniPlayerApp.swift: Bundle.module → Bundle.main (非SPM), @main 条件编译
- 签名: Apple Development + Personal Team provisioning profile
- 产物: MiniPlayer.ipa (584K)
- Hide close/minimize/zoom buttons on startup
- Show all buttons + title on hover, hide on exit
- Use asyncAfter to ensure window is ready
- Find visible window instead of first window
- Enable window drag by background when title bar hidden
- Title bar hidden by default (titleVisibility + titlebarAppearsTransparent)
- Mouse enter top 28pt area: show title bar
- Mouse exit: hide title bar
- Decouple title visibility from toolbar state
- Add PlayerStatus enum with loading/buffering/playing/error states
- Track loading elapsed time with countdown timer
- Detect buffering via timeControlStatus
- Handle playback errors with error message + retry button
- Show timeout warning after 15s of loading
- Buffering indicator in top-right corner (non-blocking)
- Localization strings for all status messages
Setting audioMix with MTAudioProcessingTap on HLS playerItem before
stream is ready blocks the audio pipeline entirely, preventing m3u8
streams from playing at all.
Revert to lazy install in startRecording() only.
Two fixes for video freeze during recording:
1. Video capture loop (30fps) moved from main-thread Timer to a
dedicated DispatchSourceTimer on 'miniplayer.videoCapture' queue.
copyPixelBuffer() on the main thread was competing with AVPlayer's
rendering pipeline for CPU time, causing frame stalls.
2. AVPlayerItemDidPlayToEndTime observer now checks notification.object
against player.currentItem. Previously object:nil caught stale
notifications from replaced items, causing spurious playNext() calls
that interrupted playback (visible as duplicate playIndex in logs).
Setting playerItem.audioMix while AVPlayer is actively playing causes
the audio/video pipeline to reconfigure, stalling video playback.
Fix: install the MTAudioProcessingTap audioMix at item creation time
(in playIndex, before replaceCurrentItem), so the pipeline is already
configured when playback begins. startRecording now only flips the
isRecording flag and skips re-installation if audioMix exists.
Also removed the guard that blocked tap installation when tracks
weren't available yet (HLS streams) — the wildcard trackID
(kCMPersistentTrackID_Invalid) handles this case.
Root cause found: HLS variant switching changes audio trackID mid-recording
(e.g. 2→10). Binding to a specific trackID causes tap to never fire.
Using kCMPersistentTrackID_Invalid (wildcard) matches all audio tracks
regardless of variant switches.
- AudioTapContext tracks: peak amplitude, active/silent call counts,
skip/error counters, last active timestamp
- tapProcess computes peak per buffer (every 16th sample for perf)
- writeDiagnosticReport() generates .txt report after each recording:
* Verdict: OK / FAIL / WARN with specific diagnosis
* Full tap stats, format info, skip/error breakdown
* 'How to Read' section for quick interpretation
- Report saved alongside mp4 (same name, .txt extension)
- showSaveDialog moves both mp4 and report together
- Replace single dataBuffer with double-buffer (dataBuffers tuple)
to eliminate data race between real-time tapProcess thread and
async dispatch queue reading the buffer
- Use kCMPersistentTrackID_Invalid for audioMix trackID so the tap
survives HLS variant switches (track ID changes on quality change)
MTAudioProcessingTap with AVPlayerItem.audioMix doesn't fire its process
callback for HLS streams on macOS (known limitation). This caused recorded
videos to have no audio track when recording HLS content.
Replace with CoreAudio AudioDeviceCreateIOProcID approach that captures
PCM audio directly from the system output device. This works for all
audio sources (HLS, local files, etc).
Key changes:
- Remove MTAudioProcessingTap and all tap callbacks
- Add CoreAudio device IOProc for audio capture
- Pre-allocated buffer to avoid malloc in real-time audio thread
- Serial dispatch queue for CMSampleBuffer processing off audio thread
- AudioTimeStamp → CMTime conversion for proper timestamps
- RecorderState.isFinalizing flag set during stopRecording → finishWriting
- applicationShouldTerminate returns .terminateLater if finalizing
- Shows alert '正在保存录制文件...' to inform user
- After save dialog completes, calls NSApp.reply(toApplicationShouldTerminate: true)
- All failure paths also check and resolve pendingTerminate
- Fixes: 9-min recording lost because user closed app before async finishWriting completed
- setupAudioTap: log all tracks at start, KVO count changes
- findAudioTrackID: log when assetTrack is nil (HLS not loaded)
- Polling: add async asset.load(.tracks) fallback after 5s
- Extend timeout from 20s to 30s
- Status log every 5s during polling