vibe-proxy/backend/internal/client/codex/live/media.go
2026-08-24 00:10:41 +02:00

887 lines
26 KiB
Go

package live
import (
"context"
"errors"
"fmt"
"io"
"net"
"strings"
"sync"
"github.com/google/uuid"
"github.com/pion/interceptor"
"github.com/pion/rtp"
"github.com/pion/webrtc/v4"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/proxyutil"
log "github.com/sirupsen/logrus"
"golang.org/x/net/proxy"
)
const (
realtimeDataChannelLabel = "oai-events"
mediaDataQueueSize = 64
mediaDataMessageMaxSize = 256 << 10
mediaDataBufferedMaxSize = 1 << 20
)
var opusCodec = webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeOpus,
ClockRate: 48000,
Channels: 2,
SDPFmtpLine: "minptime=10;useinbandfec=1",
}
type mediaRelaySession interface {
AcceptUpstreamAnswer(context.Context, string) (string, error)
SetCallID(string)
SetCloseHandler(func(string))
Close() error
CloseWithReason(string) error
}
type mediaRelayFactory interface {
NewSession(context.Context, string, mediaSessionRoute) (mediaRelaySession, string, error)
}
type mediaSessionRoute struct {
proxyURL string
credential string
authIndex string
}
type pionMediaRelay struct {
downstreamAPI *webrtc.API
upstreamAPI *webrtc.API
proxyUpstreamAPI *webrtc.API
configuration webrtc.Configuration
limiter *mediaSessionLimiter
}
type mediaSessionLimiter struct {
mu sync.Mutex
limit int
active int
}
type pionMediaSession struct {
downstream *webrtc.PeerConnection
upstream *webrtc.PeerConnection
bridge *dataChannelBridge
done chan struct{}
closeOnce sync.Once
closeErr error
failureOnce sync.Once
handlerMu sync.Mutex
onClose func(string)
failureReason string
handlerCalled bool
mediaSessionID string
callID string
releaseSlot func()
proxyDialer proxy.ContextDialer
proxyScheme string
credential string
authIndex string
forwardingLogOnce sync.Once
localOffer string
tunnelsMu sync.Mutex
tunnels []*tcpCandidateTunnel
}
type dataChannelMessage struct {
data []byte
isString bool
}
type dataChannelPipe struct {
name string
done <-chan struct{}
queue chan dataChannelMessage
ready chan struct{}
readyOnce sync.Once
writable chan struct{}
destination *webrtc.DataChannel
mu sync.RWMutex
onError func(error)
}
type dataChannelBridge struct {
done <-chan struct{}
downToUp *dataChannelPipe
upToDown *dataChannelPipe
closeOnce sync.Once
downstreamMu sync.Mutex
downstream *webrtc.DataChannel
upstreamMu sync.Mutex
upstream *webrtc.DataChannel
}
func newPionMediaRelay(relayConfig config.CodexLiveMediaRelayConfig) (*pionMediaRelay, error) {
return newPionMediaRelayWithLimiter(relayConfig, &mediaSessionLimiter{})
}
func newPionMediaRelayWithLimiter(relayConfig config.CodexLiveMediaRelayConfig, limiter *mediaSessionLimiter) (*pionMediaRelay, error) {
if errValidate := relayConfig.Validate(); errValidate != nil {
return nil, errValidate
}
downstreamAPI, errAPI := newPionAPI(relayConfig, relayConfig.DisablePrivateRemoteIPs)
if errAPI != nil {
return nil, errAPI
}
upstreamAPI, errAPI := newPionAPI(relayConfig, false)
if errAPI != nil {
return nil, errAPI
}
proxyUpstreamAPI, errAPI := newPionProxyAPI(relayConfig)
if errAPI != nil {
return nil, errAPI
}
iceServers := make([]webrtc.ICEServer, 0, len(relayConfig.ICEServers))
for _, server := range relayConfig.ICEServers {
urls := make([]string, 0, len(server.URLs))
for _, rawURL := range server.URLs {
urls = append(urls, strings.TrimSpace(rawURL))
}
iceServers = append(iceServers, webrtc.ICEServer{
URLs: urls,
Username: server.Username,
Credential: server.Credential,
CredentialType: webrtc.ICECredentialTypePassword,
})
}
if limiter == nil {
limiter = &mediaSessionLimiter{}
}
limiter.setLimit(relayConfig.EffectiveMaxSessions())
return &pionMediaRelay{
downstreamAPI: downstreamAPI,
upstreamAPI: upstreamAPI,
proxyUpstreamAPI: proxyUpstreamAPI,
configuration: webrtc.Configuration{ICEServers: iceServers},
limiter: limiter,
}, nil
}
func (l *mediaSessionLimiter) setLimit(limit int) {
if l == nil {
return
}
l.mu.Lock()
l.limit = limit
l.mu.Unlock()
}
func (l *mediaSessionLimiter) acquire() bool {
if l == nil {
return false
}
l.mu.Lock()
defer l.mu.Unlock()
if l.limit <= 0 || l.active >= l.limit {
return false
}
l.active++
return true
}
func (l *mediaSessionLimiter) release() {
if l == nil {
return
}
l.mu.Lock()
if l.active > 0 {
l.active--
}
l.mu.Unlock()
}
func newPionAPI(relayConfig config.CodexLiveMediaRelayConfig, filterPrivateRemoteIPs bool) (*webrtc.API, error) {
return newPionAPIWithOptions(relayConfig, filterPrivateRemoteIPs, false)
}
func newPionProxyAPI(relayConfig config.CodexLiveMediaRelayConfig) (*webrtc.API, error) {
return newPionAPIWithOptions(relayConfig, false, true)
}
func newPionAPIWithOptions(relayConfig config.CodexLiveMediaRelayConfig, filterPrivateRemoteIPs, loopbackOnly bool) (*webrtc.API, error) {
mediaEngine := &webrtc.MediaEngine{}
if errRegister := mediaEngine.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: opusCodec,
PayloadType: 111,
}, webrtc.RTPCodecTypeAudio); errRegister != nil {
return nil, fmt.Errorf("register Opus codec: %w", errRegister)
}
interceptorRegistry := &interceptor.Registry{}
if errRegister := webrtc.RegisterDefaultInterceptors(mediaEngine, interceptorRegistry); errRegister != nil {
return nil, fmt.Errorf("register WebRTC interceptors: %w", errRegister)
}
settingEngine := webrtc.SettingEngine{}
if !loopbackOnly {
if relayConfig.UDPPortMin != 0 {
if errPorts := settingEngine.SetEphemeralUDPPortRange(relayConfig.UDPPortMin, relayConfig.UDPPortMax); errPorts != nil {
return nil, fmt.Errorf("configure WebRTC UDP port range: %w", errPorts)
}
}
if publicIP := strings.TrimSpace(relayConfig.PublicIP); publicIP != "" {
settingEngine.SetNAT1To1IPs([]string{publicIP}, webrtc.ICECandidateTypeHost)
}
}
if filterPrivateRemoteIPs {
settingEngine.SetRemoteIPFilter(isPublicRemoteIP)
}
if loopbackOnly {
settingEngine.SetNetworkTypes([]webrtc.NetworkType{
webrtc.NetworkTypeUDP4,
webrtc.NetworkTypeUDP6,
webrtc.NetworkTypeTCP4,
webrtc.NetworkTypeTCP6,
})
settingEngine.SetIncludeLoopbackCandidate(true)
settingEngine.SetIPFilter(func(ip net.IP) bool {
return ip != nil && ip.IsLoopback()
})
}
return webrtc.NewAPI(
webrtc.WithMediaEngine(mediaEngine),
webrtc.WithInterceptorRegistry(interceptorRegistry),
webrtc.WithSettingEngine(settingEngine),
), nil
}
func isPublicRemoteIP(ip net.IP) bool {
return ip != nil && !ip.IsUnspecified() && !ip.IsLoopback() && !ip.IsPrivate() &&
!ip.IsLinkLocalUnicast() && !ip.IsLinkLocalMulticast() && !ip.IsMulticast()
}
func (r *pionMediaRelay) NewSession(ctx context.Context, clientOffer string, route mediaSessionRoute) (mediaRelaySession, string, error) {
if r == nil || r.downstreamAPI == nil || r.upstreamAPI == nil || r.proxyUpstreamAPI == nil || r.limiter == nil {
return nil, "", errors.New("Codex live media relay unavailable")
}
if errContext := ctx.Err(); errContext != nil {
return nil, "", errContext
}
builtProxyDialer, proxyMode, errProxy := proxyutil.BuildDialer(route.proxyURL)
if errProxy != nil {
return nil, "", fmt.Errorf("configure Codex live remote TCP proxy: %w", errProxy)
}
proxied := proxyMode == proxyutil.ModeProxy
var proxyDialer proxy.ContextDialer
if proxied {
contextDialer, ok := builtProxyDialer.(proxy.ContextDialer)
if !ok {
return nil, "", errors.New("Codex live remote TCP proxy does not support cancellation")
}
proxyDialer = contextDialer
}
if !r.limiter.acquire() {
return nil, "", errors.New("Codex live media relay capacity exhausted")
}
releaseSlot := r.limiter.release
downstream, errDownstream := r.downstreamAPI.NewPeerConnection(r.configuration)
if errDownstream != nil {
releaseSlot()
return nil, "", fmt.Errorf("create downstream PeerConnection: %w", errDownstream)
}
upstreamAPI := r.upstreamAPI
upstreamConfiguration := r.configuration
if proxied {
upstreamAPI = r.proxyUpstreamAPI
upstreamConfiguration.ICEServers = nil
}
upstream, errUpstream := upstreamAPI.NewPeerConnection(upstreamConfiguration)
if errUpstream != nil {
releaseSlot()
if errClose := downstream.Close(); errClose != nil {
log.WithError(errClose).Debug("codex live media: close downstream PeerConnection after setup error")
}
return nil, "", fmt.Errorf("create upstream PeerConnection: %w", errUpstream)
}
session := &pionMediaSession{
downstream: downstream,
upstream: upstream,
done: make(chan struct{}),
mediaSessionID: uuid.NewString(),
releaseSlot: releaseSlot,
proxyDialer: proxyDialer,
proxyScheme: proxyScheme(route.proxyURL),
credential: strings.TrimSpace(route.credential),
authIndex: strings.TrimSpace(route.authIndex),
}
session.bridge = newDataChannelBridge(session.done, func(err error) {
session.fail("data_channel_failed", err)
})
session.installStateHandlers()
log.WithFields(session.logFields("session")).Info("codex live WebRTC media session created")
if errRemote := downstream.SetRemoteDescription(webrtc.SessionDescription{
Type: webrtc.SDPTypeOffer,
SDP: clientOffer,
}); errRemote != nil {
_ = session.Close()
return nil, "", fmt.Errorf("set downstream WebRTC offer: %w", errRemote)
}
toDesktop, errTrack := webrtc.NewTrackLocalStaticRTP(opusCodec, "audio", "codex-live")
if errTrack != nil {
_ = session.Close()
return nil, "", fmt.Errorf("create downstream audio track: %w", errTrack)
}
downstreamSender, errTrack := downstream.AddTrack(toDesktop)
if errTrack != nil {
_ = session.Close()
return nil, "", fmt.Errorf("add downstream audio track: %w", errTrack)
}
go drainRTCP("downstream", downstreamSender, session.done)
toOpenAI, errTrack := webrtc.NewTrackLocalStaticRTP(opusCodec, "audio", "codex-live")
if errTrack != nil {
_ = session.Close()
return nil, "", fmt.Errorf("create upstream audio track: %w", errTrack)
}
upstreamSender, errTrack := upstream.AddTrack(toOpenAI)
if errTrack != nil {
_ = session.Close()
return nil, "", fmt.Errorf("add upstream audio track: %w", errTrack)
}
go drainRTCP("upstream", upstreamSender, session.done)
downstream.OnTrack(func(track *webrtc.TrackRemote, _ *webrtc.RTPReceiver) {
if !strings.EqualFold(track.Codec().MimeType, webrtc.MimeTypeOpus) {
return
}
go relayRTP("downstream-to-upstream", track, toOpenAI, session.done)
})
upstream.OnTrack(func(track *webrtc.TrackRemote, _ *webrtc.RTPReceiver) {
if !strings.EqualFold(track.Codec().MimeType, webrtc.MimeTypeOpus) {
return
}
go relayRTP("upstream-to-downstream", track, toDesktop, session.done)
})
downstream.OnDataChannel(func(channel *webrtc.DataChannel) {
if channel.Label() != realtimeDataChannelLabel {
if errClose := channel.Close(); errClose != nil {
log.WithError(errClose).Debug("codex live media: close unsupported downstream DataChannel")
}
return
}
session.bridge.attachDownstream(channel)
})
upstreamChannel, errChannel := upstream.CreateDataChannel(realtimeDataChannelLabel, nil)
if errChannel != nil {
_ = session.Close()
return nil, "", fmt.Errorf("create upstream DataChannel: %w", errChannel)
}
session.bridge.attachUpstream(upstreamChannel)
gatherComplete := webrtc.GatheringCompletePromise(upstream)
offer, errOffer := upstream.CreateOffer(nil)
if errOffer != nil {
_ = session.Close()
return nil, "", fmt.Errorf("create upstream WebRTC offer: %w", errOffer)
}
if errLocal := upstream.SetLocalDescription(offer); errLocal != nil {
_ = session.Close()
return nil, "", fmt.Errorf("set upstream WebRTC offer: %w", errLocal)
}
select {
case <-gatherComplete:
case <-ctx.Done():
_ = session.Close()
return nil, "", fmt.Errorf("gather upstream WebRTC candidates: %w", ctx.Err())
}
localDescription := upstream.LocalDescription()
if localDescription == nil || strings.TrimSpace(localDescription.SDP) == "" {
_ = session.Close()
return nil, "", errors.New("upstream WebRTC offer is empty")
}
session.localOffer = localDescription.SDP
return session, localDescription.SDP, nil
}
func (s *pionMediaSession) AcceptUpstreamAnswer(ctx context.Context, upstreamAnswer string) (string, error) {
if s == nil || s.upstream == nil || s.downstream == nil {
return "", errors.New("Codex live media session unavailable")
}
answerToApply := upstreamAnswer
if s.proxyDialer != nil {
rewrittenAnswer, tunnels, errProxy := prepareProxiedUpstreamAnswer(upstreamAnswer, s.localOffer, s.proxyDialer)
if errProxy != nil {
return "", errProxy
}
for _, tunnel := range tunnels {
tunnel.setForwardingStartedHandler(s.logForwardingStarted)
}
if !s.installCandidateTunnels(tunnels) {
errClosed := errors.New("Codex live media session closed while configuring TCP proxy")
if errClose := closeCandidateTunnels(tunnels); errClose != nil {
return "", errors.Join(errClosed, fmt.Errorf("close TCP candidate tunnels: %w", errClose))
}
return "", errClosed
}
answerToApply = rewrittenAnswer
}
if errRemote := s.upstream.SetRemoteDescription(webrtc.SessionDescription{
Type: webrtc.SDPTypeAnswer,
SDP: answerToApply,
}); errRemote != nil {
errSetRemote := fmt.Errorf("set upstream WebRTC answer: %w", errRemote)
if errClose := s.closeCandidateTunnels(); errClose != nil {
return "", errors.Join(errSetRemote, fmt.Errorf("close TCP candidate tunnels: %w", errClose))
}
return "", errSetRemote
}
gatherComplete := webrtc.GatheringCompletePromise(s.downstream)
answer, errAnswer := s.downstream.CreateAnswer(nil)
if errAnswer != nil {
return "", fmt.Errorf("create downstream WebRTC answer: %w", errAnswer)
}
if errLocal := s.downstream.SetLocalDescription(answer); errLocal != nil {
return "", fmt.Errorf("set downstream WebRTC answer: %w", errLocal)
}
select {
case <-gatherComplete:
case <-ctx.Done():
return "", fmt.Errorf("gather downstream WebRTC candidates: %w", ctx.Err())
}
localDescription := s.downstream.LocalDescription()
if localDescription == nil || strings.TrimSpace(localDescription.SDP) == "" {
return "", errors.New("downstream WebRTC answer is empty")
}
return localDescription.SDP, nil
}
func (s *pionMediaSession) installCandidateTunnels(tunnels []*tcpCandidateTunnel) bool {
if s == nil {
return false
}
s.tunnelsMu.Lock()
defer s.tunnelsMu.Unlock()
select {
case <-s.done:
return false
default:
}
s.tunnels = tunnels
return true
}
func (s *pionMediaSession) closeCandidateTunnels() error {
if s == nil {
return nil
}
s.tunnelsMu.Lock()
tunnels := s.tunnels
s.tunnels = nil
s.tunnelsMu.Unlock()
return closeCandidateTunnels(tunnels)
}
func (s *pionMediaSession) SetCallID(callID string) {
if s == nil {
return
}
s.handlerMu.Lock()
s.callID = strings.TrimSpace(callID)
s.handlerMu.Unlock()
}
func (s *pionMediaSession) logFields(peer string) log.Fields {
fields := log.Fields{
"media_session_id": s.mediaSessionID,
"peer": peer,
}
s.handlerMu.Lock()
callID := s.callID
s.handlerMu.Unlock()
if callID != "" {
fields["call_id"] = callID
}
if s.proxyDialer != nil && (peer == "remote" || peer == "session") {
fields["remote_transport"] = "tcp"
fields["proxy_scheme"] = s.proxyScheme
}
return fields
}
func (s *pionMediaSession) forwardingLogFields() log.Fields {
fields := s.logFields("remote")
if s.authIndex != "" {
fields["auth_index"] = s.authIndex
}
if s.credential != "" {
fields["credential"] = s.credential
}
if s.proxyDialer != nil {
fields["connection"] = "via " + s.proxyScheme + " proxy"
fields["remote_transport"] = "tcp"
} else {
fields["connection"] = "direct"
fields["remote_transport"] = "ice"
}
if s.upstream != nil {
fields["state"] = s.upstream.ConnectionState().String()
}
return fields
}
func (s *pionMediaSession) logForwardingStarted() {
if s == nil {
return
}
s.forwardingLogOnce.Do(func() {
log.WithFields(s.forwardingLogFields()).Info("codex live remote media forwarding started")
})
}
func (s *pionMediaSession) SetCloseHandler(handler func(string)) {
if s == nil {
return
}
s.handlerMu.Lock()
s.onClose = handler
reason := s.failureReason
callHandler := handler != nil && reason != "" && !s.handlerCalled
if callHandler {
s.handlerCalled = true
}
s.handlerMu.Unlock()
if callHandler {
handler(reason)
}
}
func (s *pionMediaSession) Close() error {
return s.CloseWithReason("closed")
}
func (s *pionMediaSession) CloseWithReason(reason string) error {
if s == nil {
return nil
}
s.closeOnce.Do(func() {
fields := s.logFields("session")
fields["reason"] = reason
log.WithFields(fields).Info("codex live WebRTC media session closing")
close(s.done)
if s.bridge != nil {
s.bridge.close()
}
var closeErrors []error
if errClose := s.closeCandidateTunnels(); errClose != nil {
closeErrors = append(closeErrors, fmt.Errorf("close TCP candidate tunnels: %w", errClose))
}
if errClose := s.closePeerConnection("local", s.downstream); errClose != nil {
closeErrors = append(closeErrors, fmt.Errorf("close downstream PeerConnection: %w", errClose))
}
if errClose := s.closePeerConnection("remote", s.upstream); errClose != nil {
closeErrors = append(closeErrors, fmt.Errorf("close upstream PeerConnection: %w", errClose))
}
if s.releaseSlot != nil {
s.releaseSlot()
}
s.closeErr = errors.Join(closeErrors...)
if s.closeErr != nil {
log.WithFields(fields).WithError(s.closeErr).Warn("codex live WebRTC media session closed with errors")
} else {
log.WithFields(fields).Info("codex live WebRTC media session closed")
}
})
return s.closeErr
}
func (s *pionMediaSession) closePeerConnection(peer string, connection *webrtc.PeerConnection) error {
if connection == nil {
return nil
}
fields := s.logFields(peer)
fields["state_before"] = connection.ConnectionState().String()
errClose := connection.Close()
fields["state_after"] = connection.ConnectionState().String()
if errClose != nil {
log.WithFields(fields).WithError(errClose).Warn("codex live WebRTC peer close failed")
return errClose
}
log.WithFields(fields).Info("codex live WebRTC peer closed")
return nil
}
func (s *pionMediaSession) installStateHandlers() {
handle := func(peer, reasonPrefix string) func(webrtc.PeerConnectionState) {
return func(state webrtc.PeerConnectionState) {
fields := s.logFields(peer)
fields["state"] = state.String()
switch state {
case webrtc.PeerConnectionStateConnecting:
log.WithFields(fields).Info("codex live WebRTC peer connecting")
case webrtc.PeerConnectionStateConnected:
log.WithFields(fields).Info("codex live WebRTC peer connected")
if peer == "remote" {
s.logForwardingStarted()
}
case webrtc.PeerConnectionStateDisconnected:
log.WithFields(fields).Warn("codex live WebRTC peer disconnected")
case webrtc.PeerConnectionStateFailed:
log.WithFields(fields).Warn("codex live WebRTC peer failed")
s.fail(reasonPrefix+"_failed", fmt.Errorf("%s PeerConnection failed", reasonPrefix))
case webrtc.PeerConnectionStateClosed:
select {
case <-s.done:
return
default:
log.WithFields(fields).Info("codex live WebRTC peer closed by remote")
s.fail(reasonPrefix+"_closed", fmt.Errorf("%s PeerConnection closed", reasonPrefix))
}
default:
log.WithFields(fields).Debug("codex live WebRTC peer state changed")
}
}
}
s.downstream.OnConnectionStateChange(handle("local", "downstream"))
s.upstream.OnConnectionStateChange(handle("remote", "upstream"))
}
func (s *pionMediaSession) fail(reason string, err error) {
s.failureOnce.Do(func() {
if err != nil {
log.WithFields(s.logFields("session")).WithField("reason", reason).WithError(err).Warn("codex live WebRTC media session failed")
}
if errClose := s.CloseWithReason(reason); errClose != nil {
log.WithError(errClose).Debug("codex live media: close failed session")
}
s.handlerMu.Lock()
s.failureReason = reason
handler := s.onClose
callHandler := handler != nil && !s.handlerCalled
if callHandler {
s.handlerCalled = true
}
s.handlerMu.Unlock()
if callHandler {
handler(reason)
}
})
}
func relayRTP(name string, source *webrtc.TrackRemote, destination *webrtc.TrackLocalStaticRTP, done <-chan struct{}) {
for {
packet, _, errRead := source.ReadRTP()
if errRead != nil {
if !isClosedMediaError(errRead, done) {
log.WithError(errRead).Debugf("codex live media: %s RTP read stopped", name)
}
return
}
normalizeRTPPacket(packet)
if errWrite := destination.WriteRTP(packet); errWrite != nil {
if !isClosedMediaError(errWrite, done) {
log.WithError(errWrite).Debugf("codex live media: %s RTP write stopped", name)
}
return
}
}
}
func normalizeRTPPacket(packet *rtp.Packet) {
if packet == nil {
return
}
packet.Extension = false
packet.ExtensionProfile = 0
packet.Extensions = nil
}
func drainRTCP(name string, sender *webrtc.RTPSender, done <-chan struct{}) {
for {
if _, _, errRead := sender.ReadRTCP(); errRead != nil {
if !isClosedMediaError(errRead, done) {
log.WithError(errRead).Debugf("codex live media: %s RTCP reader stopped", name)
}
return
}
}
}
func isClosedMediaError(err error, done <-chan struct{}) bool {
select {
case <-done:
return true
default:
}
return errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed)
}
func newDataChannelBridge(done <-chan struct{}, onError func(error)) *dataChannelBridge {
bridge := &dataChannelBridge{done: done}
bridge.downToUp = newDataChannelPipe("downstream-to-upstream", done, onError)
bridge.upToDown = newDataChannelPipe("upstream-to-downstream", done, onError)
return bridge
}
func newDataChannelPipe(name string, done <-chan struct{}, onError func(error)) *dataChannelPipe {
pipe := &dataChannelPipe{
name: name,
done: done,
queue: make(chan dataChannelMessage, mediaDataQueueSize),
ready: make(chan struct{}),
writable: make(chan struct{}, 1),
onError: onError,
}
go pipe.run()
return pipe
}
func (b *dataChannelBridge) attachDownstream(channel *webrtc.DataChannel) {
b.downstreamMu.Lock()
if b.downstream != nil {
b.downstreamMu.Unlock()
if errClose := channel.Close(); errClose != nil {
log.WithError(errClose).Debug("codex live media: close duplicate downstream DataChannel")
}
return
}
b.downstream = channel
b.downstreamMu.Unlock()
b.upToDown.setDestination(channel)
b.bindSource(channel, b.downToUp)
}
func (b *dataChannelBridge) attachUpstream(channel *webrtc.DataChannel) {
b.upstreamMu.Lock()
if b.upstream != nil {
b.upstreamMu.Unlock()
if errClose := channel.Close(); errClose != nil {
log.WithError(errClose).Debug("codex live media: close duplicate upstream DataChannel")
}
return
}
b.upstream = channel
b.upstreamMu.Unlock()
b.downToUp.setDestination(channel)
b.bindSource(channel, b.upToDown)
}
func (b *dataChannelBridge) bindSource(channel *webrtc.DataChannel, destination *dataChannelPipe) {
channel.OnMessage(func(message webrtc.DataChannelMessage) {
if len(message.Data) > mediaDataMessageMaxSize {
destination.reportError(fmt.Errorf("%s DataChannel message exceeds %d bytes", destination.name, mediaDataMessageMaxSize))
return
}
payload := append([]byte(nil), message.Data...)
select {
case destination.queue <- dataChannelMessage{data: payload, isString: message.IsString}:
case <-b.done:
}
})
channel.OnError(func(err error) {
destination.reportError(fmt.Errorf("%s DataChannel error: %w", destination.name, err))
})
channel.OnClose(func() {
select {
case <-b.done:
return
default:
destination.reportError(fmt.Errorf("%s DataChannel closed", destination.name))
}
})
}
func (b *dataChannelBridge) close() {
if b == nil {
return
}
b.closeOnce.Do(func() {
b.downstreamMu.Lock()
downstream := b.downstream
b.downstreamMu.Unlock()
if downstream != nil {
if errClose := downstream.Close(); errClose != nil {
log.WithError(errClose).Debug("codex live media: close downstream DataChannel")
}
}
b.upstreamMu.Lock()
upstream := b.upstream
b.upstreamMu.Unlock()
if upstream != nil {
if errClose := upstream.Close(); errClose != nil {
log.WithError(errClose).Debug("codex live media: close upstream DataChannel")
}
}
})
}
func (p *dataChannelPipe) setDestination(channel *webrtc.DataChannel) {
p.mu.Lock()
p.destination = channel
p.mu.Unlock()
markReady := func() {
p.readyOnce.Do(func() { close(p.ready) })
}
channel.SetBufferedAmountLowThreshold(mediaDataBufferedMaxSize / 2)
channel.OnBufferedAmountLow(func() {
select {
case p.writable <- struct{}{}:
default:
}
})
channel.OnOpen(markReady)
if channel.ReadyState() == webrtc.DataChannelStateOpen {
markReady()
}
}
func (p *dataChannelPipe) run() {
select {
case <-p.ready:
case <-p.done:
return
}
for {
select {
case message := <-p.queue:
p.mu.RLock()
destination := p.destination
p.mu.RUnlock()
if destination == nil {
p.reportError(fmt.Errorf("%s DataChannel destination unavailable", p.name))
return
}
if !p.waitWritable(destination, len(message.data)) {
return
}
var errSend error
if message.isString {
errSend = destination.SendText(string(message.data))
} else {
errSend = destination.Send(message.data)
}
if errSend != nil {
p.reportError(fmt.Errorf("send %s DataChannel message: %w", p.name, errSend))
return
}
case <-p.done:
return
}
}
}
func (p *dataChannelPipe) waitWritable(destination *webrtc.DataChannel, messageSize int) bool {
for destination.BufferedAmount()+uint64(messageSize) > mediaDataBufferedMaxSize {
select {
case <-p.writable:
case <-p.done:
return false
}
}
return true
}
func (p *dataChannelPipe) reportError(err error) {
if p.onError != nil {
p.onError(err)
}
}