fix(ffmpeg): use app-writable temporary directory for stream buffers

This commit is contained in:
Levi committed 2026-09-13 18:00:10 +08:00
1 parent 9124976b5c
commit 637004c8a8
6 files changed
+61 -8

No files matched your search

+38 -2
View File
@@ -8,6 +8,7 @@ import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"sync/atomic"
"testing"
@@ -117,7 +118,7 @@ func TestHTTPSequentialAndValidation(t *testing.T) {
}
func TestStreamRing(t *testing.T) {
s := NewStreamInput(context.Background())
s := NewStreamInput(context.Background(), "")
defer s.Close()
// Start near the boundary to exercise wrap-around without a huge fixture.
s.read = StreamBufferLimit - 3
@@ -157,7 +158,7 @@ func TestOutputArguments(t *testing.T) {
func TestStreamBackpressureAndCancel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
s := NewStreamInput(ctx)
s := NewStreamInput(ctx, "")
defer s.Close()
f, err := os.CreateTemp(t.TempDir(), "ring")
if err != nil {
@@ -197,3 +198,38 @@ func TestStreamBackpressureAndCancel(t *testing.T) {
t.Fatal("writer did not cancel")
}
}
// A mobile app can write to its storage directory even when the OS default
// temporary directory is unavailable. The buffer must also be removed on close.
func TestStreamInputAppTempDir(t *testing.T) {
root := t.TempDir()
unavailable := filepath.Join(root, "not-a-directory")
if err := os.WriteFile(unavailable, []byte("blocked"), 0600); err != nil {
t.Fatal(err)
}
t.Setenv("TMPDIR", unavailable)
t.Setenv("TMP", unavailable)
t.Setenv("TEMP", unavailable)
dir := filepath.Join(root, "app", "temp", "ffmpeg")
s := NewStreamInput(context.Background(), dir)
t.Cleanup(func() { s.Close() })
data := []byte("media stream")
if _, err := s.Write(data); err != nil {
t.Fatal(err)
}
if filepath.Dir(s.file.Name()) != dir {
t.Fatalf("unexpected buffer: %s", s.file.Name())
}
s.End(nil)
got, err := io.ReadAll(s)
if err != nil || !bytes.Equal(got, data) {
t.Fatalf("read %q, error %v", got, err)
}
if err := s.Close(); err != nil {
t.Fatal(err)
}
entries, err := os.ReadDir(dir)
if err != nil || len(entries) != 0 {
t.Fatalf("buffer cleanup: %v, %v", entries, err)
}
}
+2 -2
View File
@@ -128,7 +128,7 @@ func TestWebMToMP4(t *testing.T) {
if err != nil {
t.Fatal(err)
}
inputs[i] = NewStreamInput(ctx)
inputs[i] = NewStreamInput(ctx, "")
defer inputs[i].Close()
if _, err = inputs[i].Write(data); err != nil {
t.Fatal(err)
@@ -205,7 +205,7 @@ func TestWASMMerge(t *testing.T) {
})
}
// A malformed input must produce an error, not successful empty output.
s := NewStreamInput(context.Background())
s := NewStreamInput(context.Background(), "")
defer s.Close()
s.Write([]byte("invalid"))
s.End(nil)
+10 -3
View File
@@ -17,6 +17,7 @@ const StreamBufferLimit = 64 * 1024 * 1024
type StreamInput struct {
ctx context.Context
tempDir string
mu sync.Mutex
file *os.File
read, written int64
@@ -26,8 +27,8 @@ type StreamInput struct {
space chan struct{}
}
func NewStreamInput(ctx context.Context) *StreamInput {
return &StreamInput{ctx: ctx, wake: make(chan struct{}, 1), space: make(chan struct{}, 1)}
func NewStreamInput(ctx context.Context, tempDir string) *StreamInput {
return &StreamInput{ctx: ctx, tempDir: tempDir, wake: make(chan struct{}, 1), space: make(chan struct{}, 1)}
}
func (s *StreamInput) Write(p []byte) (int, error) {
@@ -70,7 +71,13 @@ func (s *StreamInput) Write(p []byte) (int, error) {
return 0, nil
}
if s.file == nil {
f, err := os.CreateTemp("", "gopeed-ffmpeg-*")
// Android apps cannot write to Go's default /data/local/tmp.
if s.tempDir != "" {
if err := os.MkdirAll(s.tempDir, 0700); err != nil {
return 0, err
}
}
f, err := os.CreateTemp(s.tempDir, "gopeed-ffmpeg-*")
if err != nil {
return 0, err
}