stream.go RawHistoryFolder
1package screencast
2
3import (
4 "time"
5
6 "github.com/bnema/neferwl/internal/ports"
7 "github.com/bnema/purego-pipewire/pipewire"
8 "github.com/bnema/zerobus"
9)
10
11// stream is one cast of a session: a PipeWire node fed with frames that the
12// wayland server renders. Everything here is touched by the owner goroutine
13// only; the PipeWire handler runs on it too, inside Loop.Iterate.
14type stream struct {
15 used bool
16 sess *session
17 id uint32 // number in the object path
18 cast uint64 // ID in the ports.CastCommands
19 path string
20
21 // Target.
22 output string
23 window ports.WindowID
24 cursor bool
25 maxFPS int
26 // Stream.Parameters: position and size in the compositor space.
27 px, py, pw, ph int32
28
29 // Lifecycle.
30 needOpen bool // CastOpen is yet to be sent
31 opened bool // CastOpen was sent
32 needClose bool // CastClose is yet to be sent
33 closing bool // the session ended: free once no frame is in flight
34 stopped bool // CastStopped came: the wayland side is done
35
36 // Negotiation.
37 w, h int // size of the target, from CastSize
38 haveSize bool
39 needResize bool
40 src videoSource
41 h2 streamHandler
42 pwState int32 // last state of the PipeWire stream; -2 before the first
43 seenState bool // a state above UNCONNECTED was seen
44 nodeID uint32
45 announced bool // PipeWireStreamAdded was sent
46 format pipewire.VideoFormat
47 fw, fh int // size the format was negotiated at; 0 while renegotiating
48
49 // Frames.
50 dirty bool
51 forced bool // the first frame after STREAMING is not paced by damage
52 inflight bool
53 buf *pipewire.VideoBuffer
54 lastReq time.Time
55 seq uint64
56 pts time.Duration
57 errs int // consecutive failed frames
58}
59
60// streamHandler is the pipewire.VideoHandler of a stream. Its callbacks run
61// inside Loop.Iterate: they only record, the pump acts.
62type streamHandler struct{ st *stream }
63
64func (h *streamHandler) StateChanged(state int32, nodeID uint32, _ string) {
65 st := h.st
66 st.pwState = state
67 if nodeID != 0 {
68 st.nodeID = nodeID
69 }
70 if state > pipewire.StreamStateUnconnected {
71 st.seenState = true
72 }
73 if state == pipewire.StreamStateStreaming {
74 st.forced = true
75 }
76}
77
78func (h *streamHandler) FormatChanged(f pipewire.VideoFormat, w, hh, _ int) {
79 h.st.format, h.st.fw, h.st.fh = f, w, hh
80 // A new format shows a new picture: the first frame is not paced by damage.
81 h.st.forced = true
82}
83
84// BuffersChanged: the next pump looks for a free buffer anyway.
85func (h *streamHandler) BuffersChanged() {}
86
87// close ends the stream on the wayland side; the slot is freed by reap once
88// no frame is in flight.
89func (st *stream) close() {
90 if st.closing {
91 return
92 }
93 st.closing = true
94 st.needOpen = false
95 st.needClose = st.opened && !st.stopped
96}
97
98// encodeParameters writes the a{sv} of Stream.Parameters.
99func encodeParameters(e *zerobus.Encoder, st *stream) {
100 a := e.BeginArray('{')
101 e.Struct()
102 e.Str("position")
103 e.Variant("(ii)")
104 e.Struct()
105 e.Int32(st.px)
106 e.Int32(st.py)
107 e.Struct()
108 e.Str("size")
109 e.Variant("(ii)")
110 e.Struct()
111 e.Int32(st.pw)
112 e.Int32(st.ph)
113 e.EndArray(a)
114}