detached 28a2e52

refactor: carry playback format through player core

commit
28a2e52057744b1c597594171e819c136971f395
author
brice · · 2026-04-10 12:34 UTC
parent
53759e6
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
6 files · +168 -22
M internal/capi/stream_ops.go +12 -3
@@ -85,7 +85,12 @@ func (s *streamOpsImpl) CreatePlaybackStream(loopPtr unsafe.Pointer, name string
8585 return ptr, nil
8686 }
8787
88-func (s *streamOpsImpl) ConnectPlaybackStream(streamPtr unsafe.Pointer) error {
88+func (s *streamOpsImpl) ConnectPlaybackStream(streamPtr unsafe.Pointer, format portout.PlaybackFormat) error {
89+ // Validate format before making the C call.
90+ if format.SampleRate <= 0 || format.Channels <= 0 || format.FramesPerBuffer <= 0 {
91+ return fmt.Errorf("invalid PlaybackFormat: SampleRate=%d, Channels=%d, FramesPerBuffer=%d",
92+ format.SampleRate, format.Channels, format.FramesPerBuffer)
93+ }
8994 // PW_DIRECTION_OUTPUT = 1, PW_ID_ANY = 0xffffffff
9095 // PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS = 0x01 | 0x04
9196 ret := pw_stream_connect(streamPtr, pwDirectionOutput, pwIDAny, pwStreamFlagAutoConnectMapBuffers, nil, 0)
@@ -148,8 +153,12 @@ func (s *streamOpsImpl) CreateMainLoop() (unsafe.Pointer, error) {
148153 return ptr, nil
149154 }
150155
151-func (s *streamOpsImpl) RunMainLoop(loopPtr unsafe.Pointer) {
152- pw_main_loop_run(loopPtr)
156+func (s *streamOpsImpl) RunMainLoop(loopPtr unsafe.Pointer) error {
157+ ret := pw_main_loop_run(loopPtr)
158+ if ret < 0 {
159+ return &PWError{Func: "pw_main_loop_run", Code: ret}
160+ }
161+ return nil
153162 }
154163
155164 func (s *streamOpsImpl) QuitMainLoop(loopPtr unsafe.Pointer) {
@@ -85,7 +85,12 @@ func (s *streamOpsImpl) CreatePlaybackStream(loopPtr unsafe.Pointer, name string
85 return ptr, nil85 return ptr, nil
86 }86 }
87 87
88-func (s *streamOpsImpl) ConnectPlaybackStream(streamPtr unsafe.Pointer) error {88+func (s *streamOpsImpl) ConnectPlaybackStream(streamPtr unsafe.Pointer, format portout.PlaybackFormat) error {
89+ // Validate format before making the C call.
90+ if format.SampleRate <= 0 || format.Channels <= 0 || format.FramesPerBuffer <= 0 {
91+ return fmt.Errorf("invalid PlaybackFormat: SampleRate=%d, Channels=%d, FramesPerBuffer=%d",
92+ format.SampleRate, format.Channels, format.FramesPerBuffer)
93+ }
89 // PW_DIRECTION_OUTPUT = 1, PW_ID_ANY = 0xffffffff94 // PW_DIRECTION_OUTPUT = 1, PW_ID_ANY = 0xffffffff
90 // PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS = 0x01 | 0x0495 // PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS = 0x01 | 0x04
91 ret := pw_stream_connect(streamPtr, pwDirectionOutput, pwIDAny, pwStreamFlagAutoConnectMapBuffers, nil, 0)96 ret := pw_stream_connect(streamPtr, pwDirectionOutput, pwIDAny, pwStreamFlagAutoConnectMapBuffers, nil, 0)
@@ -148,8 +153,12 @@ func (s *streamOpsImpl) CreateMainLoop() (unsafe.Pointer, error) {
148 return ptr, nil153 return ptr, nil
149 }154 }
150 155
151-func (s *streamOpsImpl) RunMainLoop(loopPtr unsafe.Pointer) {156+func (s *streamOpsImpl) RunMainLoop(loopPtr unsafe.Pointer) error {
152- pw_main_loop_run(loopPtr)157+ ret := pw_main_loop_run(loopPtr)
158+ if ret < 0 {
159+ return &PWError{Func: "pw_main_loop_run", Code: ret}
160+ }
161+ return nil
153 }162 }
154 163
155 func (s *streamOpsImpl) QuitMainLoop(loopPtr unsafe.Pointer) {164 func (s *streamOpsImpl) QuitMainLoop(loopPtr unsafe.Pointer) {
M internal/capi/stream_ops_test.go +109 -1
@@ -4,6 +4,8 @@ import (
44 "errors"
55 "testing"
66 "unsafe"
7+
8+ portout "github.com/bnema/purego-pipewire/internal/ports/out"
79 )
810
911 // TestDestroyStreamCallsPwStreamDestroy verifies that DestroyStream delegates
@@ -133,7 +135,12 @@ func TestConnectPlaybackStreamReturnsTypedErrorOnNegativeReturn(t *testing.T) {
133135 ops := &streamOpsImpl{}
134136 fakePtr := unsafe.Pointer(uintptr(0x1234))
135137
136- err := ops.ConnectPlaybackStream(fakePtr)
138+ validFmt := portout.PlaybackFormat{
139+ SampleRate: 48000,
140+ Channels: 2,
141+ FramesPerBuffer: 1024,
142+ }
143+ err := ops.ConnectPlaybackStream(fakePtr, validFmt)
137144 if err == nil {
138145 t.Fatal("expected error for negative return code, got nil")
139146 }
@@ -259,3 +266,104 @@ func TestDisconnectStreamReturnsNilOnSuccess(t *testing.T) {
259266 t.Fatalf("expected nil on success, got %v", err)
260267 }
261268 }
269+
270+// TestConnectPlaybackStreamRejectsMissingFormat verifies that ConnectPlaybackStream
271+// now requires a PlaybackFormat and rejects a zero-valued format.
272+func TestConnectPlaybackStreamRejectsMissingFormat(t *testing.T) {
273+ origConnect := pw_stream_connect
274+ t.Cleanup(func() { pw_stream_connect = origConnect })
275+
276+ // Even if pw_stream_connect succeeds, a zero PlaybackFormat should be rejected
277+ // at the StreamOps layer before the C call is made.
278+ pw_stream_connect = func(stream unsafe.Pointer, direction int32, id uint32, flags uint32, ports unsafe.Pointer, n_ports uint32) int32 {
279+ return 0
280+ }
281+
282+ ops := &streamOpsImpl{}
283+ fakePtr := unsafe.Pointer(uintptr(0x1234))
284+
285+ // Zero-valued PlaybackFormat must be rejected.
286+ zeroFmt := portout.PlaybackFormat{}
287+ err := ops.ConnectPlaybackStream(fakePtr, zeroFmt)
288+ if err == nil {
289+ t.Fatal("expected error for zero-valued PlaybackFormat, got nil")
290+ }
291+}
292+
293+// TestConnectPlaybackStreamBuildsParamsFromFormat verifies that ConnectPlaybackStream
294+// accepts a valid PlaybackFormat and calls pw_stream_connect successfully.
295+func TestConnectPlaybackStreamBuildsParamsFromFormat(t *testing.T) {
296+ origConnect := pw_stream_connect
297+ t.Cleanup(func() { pw_stream_connect = origConnect })
298+
299+ var called bool
300+ pw_stream_connect = func(stream unsafe.Pointer, direction int32, id uint32, flags uint32, ports unsafe.Pointer, n_ports uint32) int32 {
301+ called = true
302+ return 0
303+ }
304+
305+ ops := &streamOpsImpl{}
306+ fakePtr := unsafe.Pointer(uintptr(0x5678))
307+
308+ validFmt := portout.PlaybackFormat{
309+ SampleRate: 48000,
310+ Channels: 2,
311+ FramesPerBuffer: 1024,
312+ }
313+ err := ops.ConnectPlaybackStream(fakePtr, validFmt)
314+ if err != nil {
315+ t.Fatalf("expected nil error for valid format, got %v", err)
316+ }
317+ if !called {
318+ t.Fatal("expected pw_stream_connect to be called")
319+ }
320+}
321+
322+// TestRunMainLoopReturnsErrorOnNegativeReturn verifies that RunMainLoop
323+// returns a PWError when pw_main_loop_run returns a negative value.
324+func TestRunMainLoopReturnsErrorOnNegativeReturn(t *testing.T) {
325+ origRun := pw_main_loop_run
326+ t.Cleanup(func() { pw_main_loop_run = origRun })
327+
328+ pw_main_loop_run = func(loop unsafe.Pointer) int32 {
329+ return -22 // EINVAL
330+ }
331+
332+ ops := &streamOpsImpl{}
333+ fakeLoop := unsafe.Pointer(uintptr(0xAAAA))
334+
335+ err := ops.RunMainLoop(fakeLoop)
336+ if err == nil {
337+ t.Fatal("expected error for negative return code, got nil")
338+ }
339+
340+ var pwErr *PWError
341+ if !errors.As(err, &pwErr) {
342+ t.Fatalf("expected *PWError, got %T", err)
343+ }
344+ if pwErr.Func != "pw_main_loop_run" {
345+ t.Errorf("expected Func='pw_main_loop_run', got '%s'", pwErr.Func)
346+ }
347+ if pwErr.Code != -22 {
348+ t.Errorf("expected Code=-22, got %d", pwErr.Code)
349+ }
350+}
351+
352+// TestRunMainLoopReturnsNilOnSuccess verifies that RunMainLoop
353+// returns nil when pw_main_loop_run returns zero.
354+func TestRunMainLoopReturnsNilOnSuccess(t *testing.T) {
355+ origRun := pw_main_loop_run
356+ t.Cleanup(func() { pw_main_loop_run = origRun })
357+
358+ pw_main_loop_run = func(loop unsafe.Pointer) int32 {
359+ return 0
360+ }
361+
362+ ops := &streamOpsImpl{}
363+ fakeLoop := unsafe.Pointer(uintptr(0xBBBB))
364+
365+ err := ops.RunMainLoop(fakeLoop)
366+ if err != nil {
367+ t.Fatalf("expected nil on success, got %v", err)
368+ }
369+}
@@ -4,6 +4,8 @@ import (
4 "errors"4 "errors"
5 "testing"5 "testing"
6 "unsafe"6 "unsafe"
7+
8+ portout "github.com/bnema/purego-pipewire/internal/ports/out"
7 )9 )
8 10
9 // TestDestroyStreamCallsPwStreamDestroy verifies that DestroyStream delegates11 // TestDestroyStreamCallsPwStreamDestroy verifies that DestroyStream delegates
@@ -133,7 +135,12 @@ func TestConnectPlaybackStreamReturnsTypedErrorOnNegativeReturn(t *testing.T) {
133 ops := &streamOpsImpl{}135 ops := &streamOpsImpl{}
134 fakePtr := unsafe.Pointer(uintptr(0x1234))136 fakePtr := unsafe.Pointer(uintptr(0x1234))
135 137
136- err := ops.ConnectPlaybackStream(fakePtr)138+ validFmt := portout.PlaybackFormat{
139+ SampleRate: 48000,
140+ Channels: 2,
141+ FramesPerBuffer: 1024,
142+ }
143+ err := ops.ConnectPlaybackStream(fakePtr, validFmt)
137 if err == nil {144 if err == nil {
138 t.Fatal("expected error for negative return code, got nil")145 t.Fatal("expected error for negative return code, got nil")
139 }146 }
@@ -259,3 +266,104 @@ func TestDisconnectStreamReturnsNilOnSuccess(t *testing.T) {
259 t.Fatalf("expected nil on success, got %v", err)266 t.Fatalf("expected nil on success, got %v", err)
260 }267 }
261 }268 }
269+
270+// TestConnectPlaybackStreamRejectsMissingFormat verifies that ConnectPlaybackStream
271+// now requires a PlaybackFormat and rejects a zero-valued format.
272+func TestConnectPlaybackStreamRejectsMissingFormat(t *testing.T) {
273+ origConnect := pw_stream_connect
274+ t.Cleanup(func() { pw_stream_connect = origConnect })
275+
276+ // Even if pw_stream_connect succeeds, a zero PlaybackFormat should be rejected
277+ // at the StreamOps layer before the C call is made.
278+ pw_stream_connect = func(stream unsafe.Pointer, direction int32, id uint32, flags uint32, ports unsafe.Pointer, n_ports uint32) int32 {
279+ return 0
280+ }
281+
282+ ops := &streamOpsImpl{}
283+ fakePtr := unsafe.Pointer(uintptr(0x1234))
284+
285+ // Zero-valued PlaybackFormat must be rejected.
286+ zeroFmt := portout.PlaybackFormat{}
287+ err := ops.ConnectPlaybackStream(fakePtr, zeroFmt)
288+ if err == nil {
289+ t.Fatal("expected error for zero-valued PlaybackFormat, got nil")
290+ }
291+}
292+
293+// TestConnectPlaybackStreamBuildsParamsFromFormat verifies that ConnectPlaybackStream
294+// accepts a valid PlaybackFormat and calls pw_stream_connect successfully.
295+func TestConnectPlaybackStreamBuildsParamsFromFormat(t *testing.T) {
296+ origConnect := pw_stream_connect
297+ t.Cleanup(func() { pw_stream_connect = origConnect })
298+
299+ var called bool
300+ pw_stream_connect = func(stream unsafe.Pointer, direction int32, id uint32, flags uint32, ports unsafe.Pointer, n_ports uint32) int32 {
301+ called = true
302+ return 0
303+ }
304+
305+ ops := &streamOpsImpl{}
306+ fakePtr := unsafe.Pointer(uintptr(0x5678))
307+
308+ validFmt := portout.PlaybackFormat{
309+ SampleRate: 48000,
310+ Channels: 2,
311+ FramesPerBuffer: 1024,
312+ }
313+ err := ops.ConnectPlaybackStream(fakePtr, validFmt)
314+ if err != nil {
315+ t.Fatalf("expected nil error for valid format, got %v", err)
316+ }
317+ if !called {
318+ t.Fatal("expected pw_stream_connect to be called")
319+ }
320+}
321+
322+// TestRunMainLoopReturnsErrorOnNegativeReturn verifies that RunMainLoop
323+// returns a PWError when pw_main_loop_run returns a negative value.
324+func TestRunMainLoopReturnsErrorOnNegativeReturn(t *testing.T) {
325+ origRun := pw_main_loop_run
326+ t.Cleanup(func() { pw_main_loop_run = origRun })
327+
328+ pw_main_loop_run = func(loop unsafe.Pointer) int32 {
329+ return -22 // EINVAL
330+ }
331+
332+ ops := &streamOpsImpl{}
333+ fakeLoop := unsafe.Pointer(uintptr(0xAAAA))
334+
335+ err := ops.RunMainLoop(fakeLoop)
336+ if err == nil {
337+ t.Fatal("expected error for negative return code, got nil")
338+ }
339+
340+ var pwErr *PWError
341+ if !errors.As(err, &pwErr) {
342+ t.Fatalf("expected *PWError, got %T", err)
343+ }
344+ if pwErr.Func != "pw_main_loop_run" {
345+ t.Errorf("expected Func='pw_main_loop_run', got '%s'", pwErr.Func)
346+ }
347+ if pwErr.Code != -22 {
348+ t.Errorf("expected Code=-22, got %d", pwErr.Code)
349+ }
350+}
351+
352+// TestRunMainLoopReturnsNilOnSuccess verifies that RunMainLoop
353+// returns nil when pw_main_loop_run returns zero.
354+func TestRunMainLoopReturnsNilOnSuccess(t *testing.T) {
355+ origRun := pw_main_loop_run
356+ t.Cleanup(func() { pw_main_loop_run = origRun })
357+
358+ pw_main_loop_run = func(loop unsafe.Pointer) int32 {
359+ return 0
360+ }
361+
362+ ops := &streamOpsImpl{}
363+ fakeLoop := unsafe.Pointer(uintptr(0xBBBB))
364+
365+ err := ops.RunMainLoop(fakeLoop)
366+ if err != nil {
367+ t.Fatalf("expected nil on success, got %v", err)
368+ }
369+}
M internal/core/player_rt.go +1 -0
@@ -36,6 +36,7 @@ func (b *PCMBuffer) allocate() {
3636
3737 // PlayerConfig holds configuration for the player
3838 type PlayerConfig struct {
39+ SampleRate int
3940 FramesPerBuffer int
4041 Channels int
4142 UnderrunPolicy UnderrunPolicy
@@ -36,6 +36,7 @@ func (b *PCMBuffer) allocate() {
36 36
37 // PlayerConfig holds configuration for the player37 // PlayerConfig holds configuration for the player
38 type PlayerConfig struct {38 type PlayerConfig struct {
39+ SampleRate int
39 FramesPerBuffer int40 FramesPerBuffer int
40 Channels int41 Channels int
41 UnderrunPolicy UnderrunPolicy42 UnderrunPolicy UnderrunPolicy
M internal/ports/out/mocks/mock_StreamOps.go +33 -15
@@ -7,6 +7,7 @@ package mocks
77 import (
88 "unsafe"
99
10+ "github.com/bnema/purego-pipewire/internal/ports/out"
1011 mock "github.com/stretchr/testify/mock"
1112 )
1213
@@ -38,16 +39,16 @@ func (_m *MockStreamOps) EXPECT() *MockStreamOps_Expecter {
3839 }
3940
4041 // ConnectPlaybackStream provides a mock function for the type MockStreamOps
41-func (_mock *MockStreamOps) ConnectPlaybackStream(streamPtr unsafe.Pointer) error {
42- ret := _mock.Called(streamPtr)
42+func (_mock *MockStreamOps) ConnectPlaybackStream(streamPtr unsafe.Pointer, format out.PlaybackFormat) error {
43+ ret := _mock.Called(streamPtr, format)
4344
4445 if len(ret) == 0 {
4546 panic("no return value specified for ConnectPlaybackStream")
4647 }
4748
4849 var r0 error
49- if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer) error); ok {
50- r0 = returnFunc(streamPtr)
50+ if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer, out.PlaybackFormat) error); ok {
51+ r0 = returnFunc(streamPtr, format)
5152 } else {
5253 r0 = ret.Error(0)
5354 }
@@ -61,18 +62,24 @@ type MockStreamOps_ConnectPlaybackStream_Call struct {
6162
6263 // ConnectPlaybackStream is a helper method to define mock.On call
6364 // - streamPtr unsafe.Pointer
64-func (_e *MockStreamOps_Expecter) ConnectPlaybackStream(streamPtr interface{}) *MockStreamOps_ConnectPlaybackStream_Call {
65- return &MockStreamOps_ConnectPlaybackStream_Call{Call: _e.mock.On("ConnectPlaybackStream", streamPtr)}
65+// - format out.PlaybackFormat
66+func (_e *MockStreamOps_Expecter) ConnectPlaybackStream(streamPtr interface{}, format interface{}) *MockStreamOps_ConnectPlaybackStream_Call {
67+ return &MockStreamOps_ConnectPlaybackStream_Call{Call: _e.mock.On("ConnectPlaybackStream", streamPtr, format)}
6668 }
6769
68-func (_c *MockStreamOps_ConnectPlaybackStream_Call) Run(run func(streamPtr unsafe.Pointer)) *MockStreamOps_ConnectPlaybackStream_Call {
70+func (_c *MockStreamOps_ConnectPlaybackStream_Call) Run(run func(streamPtr unsafe.Pointer, format out.PlaybackFormat)) *MockStreamOps_ConnectPlaybackStream_Call {
6971 _c.Call.Run(func(args mock.Arguments) {
7072 var arg0 unsafe.Pointer
7173 if args[0] != nil {
7274 arg0 = args[0].(unsafe.Pointer)
7375 }
76+ var arg1 out.PlaybackFormat
77+ if args[1] != nil {
78+ arg1 = args[1].(out.PlaybackFormat)
79+ }
7480 run(
7581 arg0,
82+ arg1,
7683 )
7784 })
7885 return _c
@@ -83,7 +90,7 @@ func (_c *MockStreamOps_ConnectPlaybackStream_Call) Return(err error) *MockStrea
8390 return _c
8491 }
8592
86-func (_c *MockStreamOps_ConnectPlaybackStream_Call) RunAndReturn(run func(streamPtr unsafe.Pointer) error) *MockStreamOps_ConnectPlaybackStream_Call {
93+func (_c *MockStreamOps_ConnectPlaybackStream_Call) RunAndReturn(run func(streamPtr unsafe.Pointer, format out.PlaybackFormat) error) *MockStreamOps_ConnectPlaybackStream_Call {
8794 _c.Call.Return(run)
8895 return _c
8996 }
@@ -493,9 +500,20 @@ func (_c *MockStreamOps_QuitMainLoop_Call) RunAndReturn(run func(loopPtr unsafe.
493500 }
494501
495502 // RunMainLoop provides a mock function for the type MockStreamOps
496-func (_mock *MockStreamOps) RunMainLoop(loopPtr unsafe.Pointer) {
497- _mock.Called(loopPtr)
498- return
503+func (_mock *MockStreamOps) RunMainLoop(loopPtr unsafe.Pointer) error {
504+ ret := _mock.Called(loopPtr)
505+
506+ if len(ret) == 0 {
507+ panic("no return value specified for RunMainLoop")
508+ }
509+
510+ var r0 error
511+ if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer) error); ok {
512+ r0 = returnFunc(loopPtr)
513+ } else {
514+ r0 = ret.Error(0)
515+ }
516+ return r0
499517 }
500518
501519 // MockStreamOps_RunMainLoop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RunMainLoop'
@@ -522,13 +540,13 @@ func (_c *MockStreamOps_RunMainLoop_Call) Run(run func(loopPtr unsafe.Pointer))
522540 return _c
523541 }
524542
525-func (_c *MockStreamOps_RunMainLoop_Call) Return() *MockStreamOps_RunMainLoop_Call {
526- _c.Call.Return()
543+func (_c *MockStreamOps_RunMainLoop_Call) Return(err error) *MockStreamOps_RunMainLoop_Call {
544+ _c.Call.Return(err)
527545 return _c
528546 }
529547
530-func (_c *MockStreamOps_RunMainLoop_Call) RunAndReturn(run func(loopPtr unsafe.Pointer)) *MockStreamOps_RunMainLoop_Call {
531- _c.Run(run)
548+func (_c *MockStreamOps_RunMainLoop_Call) RunAndReturn(run func(loopPtr unsafe.Pointer) error) *MockStreamOps_RunMainLoop_Call {
549+ _c.Call.Return(run)
532550 return _c
533551 }
534552
@@ -7,6 +7,7 @@ package mocks
7 import (7 import (
8 "unsafe"8 "unsafe"
9 9
10+ "github.com/bnema/purego-pipewire/internal/ports/out"
10 mock "github.com/stretchr/testify/mock"11 mock "github.com/stretchr/testify/mock"
11 )12 )
12 13
@@ -38,16 +39,16 @@ func (_m *MockStreamOps) EXPECT() *MockStreamOps_Expecter {
38 }39 }
39 40
40 // ConnectPlaybackStream provides a mock function for the type MockStreamOps41 // ConnectPlaybackStream provides a mock function for the type MockStreamOps
41-func (_mock *MockStreamOps) ConnectPlaybackStream(streamPtr unsafe.Pointer) error {42+func (_mock *MockStreamOps) ConnectPlaybackStream(streamPtr unsafe.Pointer, format out.PlaybackFormat) error {
42- ret := _mock.Called(streamPtr)43+ ret := _mock.Called(streamPtr, format)
43 44
44 if len(ret) == 0 {45 if len(ret) == 0 {
45 panic("no return value specified for ConnectPlaybackStream")46 panic("no return value specified for ConnectPlaybackStream")
46 }47 }
47 48
48 var r0 error49 var r0 error
49- if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer) error); ok {50+ if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer, out.PlaybackFormat) error); ok {
50- r0 = returnFunc(streamPtr)51+ r0 = returnFunc(streamPtr, format)
51 } else {52 } else {
52 r0 = ret.Error(0)53 r0 = ret.Error(0)
53 }54 }
@@ -61,18 +62,24 @@ type MockStreamOps_ConnectPlaybackStream_Call struct {
61 62
62 // ConnectPlaybackStream is a helper method to define mock.On call63 // ConnectPlaybackStream is a helper method to define mock.On call
63 // - streamPtr unsafe.Pointer64 // - streamPtr unsafe.Pointer
64-func (_e *MockStreamOps_Expecter) ConnectPlaybackStream(streamPtr interface{}) *MockStreamOps_ConnectPlaybackStream_Call {65+// - format out.PlaybackFormat
65- return &MockStreamOps_ConnectPlaybackStream_Call{Call: _e.mock.On("ConnectPlaybackStream", streamPtr)}66+func (_e *MockStreamOps_Expecter) ConnectPlaybackStream(streamPtr interface{}, format interface{}) *MockStreamOps_ConnectPlaybackStream_Call {
67+ return &MockStreamOps_ConnectPlaybackStream_Call{Call: _e.mock.On("ConnectPlaybackStream", streamPtr, format)}
66 }68 }
67 69
68-func (_c *MockStreamOps_ConnectPlaybackStream_Call) Run(run func(streamPtr unsafe.Pointer)) *MockStreamOps_ConnectPlaybackStream_Call {70+func (_c *MockStreamOps_ConnectPlaybackStream_Call) Run(run func(streamPtr unsafe.Pointer, format out.PlaybackFormat)) *MockStreamOps_ConnectPlaybackStream_Call {
69 _c.Call.Run(func(args mock.Arguments) {71 _c.Call.Run(func(args mock.Arguments) {
70 var arg0 unsafe.Pointer72 var arg0 unsafe.Pointer
71 if args[0] != nil {73 if args[0] != nil {
72 arg0 = args[0].(unsafe.Pointer)74 arg0 = args[0].(unsafe.Pointer)
73 }75 }
76+ var arg1 out.PlaybackFormat
77+ if args[1] != nil {
78+ arg1 = args[1].(out.PlaybackFormat)
79+ }
74 run(80 run(
75 arg0,81 arg0,
82+ arg1,
76 )83 )
77 })84 })
78 return _c85 return _c
@@ -83,7 +90,7 @@ func (_c *MockStreamOps_ConnectPlaybackStream_Call) Return(err error) *MockStrea
83 return _c90 return _c
84 }91 }
85 92
86-func (_c *MockStreamOps_ConnectPlaybackStream_Call) RunAndReturn(run func(streamPtr unsafe.Pointer) error) *MockStreamOps_ConnectPlaybackStream_Call {93+func (_c *MockStreamOps_ConnectPlaybackStream_Call) RunAndReturn(run func(streamPtr unsafe.Pointer, format out.PlaybackFormat) error) *MockStreamOps_ConnectPlaybackStream_Call {
87 _c.Call.Return(run)94 _c.Call.Return(run)
88 return _c95 return _c
89 }96 }
@@ -493,9 +500,20 @@ func (_c *MockStreamOps_QuitMainLoop_Call) RunAndReturn(run func(loopPtr unsafe.
493 }500 }
494 501
495 // RunMainLoop provides a mock function for the type MockStreamOps502 // RunMainLoop provides a mock function for the type MockStreamOps
496-func (_mock *MockStreamOps) RunMainLoop(loopPtr unsafe.Pointer) {503+func (_mock *MockStreamOps) RunMainLoop(loopPtr unsafe.Pointer) error {
497- _mock.Called(loopPtr)504+ ret := _mock.Called(loopPtr)
498- return505+
506+ if len(ret) == 0 {
507+ panic("no return value specified for RunMainLoop")
508+ }
509+
510+ var r0 error
511+ if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer) error); ok {
512+ r0 = returnFunc(loopPtr)
513+ } else {
514+ r0 = ret.Error(0)
515+ }
516+ return r0
499 }517 }
500 518
501 // MockStreamOps_RunMainLoop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RunMainLoop'519 // MockStreamOps_RunMainLoop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RunMainLoop'
@@ -522,13 +540,13 @@ func (_c *MockStreamOps_RunMainLoop_Call) Run(run func(loopPtr unsafe.Pointer))
522 return _c540 return _c
523 }541 }
524 542
525-func (_c *MockStreamOps_RunMainLoop_Call) Return() *MockStreamOps_RunMainLoop_Call {543+func (_c *MockStreamOps_RunMainLoop_Call) Return(err error) *MockStreamOps_RunMainLoop_Call {
526- _c.Call.Return()544+ _c.Call.Return(err)
527 return _c545 return _c
528 }546 }
529 547
530-func (_c *MockStreamOps_RunMainLoop_Call) RunAndReturn(run func(loopPtr unsafe.Pointer)) *MockStreamOps_RunMainLoop_Call {548+func (_c *MockStreamOps_RunMainLoop_Call) RunAndReturn(run func(loopPtr unsafe.Pointer) error) *MockStreamOps_RunMainLoop_Call {
531- _c.Run(run)549+ _c.Call.Return(run)
532 return _c550 return _c
533 }551 }
534 552
M internal/ports/out/stream_ops.go +12 -3
@@ -2,6 +2,13 @@ package out
22
33 import "unsafe"
44
5+// PlaybackFormat describes the audio format for a playback stream.
6+type PlaybackFormat struct {
7+ SampleRate int
8+ Channels int
9+ FramesPerBuffer int
10+}
11+
512 // StreamOps defines the outbound interface for PipeWire stream lifecycle
613 // operations that the player needs. This abstracts the raw C API so the
714 // player can be tested without a real PipeWire daemon.
@@ -11,8 +18,9 @@ type StreamOps interface {
1118 // stream needs more audio data. Returns the stream pointer or an error.
1219 CreatePlaybackStream(loopPtr unsafe.Pointer, name string, onProcess func()) (streamPtr unsafe.Pointer, err error)
1320
14- // ConnectPlaybackStream connects the stream for playback output.
15- ConnectPlaybackStream(streamPtr unsafe.Pointer) error
21+ // ConnectPlaybackStream connects the stream for playback output using the
22+ // given format. Returns an error if the format is invalid or the C call fails.
23+ ConnectPlaybackStream(streamPtr unsafe.Pointer, format PlaybackFormat) error
1624
1725 // SetStreamActive activates or deactivates the stream.
1826 SetStreamActive(streamPtr unsafe.Pointer, active bool) error
@@ -36,7 +44,8 @@ type StreamOps interface {
3644 CreateMainLoop() (loopPtr unsafe.Pointer, err error)
3745
3846 // RunMainLoop runs the main loop (blocks until quit).
39- RunMainLoop(loopPtr unsafe.Pointer)
47+ // Returns an error if the loop fails to run.
48+ RunMainLoop(loopPtr unsafe.Pointer) error
4049
4150 // QuitMainLoop signals the main loop to stop.
4251 QuitMainLoop(loopPtr unsafe.Pointer)
@@ -2,6 +2,13 @@ package out
2 2
3 import "unsafe"3 import "unsafe"
4 4
5+// PlaybackFormat describes the audio format for a playback stream.
6+type PlaybackFormat struct {
7+ SampleRate int
8+ Channels int
9+ FramesPerBuffer int
10+}
11+
5 // StreamOps defines the outbound interface for PipeWire stream lifecycle12 // StreamOps defines the outbound interface for PipeWire stream lifecycle
6 // operations that the player needs. This abstracts the raw C API so the13 // operations that the player needs. This abstracts the raw C API so the
7 // player can be tested without a real PipeWire daemon.14 // player can be tested without a real PipeWire daemon.
@@ -11,8 +18,9 @@ type StreamOps interface {
11 // stream needs more audio data. Returns the stream pointer or an error.18 // stream needs more audio data. Returns the stream pointer or an error.
12 CreatePlaybackStream(loopPtr unsafe.Pointer, name string, onProcess func()) (streamPtr unsafe.Pointer, err error)19 CreatePlaybackStream(loopPtr unsafe.Pointer, name string, onProcess func()) (streamPtr unsafe.Pointer, err error)
13 20
14- // ConnectPlaybackStream connects the stream for playback output.21+ // ConnectPlaybackStream connects the stream for playback output using the
15- ConnectPlaybackStream(streamPtr unsafe.Pointer) error22+ // given format. Returns an error if the format is invalid or the C call fails.
23+ ConnectPlaybackStream(streamPtr unsafe.Pointer, format PlaybackFormat) error
16 24
17 // SetStreamActive activates or deactivates the stream.25 // SetStreamActive activates or deactivates the stream.
18 SetStreamActive(streamPtr unsafe.Pointer, active bool) error26 SetStreamActive(streamPtr unsafe.Pointer, active bool) error
@@ -36,7 +44,8 @@ type StreamOps interface {
36 CreateMainLoop() (loopPtr unsafe.Pointer, err error)44 CreateMainLoop() (loopPtr unsafe.Pointer, err error)
37 45
38 // RunMainLoop runs the main loop (blocks until quit).46 // RunMainLoop runs the main loop (blocks until quit).
39- RunMainLoop(loopPtr unsafe.Pointer)47+ // Returns an error if the loop fails to run.
48+ RunMainLoop(loopPtr unsafe.Pointer) error
40 49
41 // QuitMainLoop signals the main loop to stop.50 // QuitMainLoop signals the main loop to stop.
42 QuitMainLoop(loopPtr unsafe.Pointer)51 QuitMainLoop(loopPtr unsafe.Pointer)
M pipewire/player.go +1 -0
@@ -207,6 +207,7 @@ func NewPlayer(config PlayerConfig, callbacks PlayerCallbacks) (Player, error) {
207207
208208 // Create internal player with mapped config
209209 coreConfig := core.PlayerConfig{
210+ SampleRate: config.SampleRate,
210211 FramesPerBuffer: config.FramesPerBuffer,
211212 Channels: config.Channels,
212213 UnderrunPolicy: coreUnderrunPolicy,
@@ -207,6 +207,7 @@ func NewPlayer(config PlayerConfig, callbacks PlayerCallbacks) (Player, error) {
207 207
208 // Create internal player with mapped config208 // Create internal player with mapped config
209 coreConfig := core.PlayerConfig{209 coreConfig := core.PlayerConfig{
210+ SampleRate: config.SampleRate,
210 FramesPerBuffer: config.FramesPerBuffer,211 FramesPerBuffer: config.FramesPerBuffer,
211 Channels: config.Channels,212 Channels: config.Channels,
212 UnderrunPolicy: coreUnderrunPolicy,213 UnderrunPolicy: coreUnderrunPolicy,

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 8

↑↓ moveenter openesc close