commit c7adf6fb145b50d5e016235d8f8c92b138465526
parent 561ed97c1e04e65ca49c0aaae4f2f03e0524b342
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date: Mon, 21 Sep 2026 07:30:21 -0300
docs: record that every native backend now streams
Both items the TODO tracked are done, so the section goes with them.
Diffstat:
2 files changed, 9 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
@@ -75,8 +75,9 @@ play.File("audio.m4a")
- `Decoder.StreamFile(path)` and `Decoder.Stream(data)` return a `Stream`,
an `io.Reader` over the same PCM, so a long track never has to sit in
memory whole. `Format` is known before the first read. Close it when
- done. Windows and Linux decode incrementally, and the sub-process
- backend pipes; macOS currently decodes up front and serves from memory.
+ done. Every native backend decodes incrementally, and the sub-process
+ backend pipes when given a file; only decoding from memory without a
+ native backend buffers up front.
- `Format` reports `SampleRate`, `Channels` and `BytesPerSample`, which is
always 2.
- A `Decoder` is safe for concurrent use, and `Close` waits for decodes
@@ -161,7 +162,3 @@ ship by default.
| HE-AAC v2 | yes, stereo | yes | yes |
| xHE-AAC | yes, Windows 11 | yes | partial |
| AAC-LD and AAC-ELD | no | yes | yes |
-
-## TODO
-
-- [ ] macOS: decode incrementally rather than buffering behind `Stream`
diff --git a/audio.go b/audio.go
@@ -129,9 +129,10 @@ type Format struct {
// Stream decodes compressed audio held in memory, returning PCM through
// an [io.Reader] rather than a single buffer.
//
-// The returned Stream must be closed. The Windows and Linux backends
-// decode incrementally; macOS and the subprocess fallback decode up
-// front and serve from memory, which is correct but saves nothing.
+// The returned Stream must be closed. Every native backend decodes
+// incrementally; only the subprocess fallback decodes up front and
+// serves from memory, because ffmpeg cannot read every container this
+// package supports from a pipe.
func (d *Decoder) Stream(compressed []byte) (*Stream, error) {
d.mu.RLock()
defer d.mu.RUnlock()
@@ -152,8 +153,8 @@ func (d *Decoder) Stream(compressed []byte) (*Stream, error) {
//
// The returned Stream must be closed. The Linux backend and the
// subprocess fallback read the file directly, so the PCM is never held
-// whole; the Windows backend reads the compressed file into memory
-// first, which is small next to the PCM it avoids buffering.
+// whole; Windows and macOS read the compressed file into memory first,
+// which is small next to the PCM it avoids buffering.
func (d *Decoder) StreamFile(path string) (*Stream, error) {
d.mu.RLock()
defer d.mu.RUnlock()