mirror of
https://dev.narayana.im/narayana/telegabber.git
synced 2026-08-05 12:17:06 +00:00
97 lines
2.6 KiB
Go
97 lines
2.6 KiB
Go
// adapts *ntgcalls.Client to audio.NtgClient; sub-package so audio/ stays
|
|
// buildable without libntgcalls/CGO locally
|
|
package ntgadapter
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"dev.narayana.im/narayana/telegabber/calls/audio"
|
|
"gotgcalls/ntgcalls"
|
|
)
|
|
|
|
// wraps *ntgcalls.Client and filters OnFrame to (Playback, Microphone) -
|
|
// the bridge only wants the peer's audio coming back via the external mic
|
|
type Adapter struct {
|
|
c *ntgcalls.Client
|
|
|
|
mu sync.RWMutex
|
|
handlers map[uint64]audio.NtgFrameHandler
|
|
nextID uint64
|
|
wired bool
|
|
}
|
|
|
|
func New(c *ntgcalls.Client) *Adapter {
|
|
return &Adapter{c: c, handlers: make(map[uint64]audio.NtgFrameHandler)}
|
|
}
|
|
|
|
// lazily wires the ntgcalls callback on first call; cancel drops from
|
|
// the adapter-owned dispatch table (C++ binding's OnFrame is append-only)
|
|
func (a *Adapter) OnFrame(h audio.NtgFrameHandler) func() {
|
|
a.mu.Lock()
|
|
id := a.nextID
|
|
a.nextID++
|
|
a.handlers[id] = h
|
|
wire := !a.wired
|
|
a.wired = true
|
|
a.mu.Unlock()
|
|
|
|
if wire {
|
|
a.c.OnFrame(func(chatID int64, mode ntgcalls.StreamMode, device ntgcalls.StreamDevice, frames []ntgcalls.Frame) {
|
|
if mode != ntgcalls.PlaybackStream || device != ntgcalls.MicrophoneStream {
|
|
return
|
|
}
|
|
pcm := make([]audio.PCMFrame, 0, len(frames))
|
|
for _, f := range frames {
|
|
pcm = append(pcm, audio.PCMFrame{SSRC: f.Ssrc, Data: f.Data})
|
|
}
|
|
a.mu.RLock()
|
|
hs := make([]audio.NtgFrameHandler, 0, len(a.handlers))
|
|
for _, h := range a.handlers {
|
|
hs = append(hs, h)
|
|
}
|
|
a.mu.RUnlock()
|
|
for _, h := range hs {
|
|
h(chatID, pcm)
|
|
}
|
|
})
|
|
}
|
|
|
|
return func() {
|
|
a.mu.Lock()
|
|
delete(a.handlers, id)
|
|
a.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
// uses the Microphone device slot in both directions (ntgcalls P2P pattern)
|
|
func (a *Adapter) SetExternalMicrophone(chatID int64, capture bool, sampleRate uint32, channels uint8) error {
|
|
desc := ntgcalls.MediaDescription{
|
|
Microphone: &ntgcalls.AudioDescription{
|
|
MediaSource: ntgcalls.MediaSourceExternal,
|
|
SampleRate: sampleRate,
|
|
ChannelCount: channels,
|
|
},
|
|
}
|
|
mode := ntgcalls.PlaybackStream
|
|
if capture {
|
|
mode = ntgcalls.CaptureStream
|
|
}
|
|
return a.c.SetStreamSources(chatID, mode, desc)
|
|
}
|
|
|
|
// errors only if both clears fail; partial cleanup is best-effort
|
|
func (a *Adapter) ClearStreams(chatID int64) error {
|
|
empty := ntgcalls.MediaDescription{}
|
|
capErr := a.c.SetStreamSources(chatID, ntgcalls.CaptureStream, empty)
|
|
pbErr := a.c.SetStreamSources(chatID, ntgcalls.PlaybackStream, empty)
|
|
if capErr != nil && pbErr != nil {
|
|
return capErr
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *Adapter) SendMicrophonePCM(chatID int64, pcm []byte) error {
|
|
return a.c.SendExternalFrame(chatID, ntgcalls.MicrophoneStream, pcm, ntgcalls.FrameData{})
|
|
}
|
|
|
|
var _ audio.NtgClient = (*Adapter)(nil)
|