detached 4c97002

player_test.go RawHistoryFolder

1//go:build integration
2
3package integration
4
5import (
6	"testing"
7	"time"
8
9	"github.com/bnema/purego-pipewire/pipewire"
10)
11
12// TestPlayerStartStopClose verifies the player lifecycle against a real PipeWire
13// daemon. This test requires a running PipeWire runtime and only validates
14// externally visible state transitions (Idle -> Playing -> Stopped -> Closed).
15// Deeper cleanup internals are covered by unit tests.
16func TestPlayerStartStopClose(t *testing.T) {
17	// Initialize PipeWire
18	initResult, err := pipewire.Init()
19	if err != nil {
20		t.Fatalf("Init returned error: %v", err)
21	}
22	defer initResult.Deinit()
23
24	// Create player with specified configuration
25	config := pipewire.PlayerConfig{
26		SampleRate:      48000,
27		Channels:        2,
28		FramesPerBuffer: 256,
29		SampleFormat:    pipewire.SampleFormatF32,
30		UnderrunPolicy:  pipewire.UnderrunFillSilence,
31	}
32
33	callbacks := pipewire.PlayerCallbacks{
34		Fill: func(buf *pipewire.PCMBuffer) (int, error) {
35			return buf.Frames, nil
36		},
37	}
38
39	player, err := pipewire.NewPlayer(config, callbacks)
40	if err != nil {
41		t.Fatalf("NewPlayer returned error: %v", err)
42	}
43
44	// Verify initial state is Idle
45	if state := player.State(); state != pipewire.PlayerStateIdle {
46		t.Errorf("Expected initial state Idle, got %v", state)
47	}
48
49	// Test Start - should transition to Playing state
50	if err := player.Start(); err != nil {
51		t.Errorf("Start returned error: %v", err)
52	}
53	if state := player.State(); state != pipewire.PlayerStatePlaying {
54		t.Errorf("After Start: expected state Playing, got %v", state)
55	}
56
57	// Test Stop - should transition to Stopped state
58	if err := player.Stop(); err != nil {
59		t.Errorf("Stop returned error: %v", err)
60	}
61	if state := player.State(); state != pipewire.PlayerStateStopped {
62		t.Errorf("After Stop: expected state Stopped, got %v", state)
63	}
64
65	// Test Close - should transition to Closed state
66	if err := player.Close(); err != nil {
67		t.Errorf("Close returned error: %v", err)
68	}
69	if state := player.State(); state != pipewire.PlayerStateClosed {
70		t.Errorf("After Close: expected state Closed, got %v", state)
71	}
72}
73
74// TestPlayerPlaybackInvokesFill proves that starting a Player under a live
75// PipeWire daemon causes the process callback to invoke the user-supplied Fill
76// callback. A buffered channel synchronizes the test instead of sleeping.
77func TestPlayerPlaybackInvokesFill(t *testing.T) {
78	initResult, err := pipewire.Init()
79	if err != nil {
80		t.Fatalf("Init returned error: %v", err)
81	}
82	defer initResult.Deinit()
83
84	filled := make(chan struct{}, 1) // buffered so Fill never blocks
85
86	config := pipewire.PlayerConfig{
87		SampleRate:      48000,
88		Channels:        2,
89		FramesPerBuffer: 512,
90		SampleFormat:    pipewire.SampleFormatF32,
91		UnderrunPolicy:  pipewire.UnderrunFillSilence,
92	}
93
94	callbacks := pipewire.PlayerCallbacks{
95		Fill: func(buf *pipewire.PCMBuffer) (int, error) {
96			// Write a small constant so the buffer isn't silent.
97			const val = 0.001
98			for ch := range buf.Samples {
99				for i := range buf.Samples[ch] {
100					buf.Samples[ch][i] = val
101				}
102			}
103			// Signal exactly once.
104			select {
105			case filled <- struct{}{}:
106			default:
107			}
108			return buf.Frames, nil
109		},
110	}
111
112	player, err := pipewire.NewPlayer(config, callbacks)
113	if err != nil {
114		t.Fatalf("NewPlayer returned error: %v", err)
115	}
116
117	if err := player.Start(); err != nil {
118		t.Fatalf("Start returned error: %v", err)
119	}
120
121	// Wait for the Fill callback to fire at least once.
122	select {
123	case <-filled:
124		// success — the real PipeWire process callback invoked Fill
125	case <-time.After(5 * time.Second):
126		t.Fatal("Timed out waiting for Fill callback; PipeWire process callback did not fire")
127	}
128
129	if err := player.Stop(); err != nil {
130		t.Errorf("Stop returned error: %v", err)
131	}
132	if err := player.Close(); err != nil {
133		t.Errorf("Close returned error: %v", err)
134	}
135}

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