Skip to content

[V1 Phase 2 · P2] Release audio playback resources when playback finishes #92

Description

@douglasjarquin

Status: Ready
Parent: #1
Audit baseline: 4258b8d2cfb98bb4809ad698028aeb905b0c732c
Priority: P2 — History playback retains its player after natural completion and lacks a complete lifecycle contract
Depends on: the Phase 0/1 pipeline fixes; may be implemented independently afterward

Problem

AudioPlayback.play() creates an AVAudioPlayer, starts it, and stores it in a retained property. The property is cleared only when stop() is explicitly called or another play replaces it.

Natural playback completion has no delegate/completion path, so the completed player and any resources it retains remain owned by Oigo for the rest of the application session. Playback errors after startup, replacement races, History window closure, and repeated play actions also have no explicit operation-generation or UI-state contract.

This is not the largest V1 blocker, but it violates the repository's release rule that session-specific resources must be released after every terminal path and makes repeated playback/resource measurements ambiguous.

Required behavior

  1. Normal completion releases the current AVAudioPlayer and associated playback state promptly.
  2. Explicit stop, replacement by another recording, decode failure, History closure where appropriate, and application termination all converge on one idempotent cleanup path.
  3. A late completion callback from an older player cannot clear or change a newer active player.
  4. Repeated playback does not accumulate players, file descriptors, decoded buffers, delegates, timers, or tasks.
  5. Playback state is truthful enough for History to present Play/Stop or completion feedback without polling.
  6. No audio path, filename, or session identity enters ordinary diagnostics.

Phased implementation plan

Phase A — Add a playback operation contract

  • Model one active playback generation containing the player and a monotonically changing operation ID.
  • Add a small retained delegate/proxy or equivalent completion hook.
  • Define terminal outcomes: completed, stopped, replaced, decode/start failure, and shutdown.
  • Keep the public surface small and independent of dictation capture/transcription state.

Phase B — Centralize idempotent teardown

  • Clear the active player only when the callback/command matches the current generation.
  • Stop and release an existing player before installing a replacement.
  • Release delegate/proxy and completion closures on every terminal path.
  • Ensure stop() is safe before start, after completion, and when called repeatedly.

Phase C — Integrate truthful History behavior

  • Let History reflect active/completed/stopped state through bounded callbacks on the main actor.
  • Disable or relabel controls only for the currently playing session.
  • Stop playback before deleting its session and during application termination.
  • Decide explicitly whether closing History stops playback; enforce and test the chosen behavior.

Phase D — Lifecycle verification

  • Add deterministic lifecycle tests for natural completion, stop, replacement, start failure, late callback, delete-during-playback, History closure, and shutdown.
  • Run 100 short playback cycles and track live players, file descriptors, memory, and completion callbacks.
  • Add native CAF playback evidence to [11/13 · Quality] Complete reliability, compatibility, and dogfood validation #12 without retaining or publishing audio content.

Acceptance criteria

  • The current player reference is nil promptly after natural playback completion.
  • Stop, replacement, failure, deletion, window lifecycle, and shutdown use one idempotent release path.
  • A late callback from player A cannot clear or alter active player B.
  • Starting a new recording stops/releases the previous player before retaining the replacement.
  • Deleting a session cannot leave its recording actively referenced or playing.
  • History displays truthful playback state without a recurring polling timer.
  • One hundred playback cycles show no growth in active players, delegates, tasks, file descriptors, or memory beyond the documented tolerance.
  • Dictation capture, transcription, insertion, and idle performance are unchanged.
  • Diagnostics remain payload-free and contain no local audio path or user/session identity.

Out of scope

  • Waveforms, scrubbing, speed control, playlists, or audio editing
  • Multiple concurrent recordings
  • Media-key integration
  • Replacing AVAudioPlayer without measured need

Handoff

Implement after the V1 P0/P1 dictation path is dependable. Add completion, stop, replacement, delete, History-close, and shutdown rows to #12's lifecycle matrix.

Metadata

Metadata

Labels

bugSomething isn't workingready

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions