forked from omriharel/deej
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsession_finder_linux.go
150 lines (115 loc) · 3.88 KB
/
session_finder_linux.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package deej
import (
"fmt"
"net"
"github.com/jfreymuth/pulse/proto"
"go.uber.org/zap"
)
type paSessionFinder struct {
logger *zap.SugaredLogger
sessionLogger *zap.SugaredLogger
client *proto.Client
conn net.Conn
}
func newSessionFinder(logger *zap.SugaredLogger) (SessionFinder, error) {
client, conn, err := proto.Connect("")
if err != nil {
logger.Warnw("Failed to establish PulseAudio connection", "error", err)
return nil, fmt.Errorf("establish PulseAudio connection: %w", err)
}
request := proto.SetClientName{
Props: proto.PropList{
"application.name": proto.PropListString("deej"),
},
}
reply := proto.SetClientNameReply{}
if err := client.Request(&request, &reply); err != nil {
return nil, err
}
sf := &paSessionFinder{
logger: logger.Named("session_finder"),
sessionLogger: logger.Named("sessions"),
client: client,
conn: conn,
}
sf.logger.Debug("Created PA session finder instance")
return sf, nil
}
func (sf *paSessionFinder) GetAllSessions() ([]Session, error) {
sessions := []Session{}
// get the master sink session
masterSink, err := sf.getMasterSinkSession()
if err == nil {
sessions = append(sessions, masterSink)
} else {
sf.logger.Warnw("Failed to get master audio sink session", "error", err)
}
// get the master source session
masterSource, err := sf.getMasterSourceSession()
if err == nil {
sessions = append(sessions, masterSource)
} else {
sf.logger.Warnw("Failed to get master audio source session", "error", err)
}
// enumerate sink inputs and add sessions along the way
if err := sf.enumerateAndAddSessions(&sessions); err != nil {
sf.logger.Warnw("Failed to enumerate audio sessions", "error", err)
return nil, fmt.Errorf("enumerate audio sessions: %w", err)
}
return sessions, nil
}
func (sf *paSessionFinder) Release() error {
if err := sf.conn.Close(); err != nil {
sf.logger.Warnw("Failed to close PulseAudio connection", "error", err)
return fmt.Errorf("close PulseAudio connection: %w", err)
}
sf.logger.Debug("Released PA session finder instance")
return nil
}
func (sf *paSessionFinder) getMasterSinkSession() (Session, error) {
request := proto.GetSinkInfo{
SinkIndex: proto.Undefined,
}
reply := proto.GetSinkInfoReply{}
if err := sf.client.Request(&request, &reply); err != nil {
sf.logger.Warnw("Failed to get master sink info", "error", err)
return nil, fmt.Errorf("get master sink info: %w", err)
}
// create the master sink session
sink := newMasterSession(sf.sessionLogger, sf.client, reply.SinkIndex, reply.Channels, true)
return sink, nil
}
func (sf *paSessionFinder) getMasterSourceSession() (Session, error) {
request := proto.GetSourceInfo{
SourceIndex: proto.Undefined,
}
reply := proto.GetSourceInfoReply{}
if err := sf.client.Request(&request, &reply); err != nil {
sf.logger.Warnw("Failed to get master source info", "error", err)
return nil, fmt.Errorf("get master source info: %w", err)
}
// create the master source session
source := newMasterSession(sf.sessionLogger, sf.client, reply.SourceIndex, reply.Channels, false)
return source, nil
}
func (sf *paSessionFinder) enumerateAndAddSessions(sessions *[]Session) error {
request := proto.GetSinkInputInfoList{}
reply := proto.GetSinkInputInfoListReply{}
if err := sf.client.Request(&request, &reply); err != nil {
sf.logger.Warnw("Failed to get sink input list", "error", err)
return fmt.Errorf("get sink input list: %w", err)
}
for _, info := range reply {
name, ok := info.Properties["application.process.binary"]
if !ok {
sf.logger.Warnw("Failed to get sink input's process name",
"sinkInputIndex", info.SinkInputIndex)
continue
}
// create the deej session object
newSession := newPASession(sf.sessionLogger, sf.client, info.SinkInputIndex, info.Channels, name.String())
// add it to our slice
*sessions = append(*sessions, newSession)
}
return nil
}