diago

package module
v0.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 14, 2026 License: MPL-2.0 Imports: 31 Imported by: 0

README

DIAGO

Go Report Card Coverage GitHub go.mod Go version

Short of dialog + GO.
Library for building VOIP solutions in GO!

Built on top of optimized SIPgo library!
In short it allows developing fast and easy testable VOIP apps to handle calls, registrations and more... It is offering High Level APIs to make telephony easier while you can go low and control every packet if needed.

Media state ownership and dialog lifecycle contracts are documented in docs/contracts.md.

Diago is mainly project driven lib, so lot of API design will/should be challenged with real working apps needs

For more information and documentation visit the website

Quick links:

Important media API change

Answer and Invite now return the media stack. Keep and use the returned media object for media operations instead of accessing media through the dialog session.

Everyone is encouraged to try the media-webrtc-pion branch and test the new returned-media API.

See WEBRTC_PION_CHANGES.md for the API migration details. WebRTC is not the primary focus yet; this work will also bring the Pion media stack into Diago.

Media security (SDES / DTLS-SRTP)

MediaConfig.SecureRTP selects plain RTP (0), SDES (1) or DTLS-SRTP (2). DTLS-SRTP verifies the peer certificate against every a=fingerprint in the remote SDP (RFC 5763) and fails closed on mismatch: an offer/answer that does not match the presented certificate aborts the media instead of being silently accepted (behavior change after v0.0.5).

When diago acts as the DTLS server (it sent the offer and the remote answered setup:active — the usual UAC path), client certificates are requested automatically whenever the remote SDP carries a=fingerprint (RFC 5763 §5). media.ServerClientAuthRequireCert maps to RequireAnyClientCert. SDP without any a=fingerprint is rejected for DTLS-SRTP instead of skipping verification. The secure-media contract is documented in docs/contracts.md §14.

If you find this project useful and you want to support/sponzor or need help with your projects, you can contact me more on mail.

Follow me on X/Twitter for regular updates

Tools/Service developed with diago:

RFCS

SIP: RFC 3261|RFC3581|RFC6026

More refer to lib github.com/emiago/sipgo Full dialog control (client/server), Registering, Authentication ...

Digest authentication (server-side INVITE challenge/validate): RFC 2617

SDP: RFC8866.

Parsing + Auto Generating for media session/audio

RTP/AVP: RFC3550

RTP Packetizers, Media Forking, RTP Session control, RTCP Sender/Receiver reports, RTCP statistics tracking, DTMF reader/writer ...

REFER:

NOTE: For specifics and questions what is covered by RFC, please open Issue. Lot of functionality can be extended even if not built in library.

Contributions

Please avoid following:

  • Creating BIG PR that creates feature or lot of refactoring without previously having Issue. Issue should explain problem or requirements you want to accomplish.
  • Creating Change Log or some other textual files that add some sort of documentation. This is why we need Issue and ID of issue should be in your commit. If you want(think it is good) to have this better documented(like webpage or readme) pls open issue.
  • English is main language for code and for comments. Any other language used PRs will be rejected.

Usage

Checkout more on Getting started, but for quick view here is echotest (hello world) example.

ua, _ := sipgo.NewUA()
dg := diago.NewDiago(ua)

dg.Serve(ctx, func(inDialog *diago.DialogServerSession) {
	inDialog.Trying() // 100 Trying
	inDialog.Answer(); // Answer - 200 OK SDP

	// Make sure file below exists in work dir
	playfile, err := os.Open("demo-echotest.wav")
	if err != nil {
		fmt.Println("Failed to open file", err)
		return
	}
	defer playfile.Close()

	// Create playback and play file.
	pb, _ := inDialog.CreatePlayback()
	if err := pb.Play(playfile, "audio/wav"); err != nil {
		fmt.Println("Playing failed", err)
	}
}

See more examples in this repo

Tracing SIP, RTP

While openning issue, consider having some traces enabled.

sip.SIPDebug = true // Enables SIP tracing
media.RTCPDebug = true // Enables RTCP tracing
media.RTPDebug = true // Enables RTP tracing. NOTE: It will dump every RTP Packet

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	HTTPDebug = os.Getenv("HTTP_DEBUG") == "true"

	DefaultPlaybackHTTPClient = http.Client{
		Timeout: 20 * time.Second,
	}
)
View Source
var (
	ErrDigestAuthNoChallenge = errors.New("no challenge")
	ErrDigestAuthBadCreds    = errors.New("bad credentials")
)
View Source
var (
	// ErrDialogNotAnswered is returned when an operation requires an answered
	// dialog with negotiated media, but no active media session exists yet
	// (dialog not answered) or no invite response was received.
	ErrDialogNotAnswered = errors.New("dialog session not answered")

	// ErrDialogClosed is returned by media operations after the dialog media
	// was closed locally with Close (or the dialog ended and the framework
	// closed it).
	ErrDialogClosed = errors.New("dialog media closed")

	// ErrDTMFUnsupported is returned by SendDTMF when the peer did not
	// negotiate telephone-event and the selected method has no fallback.
	ErrDTMFUnsupported = errors.New("dtmf: telephone-event not negotiated")

	// ErrMusicOnHoldActive is returned by PlayMusicOnHold when a hold-music
	// loop is already running on the dialog (one loop per dialog).
	ErrMusicOnHoldActive = errors.New("music on hold already active")

	// ErrMusicOnHoldNoTone is returned by PlayMusicOnHold when neither the
	// WithMoHTone option nor the dialog MediaConfig provides a hold music tone.
	ErrMusicOnHoldNoTone = errors.New("music on hold: no tone configured")
)

Lifecycle sentinels for dialog media operations. All media entry points (audio readers/writers, playback factories, Listen*, Echo) return errors matching these instead of panicking or returning bare errors when the dialog is in the corresponding lifecycle state. See docs/contracts.md §8.

View Source
var (
	// ErrPlaybackStopped is returned by Play when playback was stopped with
	// Stop() or by DTMF interrupt. It also matches io.EOF.
	ErrPlaybackStopped = errors.New("playback stopped")
	// ErrPlaybackReplayed is returned by Play when replay was requested with
	// Replay() and playback will restart from the beginning. It also matches io.EOF.
	ErrPlaybackReplayed = errors.New("playback replayed")
	// ErrSourceNotReplayable is returned when replay is requested but playback
	// source can not be restarted (generic reader without io.Seeker support).
	ErrSourceNotReplayable = errors.New("playback source is not replayable")
)

Playback errors. PlaybackStopped and PlaybackReplayed also match io.EOF for backward compatibility with code treating EOF as successful end.

View Source
var (
	// BridgeDebug enables some traces
	BridgeDebug bool
)
View Source
var (
	DefaultPlaybackURLRangeSize int = 65536
)
View Source
var ErrBridgeClosed = errors.New("bridge closed")
View Source
var (
	ErrClientEarlyMedia = errors.New("Early media detected")
)
View Source
var ErrRecordingClosed = errors.New("recording already closed")

ErrRecordingClosed is returned by StereoRecording operations issued after Close.

View Source
var (
	PlaybackBufferSize = 3840 // For now largest we support. 48000 sample rate with 2 channels
)

Functions

func NewConnRecorder

func NewConnRecorder() *connRecorder

Types

type AnswerOptions deprecated

type AnswerOptions struct {
	// OnMediaUpdate triggers when media update happens. It is blocking func, so make sure you exit
	OnMediaUpdate func(d *DialogMedia)

	// OnRefer is called on successfull REFER handling
	//
	// It creates new dialog (NewDialog) on which you need to call Invite() and Ack()
	// Any error from invite, ack or other processing should be returned for correct Notify handling
	//
	// NOTE: IT is SCOPED to handler and exiting handler will Close/Terminate this dialog!
	OnRefer func(referDialog *DialogClientSession) error
	// Codecs that will be used
	Codecs []media.Codec

	// RTPNAT is media.MediaSession.RTPNAT
	// Check media.RTPNAT... options
	RTPNAT int
}

AnswerOptions is the legacy option struct for Answer.

Deprecated: Use Answer with SignalOptions.

type AudioPlayback

type AudioPlayback struct {

	// Read only values
	// This will influence playout sampling buffer
	BitDepth    int
	NumChannels int
	// contains filtered or unexported fields
}

func NewAudioPlayback

func NewAudioPlayback(writer io.Writer, codec media.Codec) AudioPlayback

NewAudioPlayback creates a playback where writer is encoder/streamer to media codec Use dialog.CreatePlayback() instead creating manually playback

func (*AudioPlayback) Codec

func (p *AudioPlayback) Codec() media.Codec

func (*AudioPlayback) Play deprecated

func (p *AudioPlayback) Play(reader io.Reader, mimeType string) (int64, error)

Play is generic approach to play supported audio contents Empty mimeType will stream reader as buffer. Make sure that bitdepth and numchannels is set correctly

Deprecated: Use PlayContext for cancellable playback.

func (*AudioPlayback) PlayContext

func (p *AudioPlayback) PlayContext(ctx context.Context, reader io.Reader, mimeType string) (int64, error)

PlayContext plays supported audio contents and stops streaming with ctx.Err() when the context is canceled. Cancellation latency is bounded by one packet interval (the pacing wait inside the writer).

func (*AudioPlayback) PlayFile deprecated

func (p *AudioPlayback) PlayFile(filename string) (int64, error)

PlayFile will play file and close file when finished playing If you need to play same file multiple times, that use generic Play function

Deprecated: Use PlayFileContext.

func (*AudioPlayback) PlayFileContext

func (p *AudioPlayback) PlayFileContext(ctx context.Context, filename string) (int64, error)

PlayFileContext plays a wav file and closes it when finished. Cancellation stops streaming and returns ctx.Err().

func (*AudioPlayback) PlayURL deprecated

func (p *AudioPlayback) PlayURL(urlStr string) (int64, error)

PlayURL plays wav content from url.

Deprecated: Use PlayURLContext.

func (*AudioPlayback) PlayURLContext

func (p *AudioPlayback) PlayURLContext(ctx context.Context, urlStr string) (int64, error)

PlayURLContext plays wav content from url. The context bounds the request and the streaming; unlike the deprecated PlayURL there is no implicit 10s deadline when the caller provides one.

type AudioPlaybackControl

type AudioPlaybackControl struct {
	AudioPlayback
	// contains filtered or unexported fields
}

func NewAudioPlaybackControl

func NewAudioPlaybackControl(a AudioPlayback) AudioPlaybackControl

func (*AudioPlaybackControl) Mute

func (p *AudioPlaybackControl) Mute(mute bool)

func (*AudioPlaybackControl) Pause

func (p *AudioPlaybackControl) Pause()

Pause pauses playback. Playback position freezes and no RTP packets are sent, which remote side experiences as silence. Use Resume to continue from the same position.

func (*AudioPlaybackControl) Play deprecated

func (p *AudioPlaybackControl) Play(reader io.Reader, mimeType string) (int64, error)

Play plays reader content with replay support. On replay request (Replay() called from other goroutine) playback restarts if reader implements io.Seeker, otherwise error ErrSourceNotReplayable is returned. Use PlayFile or PlayURL for non-seekable sources.

Deprecated: Use PlayContext.

func (*AudioPlaybackControl) PlayContext

func (p *AudioPlaybackControl) PlayContext(ctx context.Context, reader io.Reader, mimeType string) (int64, error)

PlayContext plays reader content with replay support, stopping with ctx.Err() when the context is canceled. See Play for the replay semantics.

func (*AudioPlaybackControl) PlayFile deprecated

func (p *AudioPlaybackControl) PlayFile(filename string) (int64, error)

PlayFile plays wav file with replay support. On replay request file is reopened and playback restarts from the beginning.

Deprecated: Use PlayFileContext.

func (*AudioPlaybackControl) PlayFileContext

func (p *AudioPlaybackControl) PlayFileContext(ctx context.Context, filename string) (int64, error)

PlayFileContext plays wav file with replay support, stopping with ctx.Err() when the context is canceled.

func (*AudioPlaybackControl) PlayURL deprecated

func (p *AudioPlaybackControl) PlayURL(urlStr string) (int64, error)

PlayURL plays wav content from url with replay support. On replay request url is refetched and playback restarts from the beginning.

Deprecated: Use PlayURLContext.

func (*AudioPlaybackControl) PlayURLContext

func (p *AudioPlaybackControl) PlayURLContext(ctx context.Context, urlStr string) (int64, error)

PlayURLContext plays wav content from url with replay support, stopping with ctx.Err() when the context is canceled.

func (*AudioPlaybackControl) Replay

func (p *AudioPlaybackControl) Replay() error

Replay requests playback restart from the beginning. It MUST be called from a different goroutine than Play (ie DTMF callback) and only while playback is active. Ongoing Play returns ErrPlaybackReplayed and playback source is restarted. Sources opened with PlayFile/PlayURL can always replay. Generic Play(reader) requires reader to implement io.Seeker.

func (*AudioPlaybackControl) Resume

func (p *AudioPlaybackControl) Resume()

Resume resumes playback paused with Pause

func (*AudioPlaybackControl) State

State returns current playback state

func (*AudioPlaybackControl) Stop

func (p *AudioPlaybackControl) Stop()

Stop stops playback. Ongoing Play returns error matching ErrPlaybackStopped and io.EOF.

type AudioPlaybackDTMF

type AudioPlaybackDTMF struct {
	AudioPlaybackControl
	// contains filtered or unexported fields
}

AudioPlaybackDTMF is playback that can be interrupted or replayed with in-band RTP DTMF (telephone-event).

All received DTMF keys are delivered to DTMF() channel, so playback decision like "which key caller pressed" can be done after play.

PlaybackControl (Stop, Pause, Resume, Replay, Mute) can be used in parallel.

func (*AudioPlaybackDTMF) Close

func (p *AudioPlaybackDTMF) Close() error

Close stops DTMF reading. It should be called when DTMF playback is not needed anymore, to allow other audio reading on the dialog.

Cancellation goes through the reader gate (no conn deadlines): the in-flight read is interrupted and the reader is restored afterwards. dm may be nil when the struct is constructed manually (test stubs); the read loop simply has no media to arm in that case.

func (*AudioPlaybackDTMF) DTMF

func (p *AudioPlaybackDTMF) DTMF() <-chan rune

DTMF returns channel of DTMF keys received during playback. Channel has buffer, on overflow keys are dropped.

func (*AudioPlaybackDTMF) Play deprecated

func (p *AudioPlaybackDTMF) Play(reader io.Reader, mimeType string) (int64, error)

Play plays reader content with DTMF control.

Deprecated: Use PlayContext.

func (*AudioPlaybackDTMF) PlayContext

func (p *AudioPlaybackDTMF) PlayContext(ctx context.Context, reader io.Reader, mimeType string) (int64, error)

PlayContext plays reader content with DTMF control, stopping with ctx.Err() when the context is canceled. DTMF reading continues until Close.

func (*AudioPlaybackDTMF) PlayFile deprecated

func (p *AudioPlaybackDTMF) PlayFile(filename string) (int64, error)

PlayFile plays wav file with DTMF control.

Deprecated: Use PlayFileContext.

func (*AudioPlaybackDTMF) PlayFileContext

func (p *AudioPlaybackDTMF) PlayFileContext(ctx context.Context, filename string) (int64, error)

PlayFileContext plays wav file with DTMF control, stopping with ctx.Err() when the context is canceled.

func (*AudioPlaybackDTMF) PlayURL deprecated

func (p *AudioPlaybackDTMF) PlayURL(urlStr string) (int64, error)

PlayURL plays wav from url with DTMF control.

Deprecated: Use PlayURLContext.

func (*AudioPlaybackDTMF) PlayURLContext

func (p *AudioPlaybackDTMF) PlayURLContext(ctx context.Context, urlStr string) (int64, error)

PlayURLContext plays wav from url with DTMF control, stopping with ctx.Err() when the context is canceled.

type AudioReaderOption

type AudioReaderOption func(d *DialogMedia) error

func WithAudioReaderDTMF

func WithAudioReaderDTMF(r *DTMFReader) AudioReaderOption

WithAudioReaderDTMF creates DTMF interceptor

func WithAudioReaderJitterBuffer

func WithAudioReaderJitterBuffer(opts media.RTPJitterBufferOptions) AudioReaderOption

WithAudioReaderJitterBuffer inserts an RTP jitter buffer before the payload reader. Packet duration is derived from the negotiated audio codec.

func WithAudioReaderMediaProps

func WithAudioReaderMediaProps(p *MediaProps) AudioReaderOption

func WithAudioReaderPCMMonitor

func WithAudioReaderPCMMonitor(mon *audio.MonitorPCMReader, w io.Writer) AudioReaderOption

func WithAudioReaderRTPStats

func WithAudioReaderRTPStats(hook media.OnRTPReadStats) AudioReaderOption

WithAudioReaderRTPStats creates RTP Statistics interceptor on audio reader

type AudioRingtone

type AudioRingtone struct {
	// contains filtered or unexported fields
}

AudioRingtone is playback for ringtone

func (*AudioRingtone) Play

func (a *AudioRingtone) Play(ctx context.Context) error

func (*AudioRingtone) PlayBackground

func (a *AudioRingtone) PlayBackground() (func() error, error)

type AudioStereoRecordingWav

type AudioStereoRecordingWav struct {
	// contains filtered or unexported fields
}

func (*AudioStereoRecordingWav) AudioReader

func (*AudioStereoRecordingWav) AudioWriter

func (*AudioStereoRecordingWav) Close

func (r *AudioStereoRecordingWav) Close() error

type AudioWriterOption

type AudioWriterOption func(d *DialogMedia) error

func WithAudioWriterDTMF

func WithAudioWriterDTMF(r *DTMFWriter) AudioWriterOption

WithAudioWriterDTMF adds DTMF into audio pipeline

func WithAudioWriterMediaProps

func WithAudioWriterMediaProps(p *MediaProps) AudioWriterOption

func WithAudioWriterMonitor

func WithAudioWriterMonitor(mon *audio.MonitorPCMWriter, w io.Writer) AudioWriterOption

WithAudioWriterMonitor initializes and adds PCM monitor in audio pipeline. It records and decodes stream into PCM.

func WithAudioWriterRTPStats

func WithAudioWriterRTPStats(hook media.OnRTPWriteStats) AudioWriterOption

WithAudioWriterRTPStats creates RTP Statistics interceptor on audio writer

type Bridge

type Bridge struct {
	// Originator is dialog session that created bridge
	Originator DialogSession
	// DTMFpass is also dtmf pipeline and proxy. By default only audio media is proxied
	// NOTE: this may not work if you are already processing DTMF with CreateDTMFReader
	DTMFpass bool

	// minDialogs is just helper flag when to start proxy
	WaitDialogsNum int

	// DiscardQueuedOnStart drops RTP already queued on each leg's socket right
	// before the proxy loop starts, so bridging begins from live media instead
	// of replaying a pre-bridge backlog at the line rate - which otherwise
	// turns into permanent one-way latency (docs/contracts.md §15).
	//
	// Opt-in (default false). Enable it for answer-before-bridge B2BUAs,
	// where the peer starts sending media before the second leg joins and
	// nothing consumes the socket in between.
	DiscardQueuedOnStart bool
	// contains filtered or unexported fields
}

func NewBridge

func NewBridge() *Bridge

NewBridge creates bridge with default settings. The bridge state (dialog list, originator) is guarded internally, so the pointer must be used: methods are not safe to call on a copied value.

func (*Bridge) AddDialogSession

func (b *Bridge) AddDialogSession(d DialogSession) error

func (*Bridge) Close added in v0.0.6

func (b *Bridge) Close() error

Close stops any running proxy and clears the bridge. Idempotent; Add after Close returns ErrBridgeClosed.

func (*Bridge) GetDialogs deprecated

func (b *Bridge) GetDialogs() []DialogSession

GetDialogs returns the list of dialogs added to the bridge.

Deprecated: Not synchronized with bridge state and not used by the library; keep track of the sessions you added yourself.

func (*Bridge) Init

func (b *Bridge) Init(log *slog.Logger)

func (*Bridge) ProxyMedia

func (b *Bridge) ProxyMedia() error

ProxyMedia is explicit starting proxy media. In some cases you want to control and be signaled when bridge terminates

NOTE: Should be only called if you want to start manually proxying. It is required to set WaitDialogsNum higher than 2. Parallel user writers on the bridged dialogs are not supported: the proxy writes through the same handle.

Experimental

func (*Bridge) ProxyMediaControl

func (b *Bridge) ProxyMediaControl() (func() error, error)

ProxyMediaControl starts proxy in background and allows to stop proxy at any time. Stop should be called once and it is not needed to be called if call is terminating

Stop interrupts the proxy through the write gate of the bridged dialogs and restores the writers afterwards.

Experimental

func (*Bridge) RemoveDialogSession added in v0.0.6

func (b *Bridge) RemoveDialogSession(d DialogSession) error

RemoveDialogSession removes a dialog by ID. Missing IDs are a no-op nil. If a proxy is running it is interrupted through the write gate; removal still succeeds on pause errors (e.g. already hung up). Originator falls back to the first remaining dialog.

type BridgeMix

type BridgeMix struct {

	// WaitDialogsNum is just helper flag when to start proxy
	WaitDialogsNum int
	// RealtimeReader is almost always nesessary if you are delaying audio streaming(mixing) in bridge
	RealtimeReader bool
	Poll           bool
	// contains filtered or unexported fields
}

BridgeMix is mixing audio when having 2 or more parties.

Experimental: not fully tested yet

func NewBridgeMix

func NewBridgeMix() *BridgeMix

func (*BridgeMix) AddDialogSession

func (b *BridgeMix) AddDialogSession(d DialogSession) error

func (*BridgeMix) DialogSessionsList deprecated

func (b *BridgeMix) DialogSessionsList() []DialogSession

DialogSessionsList returns list of dialogs in bridge It is not safe to use dialogs for media until they are removed from bridge

Deprecated: Not used by the library; keep track of the sessions you added yourself.

func (*BridgeMix) Init

func (b *BridgeMix) Init()

Init initializes bridge struct. Use only if construct bridge with struct or use NewBridgeMix

func (*BridgeMix) RemoveDialogSession

func (b *BridgeMix) RemoveDialogSession(d DialogSession) error

func (*BridgeMix) String

func (b *BridgeMix) String() string

type Bridger deprecated

type Bridger interface {
	AddDialogSession(d DialogSession) error
}

Bridger is the interface satisfied by Bridge and BridgeMix.

Deprecated: The library never accepts this interface; pass *Bridge or *BridgeMix concretely. It will be removed.

type DTMFMethod added in v0.0.4

type DTMFMethod uint8

DTMFMethod selects how SendDTMF delivers digits. SIP INFO (RFC 2926) is not implemented; DTMFMethodAuto falls back to inband audio tones when the peer has no telephone-event.

const (
	DTMFMethodAuto DTMFMethod = iota
	DTMFMethodRTP
	DTMFMethodInband
)

type DTMFReader

type DTMFReader struct {
	// contains filtered or unexported fields
}

func (*DTMFReader) Listen deprecated

func (d *DTMFReader) Listen(onDTMF func(dtmf rune) error, dur time.Duration) error

Listen listens for DTMF events until dur elapses.

Deprecated: Use ListenContext with a context deadline.

func (*DTMFReader) ListenContext

func (d *DTMFReader) ListenContext(ctx context.Context, onDTMF func(dtmf rune) error) error

ListenContext listens for DTMF events until the context is done. Cancellation interrupts the in-flight read through the reader gate; DTMF events already detected are delivered through onDTMF before the read that carries them returns. Returns nil on clean cancellation.

Unlike the deprecated Listen(dur), the dialog reader is not touched with deadlines: pauses and other components do not terminate the listen.

func (*DTMFReader) OnDTMF

func (d *DTMFReader) OnDTMF(onDTMF func(dtmf rune) error)

OnDTMF must be called before audio reading

func (*DTMFReader) Read

func (d *DTMFReader) Read(buf []byte) (n int, err error)

Read exposes io.Reader that can be used as AudioReader

type DTMFSendOption added in v0.0.4

type DTMFSendOption func(*dtmfSendConfig) error

DTMFSendOption tunes DialogMedia.SendDTMF.

func WithDTMFEventDuration added in v0.0.4

func WithDTMFEventDuration(d time.Duration) DTMFSendOption

WithDTMFEventDuration sets the per-digit event hold (RFC 4733 Duration field) and the inband tone length (default 80ms).

func WithDTMFInterval added in v0.0.4

func WithDTMFInterval(d time.Duration) DTMFSendOption

WithDTMFInterval sets the silence between digits (default 80ms).

func WithDTMFMethod added in v0.0.4

func WithDTMFMethod(m DTMFMethod) DTMFSendOption

WithDTMFMethod selects the delivery method. Default DTMFMethodAuto: RFC 4733 when telephone-event is negotiated, inband dual tone otherwise.

func WithDTMFVolume added in v0.0.4

func WithDTMFVolume(v uint8) DTMFSendOption

WithDTMFVolume sets the RFC 4733 signal volume, 0-63 relative dBov (default 10 when the option is not given; 0 is the loudest). Ignored by the inband method (tone level is fixed by the audio engine; use PlayTone with WithToneVolume for custom levels).

type DTMFWriter

type DTMFWriter struct {
	// contains filtered or unexported fields
}

func (*DTMFWriter) AudioWriter

func (w *DTMFWriter) AudioWriter() *media.RTPDtmfWriter

AudioReader exposes DTMF audio writer. You should use this for parallel audio processing

func (*DTMFWriter) Write

func (w *DTMFWriter) Write(buf []byte) (n int, err error)

Write exposes as io.Writer that can be used as AudioWriter

func (*DTMFWriter) WriteDTMF

func (w *DTMFWriter) WriteDTMF(dtmf rune) error

WriteDTMF sends one RFC 4733 event. A concurrent PauseAudioWrite gate is waited out; use WriteDTMFContext when that wait must be cancellable.

func (*DTMFWriter) WriteDTMFContext added in v0.0.4

func (w *DTMFWriter) WriteDTMFContext(ctx context.Context, dtmf rune) error

WriteDTMFContext is WriteDTMF with a context: ctx cancels a wait spent behind the PauseAudioWrite gate (returning ctx.Err()); an event whose first packet was accepted completes regardless.

type Diago

type Diago struct {
	// contains filtered or unexported fields
}

func NewDiago

func NewDiago(ua *sipgo.UserAgent, opts ...DiagoOption) *Diago

NewDiago construct b2b user agent that will act as server and client

func (*Diago) DialogCacheClient deprecated

func (dg *Diago) DialogCacheClient() DialogCache[*DialogClientSession]

DialogCacheClient gives access to the internal dialog cache of client dialogs.

Deprecated: Internal cache accessor; not part of the public API surface and will be unexported. Use MatchDialog for request-to-dialog membership checks; cache access will be removed.

func (*Diago) DialogCacheServer deprecated

func (dg *Diago) DialogCacheServer() DialogCache[*DialogServerSession]

DialogCacheServer gives access to the internal dialog cache of server dialogs.

Deprecated: Internal cache accessor; not part of the public API surface and will be unexported. Use MatchDialog for request-to-dialog membership checks (the conntrack/allowlist use case); cache access will be removed.

func (*Diago) HandleFunc

func (dg *Diago) HandleFunc(f ServeDialogFunc)

HandleFunc registers you handler function for dialog. Must be called before serving request

func (*Diago) Invite

func (dg *Diago) Invite(ctx context.Context, recipient sip.Uri, opts ...SignalOption) (d *DialogClientSession, err error)

Invite makes outgoing call leg and waits for answer. It is helper that calls - NewDialog - dialog.Invite

Options allow full call customization, checkout SignalOption. Each SignalOption func is executed exactly once for the whole call setup. The final ACK is sent without options; call Ack directly if you need ACK headers. Honors: everything DialogClientSession.Invite honors.

For better control more details use above functions instead. If you want to bridge call then use helper InviteBridge

func (*Diago) InviteBridge

func (dg *Diago) InviteBridge(ctx context.Context, recipient sip.Uri, bridge *Bridge, opts ...SignalOption) (d *DialogClientSession, err error)

InviteBridge makes outgoing call leg and does bridging. Outgoing session will be added into bridge on answer If bridge has Originator (first participant) it will be used for creating outgoing call leg as in B2BUA When bridge is provided then this call will be bridged with any participant already present in bridge Honors: everything DialogClientSession.Invite honors; Originator defaults to the bridge originator when not set. Options execute exactly once.

func (*Diago) MatchDialog added in v0.0.2

func (dg *Diago) MatchDialog(req *sip.Request) (DialogSession, bool)

MatchDialog resolves an in-dialog SIP request to the dialog tracked by this engine. It mirrors internal routing order: server (UAS) role via DialogIDFromRequestUAS first, then client (UAC) via DialogIDFromRequestUAC. ok is false for out-of-dialog, missing headers, or unknown dialogs. When ok, the returned DialogSession is the live *DialogServerSession or *DialogClientSession (type-assert if role matters).

func (*Diago) NewDialog

func (dg *Diago) NewDialog(recipient sip.Uri, opts ...SignalOption) (d *DialogClientSession, err error)

NewDialog creates a new client dialog session after you can perform dialog Invite - You call Invite(...) after this call followed with ACK Options allow selecting transport (WithDialogTransport, WithDialogTransportID), overriding Contact and per-dialog media configuration. Honors: dialog (Transport, TransportID), msg (Contact); the remaining fields are honored by the later Invite call.

func (*Diago) Register

func (dg *Diago) Register(ctx context.Context, recipient sip.Uri, opts ...SignalOption) error

Register will create register transaction and keep registration ongoing until error is hit. For more granular control over registrations use RegisterTransaction. Options configure the transaction (WithRegisterExpiry, WithRegisterRetryInterval, WithRegisterProxyHost, WithRegisterAllowHeaders, WithOnRegistered) and every REGISTER attempt (WithAuthCredentials, WithContact, WithHeaders, WithRequestMutator). Honors: msg (Headers, Contact, MutateRequest), dialog (Username, Password), register (all).

func (*Diago) RegisterTransaction

func (dg *Diago) RegisterTransaction(ctx context.Context, recipient sip.Uri, opts ...SignalOption) (*RegisterTransaction, error)

Register transaction creates register transaction object that can be used for Register Unregister requests Options configure the transaction (see Register) and every REGISTER attempt. Honors: msg (Headers, Contact, MutateRequest), dialog (Username, Password), register (all).

func (*Diago) Serve

func (dg *Diago) Serve(ctx context.Context, f ServeDialogFunc) error

Serve starts 'Server' handle for SIP. Should be called for UAS but can be skipped for UAC behavior

Handler contract (docs/contracts.md §6): when the ServeDialogFunc returns, the framework tears the call down — it hangs up the dialog (BYE once confirmed, otherwise a 480 decline) with a 10s timeout, then closes the dialog and its media. A handler that wants the call alive must block until the call ends (or hang up itself before returning); goroutines spawned in the handler must not rely on the dialog afterwards.

func (*Diago) ServeBackground

func (dg *Diago) ServeBackground(ctx context.Context, f ServeDialogFunc) error

ServeBackground starts serving in background, but waits server listener to be started before returning Checkout more info on Serve()

func (*Diago) Shutdown

func (dg *Diago) Shutdown(ctx context.Context) error

Shutdown gracefully stops the Diago instance:

  1. It hangs up and closes every dialog tracked in the dialog caches (server dialogs are tracked from the incoming INVITE, client dialogs once confirmed). Media sockets close with the dialogs and the cache entries are evicted.
  2. It stops the SIP listeners started by Serve/ServeBackground and waits for them to exit.

Ownership boundaries (docs/contracts.md §11):

  • The UserAgent is NOT closed: it is caller-owned and may be shared by multiple Diago instances. Call ua.Close() yourself when done.
  • Outgoing dialogs that never reached an answer are not in the cache; cancel their Invite contexts instead (which sends CANCEL).
  • Register transactions are caller-owned goroutines: cancel their context (which triggers Unregister/loop exit) around Shutdown.

In-flight serve handlers keep running concurrently with phase 1; their own Hangup/Close on an already-torn-down dialog is a tolerated no-op path. Shutdown is idempotent. After Shutdown, Serve/ServeBackground return an error. Register transaction loops must be cancelled by their owner.

type DiagoOption

type DiagoOption func(dg *Diago)

func WithAuth

func WithAuth(auth sipgo.DigestAuth) DiagoOption

WithAuth is deprecated and currently unused: it holds client-side digest credentials (sipgo.DigestAuth), but diago does not perform automatic client-side authentication at the UA level. Use WithAuthCredentials per dialog/register instead. For authenticating INCOMING calls use DigestAuthServer and DialogServerSession.Authorize in your serve handler.

func WithClient

func WithClient(client *sipgo.Client) DiagoOption

WithClient allows providing custom client handle. Consider still it needs to use same UA as diago

func WithLogger

func WithLogger(l *slog.Logger) DiagoOption

func WithMediaConfig

func WithMediaConfig(conf MediaConfig) DiagoOption

func WithServer

func WithServer(srv *sipgo.Server) DiagoOption

WithServer allows providing custom server handle. Consider still it needs to use same UA as diago

func WithServerRequestMiddleware

func WithServerRequestMiddleware(f func(next sipgo.RequestHandler) sipgo.RequestHandler) DiagoOption

func WithTransport

func WithTransport(t Transport) DiagoOption

type DialogCache

type DialogCache[T DialogSession] interface {
	DialogStore(ctx context.Context, id string, v T) error
	DialogLoad(ctx context.Context, id string) (T, error)
	DialogDelete(ctx context.Context, id string) error
	DialogRange(ctx context.Context, f func(id string, d T) bool) error
}

type DialogCachePool

type DialogCachePool struct {
	// contains filtered or unexported fields
}

func (*DialogCachePool) MatchDialog

func (*DialogCachePool) MatchDialogClient

func (p *DialogCachePool) MatchDialogClient(req *sip.Request) (*DialogClientSession, error)

func (*DialogCachePool) MatchDialogServer

func (p *DialogCachePool) MatchDialogServer(req *sip.Request) (*DialogServerSession, error)

type DialogClientSession

type DialogClientSession struct {
	*sipgo.DialogClientSession

	DialogMedia
	// contains filtered or unexported fields
}

DialogClientSession represents outbound channel

func (*DialogClientSession) Ack

func (d *DialogClientSession) Ack(ctx context.Context, opts ...SignalOption) error

Ack acknowledgeds media Before Ack normally you want to setup more stuff like bridging Options allow customizing headers of the ACK request. Honors: msg (Headers, Contact, MutateRequest).

func (*DialogClientSession) Close

func (d *DialogClientSession) Close() error

Close frees local resources (media stack and dialog cleanup hooks). It is idempotent and does NOT send any SIP message: closing without Hangup leaves the remote leg up. Signaling teardown is the caller's responsibility on client dialogs (docs/contracts.md §6, §7).

func (*DialogClientSession) DialogSIP

func (d *DialogClientSession) DialogSIP() *sipgo.Dialog

func (*DialogClientSession) FromUser

func (d *DialogClientSession) FromUser() string

func (*DialogClientSession) Hangup

func (d *DialogClientSession) Hangup(ctx context.Context, opts ...SignalOption) error

Hangup terminates dialog with BYE. Options allow customizing headers of the BYE request (ex. Reason header), Contact and to mutate the final request.

Behavior by dialog state (docs/contracts.md §7):

  • no response received yet: error ErrDialogNotAnswered (cancel the Invite context instead, which sends CANCEL)
  • provisional response received (early dialog): BYE on the early dialog
  • confirmed: BYE, waits for 200
  • already ended: returns nil silently

Honors: msg (Headers, Contact, MutateRequest).

func (*DialogClientSession) Hold

func (d *DialogClientSession) Hold(ctx context.Context, opts ...SignalOption) error

Hold puts dialog on hold (media sendonly). Options allow customizing the re-INVITE. Honors: msg (Headers, Contact, Body, MutateRequest); media: WithMusicOnHold selects the hold music started automatically after the re-INVITE succeeds (falling back to MediaConfig.MusicOnHold); other media overrides are not consumed.

The automatic music runs detached from the Hold call's context (which typically carries the re-INVITE timeout) until Unhold, Stop/StopMusicOnHold, or dialog Close; when the dialog-level default is unset (no MusicOnHold configured anywhere) Hold behaves as before and plays nothing.

func (*DialogClientSession) ID added in v0.0.6

func (d *DialogClientSession) ID() string

func (*DialogClientSession) Invite

func (d *DialogClientSession) Invite(ctx context.Context, opts ...SignalOption) error

Invite sends Invite request and establishes [early] media. Normally you need to call Ack after.

Normal Answer with 200 OK (SDP) - You MUST call Ack() after to acknowledge session.

Early Media Detect: - WithEarlyMediaDetect() must be set as part of options otherwise it ignores early media - It RETURNS ErrClientEarlyMedia if remote answers with 183 Session in Progress - Media is negotiated and setuped - You need to call WaitAnswer() if you want to proceed with answering call

Options allow customizing Contact, From, custom headers, SDP body, media (IP, codecs, fully custom media session) and a final request mutator.

Honors: msg (Headers, Contact, Body, MutateRequest), media (all), dialog (all).

Errors: - sipgo.ErrDialogResponse - ErrClientEarlyMedia

NOTE: It updates internal invite request so NOT THREAD SAFE. If you pass originator it will use originator to set correct from header and avoid media transcoding

func (*DialogClientSession) ReInvite

func (d *DialogClientSession) ReInvite(ctx context.Context, opts ...SignalOption) error

ReInvite sends new invite based on current media session Options allow customizing Contact, headers, SDP body and to mutate the final request. Honors: msg (Headers, Contact, Body, MutateRequest); media overrides are not consumed.

func (*DialogClientSession) Refer

func (d *DialogClientSession) Refer(ctx context.Context, referTo sip.Uri, opts ...SignalOption) error

Refer tries todo refer (blind transfer) on call. Options allow customizing headers of the REFER request and receiving the transfer status via WithOnReferNotify. Honors: msg (Headers, Contact, MutateRequest); dialog (OnReferNotify via WithOnReferNotify).

NOTE: It is expected that after calling this you are hanguping call to send BYE

func (*DialogClientSession) RemoteContact

func (d *DialogClientSession) RemoteContact() *sip.ContactHeader

func (*DialogClientSession) ToUser

func (d *DialogClientSession) ToUser() string

func (*DialogClientSession) Unhold

func (d *DialogClientSession) Unhold(ctx context.Context, opts ...SignalOption) error

Unhold takes dialog back from hold (media sendrecv). Options allow customizing the re-INVITE. Honors: msg (Headers, Contact, Body, MutateRequest); the music started automatically by Hold is stopped on success; other media overrides are not consumed.

func (*DialogClientSession) WaitAnswer

func (d *DialogClientSession) WaitAnswer(ctx context.Context, opts sipgo.AnswerOptions) error

WaitAnswer waits dialog on answer. It should only be used if you have error Invite but still want to continue ex. ErrClientEarlyMedia was returned but you want to proceed with answering

type DialogData

type DialogData struct {
	InviteRequest sip.Request
	State         sip.DialogState
}

type DialogMedia

type DialogMedia struct {

	// Packet reader is default reader for RTP audio stream
	// Use always AudioReader to get current Audio reader
	// Use this only as read only
	// It MUST be always created on Media Session Init
	// Only safe to use after dialog Answered (Completed state)
	RTPPacketReader *media.RTPPacketReader

	// Packet writer is default writer for RTP audio stream
	// Use always AudioWriter to get current Audio reader
	// Use this only as read only
	RTPPacketWriter *media.RTPPacketWriter
	// contains filtered or unexported fields
}

DialogMedia is the media half shared by server and client dialog sessions.

Ownership contract (docs/contracts.md §1): DialogMedia is the sole owner of the active *media.MediaSession and *media.RTPSession. It owns exactly one of each at a time; re-INVITEs and media updates replace them under mu, while RTPPacketReader and RTPPacketWriter are stable handles whose internals are hot-swapped on every update. Code that needs media-session services (deadlines, codecs) must resolve them through DialogMedia at use time and never capture a *media.MediaSession pointer.

Concurrency contract (docs/contracts.md §5): all mutable media state is guarded by mu; accessor methods (MediaSession, RTPSession, AudioReader, AudioWriter, RemoteContact) are safe from any goroutine. Option functions passed to AudioReader/AudioWriter run under mu and must not block or call back into locking DialogMedia methods. The setup phase of a dialog (Invite/Answer/ProgressMedia) is single-threaded by contract.

func (*DialogMedia) AudioReader

func (d *DialogMedia) AudioReader(opts ...AudioReaderOption) (io.Reader, error)

AudioReader returns io.Reader on which you can read your ENCODED audio. By default it is RTPPacketReader unless overwritten with SetAudioReader().

NOTE: AudioReader must be called after negotiation is finished, like Answer() Reading buffer should be equal or bigger of media.RTPBufSize Use AuidioListen for optimized reading.

func (*DialogMedia) AudioStereoRecordingCreate deprecated

func (d *DialogMedia) AudioStereoRecordingCreate(wavFile *os.File) (*AudioStereoRecordingWav, error)

AudioStereoRecordingCreate creates Stereo Recording audio Pipeline and stores as Wav file format For audio to be recorded use AudioReader and AudioWriter from Recording

It does NOT install itself into the pipeline: the caller pulls the returned AudioReader/AudioWriter (a manual echo/copy loop) or wires them in with SetAudioReader/SetAudioWriter.

Deprecated: Use DialogMedia.StartStereoRecording instead. It self-installs the tap into the current audio chain atomically (no deprecated setter, no half-wired window), is fail-open by default so a full disk cannot interrupt bridged media, and supports Pause/Resume and configurable spool. See docs/contracts.md §12 for the install-before-Bridge timing contract. This method will be removed in a future release; do not use in new code.

func (*DialogMedia) AudioWriter

func (d *DialogMedia) AudioWriter(opts ...AudioWriterOption) (io.Writer, error)

AudioWriter returns io.Writer on which you can write your ENCODED audio. By default it is RTPPacketWriter unless overwritten with SetAudioWriter(). NOTE: RTPPacketWriter has running sample clock, but it expects samples sent, match sample duration of codec.

func (*DialogMedia) Close

func (d *DialogMedia) Close() error

func (*DialogMedia) CreateDTMFReader added in v0.0.6

func (m *DialogMedia) CreateDTMFReader() (*DTMFReader, error)

CreateDTMFReader is DTMF over RTP. It reads audio and provides hook for dtmf while listening for audio Use Listen or OnDTMF after this call

Unlike the WithAudioReaderDTMF option, the returned reader is NOT installed into the dialog audio pipeline: it wraps the current audio reader chain at creation time. Deadline control resolves the current media session at use time (docs/contracts.md §4).

func (*DialogMedia) CreateDTMFWriter added in v0.0.6

func (m *DialogMedia) CreateDTMFWriter() (*DTMFWriter, error)

CreateDTMFWriter is DTMF over RTP on the write side.

Unlike the WithAudioWriterDTMF option, the returned writer is NOT installed into the dialog audio pipeline: it wraps the current audio writer chain at creation time (docs/contracts.md §4).

func (*DialogMedia) CreatePlayback added in v0.0.6

func (d *DialogMedia) CreatePlayback() (AudioPlayback, error)

CreatePlayback creates playback for audio

func (*DialogMedia) CreatePlaybackControl added in v0.0.6

func (d *DialogMedia) CreatePlaybackControl() (AudioPlaybackControl, error)

CreatePlaybackControl creates playback with controls like mute, stop, pause, resume and replay

func (*DialogMedia) CreatePlaybackDTMF added in v0.0.6

func (d *DialogMedia) CreatePlaybackDTMF(opts ...PlaybackDTMFOption) (*AudioPlaybackDTMF, error)

CreatePlaybackDTMF creates playback controlled with in-band RTP DTMF.

By default any DTMF key interrupts playback. Use options to customize:

pb, _ := dialog.CreatePlaybackDTMF(
    WithInterruptKeys("1234567890"), // only number keys interrupt
    WithReplayKeys("*"),             // star key replays prompt
)
go pb.PlayFile("menu.wav")
dtmf := <-pb.DTMF()
pb.Close()

While playback is active DTMF keys are detected by reading audio RTP in background. Other audio reading (CreateDTMFReader, Echo, recording) MUST NOT be used until Close is called.

func (*DialogMedia) CreateRingtonePlayback added in v0.0.6

func (d *DialogMedia) CreateRingtonePlayback() (AudioRingtone, error)

CreateRingtonePlayback creates playback for ringtone

Experimental

func (*DialogMedia) DiscardQueuedRTP added in v0.0.6

func (d *DialogMedia) DiscardQueuedRTP() (int, error)

DiscardQueuedRTP drops any RTP datagrams already queued on the dialog's socket and returns how many were discarded. A media consumer calls it at the moment it starts - Bridge does so before its proxy loop (see Bridge.DiscardQueuedOnStart) - so forwarding begins from live media instead of replaying a backlog that piled up while nobody was reading. Replaying that backlog at the line rate is a common source of permanent one-way delay for answer-before-bridge B2BUAs (see docs/contracts.md §15).

Precondition: no other component is reading this dialog's audio at the same time. The drain reads the socket directly, bypassing the audio chain, so installed taps/interceptors, RTP statistics and SRTP state do not observe the dropped packets.

A DTLS-SRTP dialog whose handshake has not completed is a no-op: DTLS flights share the socket with SRTP and dropping them would break the handshake.

func (*DialogMedia) Echo deprecated

func (d *DialogMedia) Echo() error

Echo does audio echo for you

Deprecated: Use EchoContext.

func (*DialogMedia) EchoContext

func (d *DialogMedia) EchoContext(ctx context.Context) error

EchoContext does audio echo until the context is canceled. Cancellation interrupts the in-flight read through the reader gate and returns ctx.Err().

func (*DialogMedia) InitMediaSession deprecated

func (d *DialogMedia) InitMediaSession(m *media.MediaSession, r *media.RTPPacketReader, w *media.RTPPacketWriter)

InitMediaSession manually installs a media session and its stable handles.

Deprecated: Media session creation is owned by DialogMedia and driven by the signaling lifecycle (ProgressMedia/Answer, re-INVITE). Manual initialization is not supported; see docs/contracts.md for the ownership model.

func (*DialogMedia) IsRemoteHeld added in v0.0.5

func (d *DialogMedia) IsRemoteHeld() bool

IsRemoteHeld reports whether the negotiated media direction currently prevents us from sending — the remote peer put us on hold or set one-way media. It is updated on every media install (inbound re-INVITE offers and answers to our own offers, including the initial INVITE answer); our own Hold/Unhold negotiate sendonly/sendrecv and do not change it. Pair with WithOnMediaUpdate to react to remote hold/unhold from application code.

func (*DialogMedia) Listen

func (d *DialogMedia) Listen() (err error)

Listen keeps reading stream until it gets closed or deadlined Use ListenBackground or ListenContext for better control

func (*DialogMedia) ListenBackground

func (d *DialogMedia) ListenBackground() (stop func() error, err error)

ListenBackground listens on stream in background and allows correct stoping of stream on network layer

func (*DialogMedia) ListenContext

func (d *DialogMedia) ListenContext(pctx context.Context) error

ListenContext listens until context is canceled. Cancellation interrupts the in-flight read through the reader gate and returns ctx.Err(); the dialog media stays usable afterwards.

func (*DialogMedia) ListenUntil deprecated

func (d *DialogMedia) ListenUntil(dur time.Duration) error

ListenUntil listens until dur elapses.

Deprecated: Use ListenContext with a context deadline.

func (*DialogMedia) Media

func (d *DialogMedia) Media() *DialogMedia

func (*DialogMedia) MediaSession

func (d *DialogMedia) MediaSession() *media.MediaSession

func (*DialogMedia) OnClose

func (d *DialogMedia) OnClose(f func() error)

func (*DialogMedia) PauseAudioRead

func (d *DialogMedia) PauseAudioRead() (func(), error)

PauseAudioRead pauses the dialog audio reader: reads through the audio pipeline return media.ErrReadPaused until the returned release is called. Pause is refcounted, so concurrent pausers can not resume each other; the returned release must be called exactly once.

func (*DialogMedia) PauseAudioWrite

func (d *DialogMedia) PauseAudioWrite() (func(), error)

PauseAudioWrite pauses the dialog audio writer: writes through the audio pipeline return media.ErrWritePaused until the returned release is called. Refcounted like PauseAudioRead. An in-flight write completes first, so pause latency is bounded by one packet interval.

func (*DialogMedia) PlayMusicOnHold added in v0.0.5

func (d *DialogMedia) PlayMusicOnHold(ctx context.Context, opts ...MoHOption) (*MusicOnHold, error)

PlayMusicOnHold starts looping hold music on the dialog and returns immediately. The tone source is the WithMoHTone option, falling back to the dialog-level MediaConfig.MusicOnHold; with neither configured it returns ErrMusicOnHoldNoTone. A dialog runs at most one hold-music loop: starting a second one returns ErrMusicOnHoldActive. Media setup errors (not answered, closed) are returned synchronously.

The loop runs until Stop, StopMusicOnHold, ctx cancellation, or the dialog media closing; ctx cancellation surfaces through Stop as nil. While the peer holds us (negotiated recvonly/inactive) the RTP direction gate drops the audio — a warning is logged and the loop keeps running so an Unhold on either side resumes audibly.

func (*DialogMedia) PlayTone added in v0.0.4

func (d *DialogMedia) PlayTone(ctx context.Context, tone audio.Tone, opts ...ToneOption) error

PlayTone synthesizes a tone and streams it to the dialog's audio writer. It blocks until the tone finishes; with WithToneLoop it runs until ctx is canceled and then returns ctx.Err() (docs/contracts.md §9 cancellation style — cancellation latency is one packet interval).

While the dialog's write gate is paused by another component (PauseAudioWrite), PlayTone waits for the gate to release instead of failing the tone; ctx cancellation still interrupts the wait.

The tone is written into the audio pipeline regardless of the negotiated SDP direction: playing ringback to an answered originator works after Answer, and a peer that is sendonly simply ignores the packets.

func (*DialogMedia) RTPSession

func (d *DialogMedia) RTPSession() *media.RTPSession

RTPSession returns underhood rtp session NOTE: this can be nil

func (*DialogMedia) SendDTMF added in v0.0.4

func (m *DialogMedia) SendDTMF(ctx context.Context, digits string, opts ...DTMFSendOption) error

SendDTMF sends a digit string ('0'-'9', 'A'-'D' case-insensitive, '*', '#') on the answered dialog. It blocks for roughly (event duration + interval) per digit (no trailing interval after the last one). The context is honored between digits and while waiting on a paused audio-write gate; an in-flight event completes first, so with the gate open cancellation latency is bounded by one event.

The media pipeline is resolved at use time per docs/contracts.md §4: auto mode picks RFC 4733 on the negotiated telephone-event payload type, and falls back to inband dual tones when the peer does not support it. Explicit DTMFMethodRTP without negotiation returns ErrDTMFUnsupported.

SIP INFO DTMF is NOT yet supported as a method.

func (*DialogMedia) SetAudioReader

func (d *DialogMedia) SetAudioReader(r io.Reader)

SetAudioReader adds/changes audio reader. Use this when you want to have interceptors of your audio

func (*DialogMedia) SetAudioWriter deprecated

func (d *DialogMedia) SetAudioWriter(r io.Writer)

SetAudioWriter adds/changes audio reader. Use this when you want to have pipelines of your audio

Deprecated: Use AudioWriter options (WithAudioWriterDTMF, WithAudioWriterMonitor) to extend the pipeline; they keep the stable-handle chain consistent.

func (*DialogMedia) StartRTP deprecated

func (d *DialogMedia) StartRTP(rw int8, dur time.Duration) error

StartRTP clears the conn deadline (both directions). The dur parameter is ignored and kept only for signature compatibility.

Deprecated: Use the release function returned by PauseAudioRead / PauseAudioWrite.

func (*DialogMedia) StartStereoRecording added in v0.0.3

func (d *DialogMedia) StartStereoRecording(w io.WriteSeeker, opts ...RecordingOption) (*StereoRecording, error)

StartStereoRecording installs a stereo WAV recording tap into the dialog's audio pipeline and returns a handle to drive it. Both directions are decoded and interleaved into w at Close. Unlike the deprecated SetAudioReader/SetAudioWriter dance, the tap wraps the current reader and writer heads atomically under the media lock, so there is no half-wired window and no deprecated setter is touched.

It must be called before the dialog joins a Bridge: BridgeMix resolves each leg's reader/writer exactly once at AddDialogSession, so a tap installed afterwards is bypassed by bridged traffic. See docs/contracts.md.

The caller keeps ownership of w: StartStereoRecording and Close never close it. Close the underlying file (and, for a recording to a fresh file, handle its fd) after Close returns.

func (*DialogMedia) StopMusicOnHold added in v0.0.5

func (d *DialogMedia) StopMusicOnHold() error

StopMusicOnHold stops the running hold-music loop, if any, and waits for it to exit. It is a no-op returning nil when nothing is playing.

func (*DialogMedia) StopRTP deprecated

func (d *DialogMedia) StopRTP(rw int8, dur time.Duration) error

StopRTP pauses reading and/or writing by expiring the shared conn deadline. It is a durable, global state: any other component's StartRTP clears it for everyone.

Deprecated: Use PauseAudioRead / PauseAudioWrite, which are refcounted and scoped to this dialog's stable handles.

type DialogServerSession

type DialogServerSession struct {
	*sipgo.DialogServerSession

	// MediaSession *media.MediaSession
	DialogMedia
	// contains filtered or unexported fields
}

DialogServerSession represents inbound channel

func (*DialogServerSession) Answer

func (d *DialogServerSession) Answer(opts ...SignalOption) error

Answer creates media session and answers After this new AudioReader and AudioWriter are created for audio manipulation Options allow customizing Contact, headers, SDP body and media of the 200 OK response. Honors: msg (Headers, Contact, Body, MutateResponse), dialog (OnMediaUpdate, OnRefer); media overrides only apply when Answer creates the media session itself (no early media from ProgressMedia). NOTE: Not final API

func (*DialogServerSession) AnswerLate

func (d *DialogServerSession) AnswerLate(opts ...SignalOption) error

AnswerLate does answer with Late offer. Options allow customizing Contact, headers, SDP body and media of the 200 OK response. Honors: msg (Headers, Contact, Body, MutateResponse), media (all).

func (*DialogServerSession) AnswerOptions deprecated

func (d *DialogServerSession) AnswerOptions(opt AnswerOptions) error

AnswerOptions allows to answer dialog with options

Deprecated: Use Answer with SignalOptions

func (*DialogServerSession) Authorize added in v0.0.5

func (d *DialogServerSession) Authorize(s *DigestAuthServer, auth DigestAuth) error

Authorize challenges and validates the incoming INVITE with SIP digest authentication (RFC 2617).

Call it as the first thing in your serve handler:

dg.Serve(ctx, func(d *diago.DialogServerSession) error {
	if err := d.Authorize(digestAuthServer, diago.DigestAuth{Username: "u", Password: "p"}); err != nil {
		return err // 401 is sent; returning terminates the dialog
	}
	...
})

On the first call the INVITE transaction is answered 401 Unauthorized with a WWW-Authenticate challenge and a non-nil error is returned; the caller must re-INVITE with an Authorization header. Failed validation is answered the same way inside the transaction (401 carrying a fresh challenge for bad credentials or unknown/expired nonces, 400/403 for malformed credentials) and also returns a non-nil error. Successful validation sends no response - dialog processing (Trying, Ringing, Answer) continues normally.

func (*DialogServerSession) Close

func (d *DialogServerSession) Close() error

Close frees local resources (media stack and dialog cleanup hooks). It is idempotent and does NOT send any SIP message. Server dialogs are closed by the framework when the serve handler returns (docs/contracts.md §6).

func (*DialogServerSession) DialogSIP

func (d *DialogServerSession) DialogSIP() *sipgo.Dialog

func (*DialogServerSession) FromUser

func (d *DialogServerSession) FromUser() string

func (*DialogServerSession) Hangup

func (d *DialogServerSession) Hangup(ctx context.Context, opts ...SignalOption) error

Hangup terminates dialog. When dialog is confirmed BYE is sent, otherwise the INVITE is declined with 480 (and nil is returned — declining succeeded). Options allow customizing headers (ex. Reason), Contact and body of the outgoing message. See docs/contracts.md §7 for the full matrix. Honors: msg (Headers, Contact, MutateRequest on BYE, MutateResponse on 480 decline); Body is ignored.

func (*DialogServerSession) Hold

func (d *DialogServerSession) Hold(ctx context.Context, opts ...SignalOption) error

Hold puts dialog on hold (media sendonly). Options allow customizing the re-INVITE. Honors: msg (Headers, Contact, Body, MutateRequest); media: WithMusicOnHold selects the hold music started automatically after the re-INVITE succeeds (falling back to MediaConfig.MusicOnHold); other media overrides are not consumed.

The automatic music runs detached from the Hold call's context (which typically carries the re-INVITE timeout) until Unhold, Stop/StopMusicOnHold, or dialog Close; when the dialog-level default is unset (no MusicOnHold configured anywhere) Hold behaves as before and plays nothing.

func (*DialogServerSession) ID added in v0.0.6

func (d *DialogServerSession) ID() string

func (*DialogServerSession) Progress deprecated

func (d *DialogServerSession) Progress() error

Progress sends 100 trying.

Deprecated: Use Trying. It will change behavior to 183 Session Progress in future releases

func (*DialogServerSession) ProgressMedia

func (d *DialogServerSession) ProgressMedia(opts ...SignalOption) error

ProgressMedia sends 183 Session Progress and creates early media

Honors: msg (Headers, Contact, Body, MutateResponse), media (all).

Experimental: Naming of API might change

func (*DialogServerSession) ProgressMediaOptions deprecated

func (d *DialogServerSession) ProgressMediaOptions(opt ProgressMediaOptions) error

ProgressMediaOptions sends 183 Session Progress with options.

Deprecated: Use ProgressMedia with SignalOptions

func (*DialogServerSession) ReInvite

func (d *DialogServerSession) ReInvite(ctx context.Context, opts ...SignalOption) error

ReInvite sends a re-INVITE with the current media session. Honors: msg (Headers, Contact, Body, MutateRequest); media overrides are not consumed. The exchange (491 retry, 2xx ACK) is driven by reInviteExchange, shared with the client dialog.

func (*DialogServerSession) ReadAck

func (*DialogServerSession) Refer

func (d *DialogServerSession) Refer(ctx context.Context, referTo sip.Uri, opts ...SignalOption) error

Refer tries todo refer (blind transfer) on call. Options allow customizing headers of the REFER request and receiving the transfer status via WithOnReferNotify. Honors: msg (Headers, Contact, MutateRequest); dialog (OnReferNotify via WithOnReferNotify).

NOTE: It is expected that after calling this you are hanguping call to send BYE

func (*DialogServerSession) RemoteContact

func (d *DialogServerSession) RemoteContact() *sip.ContactHeader

func (*DialogServerSession) RespondSDP

func (d *DialogServerSession) RespondSDP(body []byte, opts ...SignalOption) error

RespondSDP responds with 200 OK and provided SDP body. Options can customize status headers and Contact of the response. Honors: msg (Headers, Contact, MutateResponse); Body is ignored (use the body argument).

func (*DialogServerSession) Ringing

func (d *DialogServerSession) Ringing(opts ...SignalOption) error

Ringing sends 180 Ringing. Honors: msg (Headers, Contact, MutateResponse); Body is ignored.

func (*DialogServerSession) ToUser

func (d *DialogServerSession) ToUser() string

User that was dialed

func (*DialogServerSession) Transport

func (d *DialogServerSession) Transport() string

func (*DialogServerSession) Trying

func (d *DialogServerSession) Trying(opts ...SignalOption) error

Trying sends 100 Trying. Honors: msg (Headers, Contact, MutateResponse); Body is ignored.

func (*DialogServerSession) Unhold

func (d *DialogServerSession) Unhold(ctx context.Context, opts ...SignalOption) error

Unhold takes dialog back from hold (media sendrecv). Options allow customizing the re-INVITE. Honors: msg (Headers, Contact, Body, MutateRequest); the music started automatically by Hold is stopped on success; other media overrides are not consumed.

type DialogSession

type DialogSession interface {
	ID() string
	Context() context.Context
	// Hangup terminates the dialog. Options allow customizing headers of the
	// outgoing BYE / decline response (ex. Reason header)
	Hangup(ctx context.Context, opts ...SignalOption) error
	Media() *DialogMedia
	DialogSIP() *sipgo.Dialog
	Do(ctx context.Context, req *sip.Request) (*sip.Response, error)
	Close() error
}

type DigestAuth

type DigestAuth struct {
	Username string
	Password string
	Realm    string
	Expire   time.Duration

	// Algorithms are the digest hash algorithms advertised in the 401
	// challenge (RFC 8760). One WWW-Authenticate challenge is issued per
	// algorithm, each with its own nonce; the client picks one to respond
	// with. Must be supported by github.com/icholy/digest: MD5, SHA-256,
	// SHA-512, SHA-512-256. Defaults to MD5 when empty.
	Algorithms []string
}

type DigestAuthServer

type DigestAuthServer struct {
	// contains filtered or unexported fields
}

func NewDigestServer

func NewDigestServer() *DigestAuthServer

func (*DigestAuthServer) AuthorizeDialog

func (s *DigestAuthServer) AuthorizeDialog(d *DialogServerSession, auth DigestAuth) error

func (*DigestAuthServer) AuthorizeRequest

func (s *DigestAuthServer) AuthorizeRequest(req *sip.Request, auth DigestAuth) (res *sip.Response, err error)

AuthorizeRequest authorizes request. Returns SIP response that can be passed with error

func (*DigestAuthServer) Close

func (s *DigestAuthServer) Close()

type InviteClientOptions deprecated

type InviteClientOptions struct {
	Originator DialogSession
	OnResponse func(res *sip.Response) error
	// OnMediaUpdate called when media is changed.
	// NOTE: you should not block this call as it blocks response processing.
	OnMediaUpdate func(d *DialogMedia)
	// OnRefer is called on successfull REFER handling
	//
	// It creates new dialog (NewDialog) on which you need to call Invite() and Ack()
	// Any error from invite, ack or other processing should be returned for correct Notify handling
	//
	// NOTE: IT is SCOPED to handler and exiting handler will Close/Terminate this dialog!
	OnRefer OnReferDialogFunc
	// For digest authentication
	Username string
	Password string

	// Custom headers to pass. DO NOT SET THIS to nil
	Headers []sip.Header
	// Stop on early media. ErrClientEarlyMedia will be returned
	EarlyMediaDetect bool
}

InviteClientOptions is passed on dialog client Invite with extra control over dialog

Deprecated: Use Invite with SignalOptions. Convert existing struct with Options()

func (*InviteClientOptions) Options

func (o *InviteClientOptions) Options() ([]SignalOption, error)

Options converts legacy options into SignalOptions

func (*InviteClientOptions) WithAnonymousCaller deprecated

func (o *InviteClientOptions) WithAnonymousCaller()

WithAnonymousCaller sets from user Anonymous per RFC

Deprecated: Use Invite with WithHeaders and sip.FromHeader

func (*InviteClientOptions) WithCaller deprecated

func (o *InviteClientOptions) WithCaller(displayName string, callerID string, host string)

WithCaller allows simpler way modifying caller

Deprecated: Use Invite with WithHeaders and sip.FromHeader

type InviteOptions deprecated

type InviteOptions struct {
	Originator DialogSession
	OnResponse func(res *sip.Response) error
	Transport  string
	// For digest authentication
	Username string
	Password string
	// Custom headers to pass. DO NOT SET THIS to nil
	Headers []sip.Header
}

InviteOptions is the legacy per-call option struct for Invite.

Deprecated: Use Diago.Invite with SignalOptions; convert existing structs with Options().

func (*InviteOptions) Options

func (o *InviteOptions) Options() ([]SignalOption, error)

Options converts legacy options into SignalOptions

type MediaConfig

type MediaConfig struct {
	Codecs []media.Codec
	// Currently supported Single. Check media.SRTP... constants
	// Experimental
	SecureRTPAlg uint16
	// SecureRTP 0 - none, 1 - sdes
	SecureRTP int
	// BindIP is local IP used to bind RTP/RTCP listeners.
	// When nil the interfaces IP is resolved on session creation.
	BindIP net.IP
	// ExternalIP is the IP advertised inside SDP (c= line).
	ExternalIP net.IP
	// RTPNAT 0 - disabled, 1 - learn source. Check media.RTPNAT options
	RTPNAT int
	// DTLSConf used for DTLS-SRTP
	DTLSConf media.DTLSConfig

	// RTPPortStart/RTPPortEnd define the local RTP port range for media
	// sessions created from this config. Ports are picked in steps of 2 so
	// the RTCP port (RTP+1) fits. Zero values inherit the media package
	// globals media.RTPPortStart/RTPPortEnd (0 = ephemeral ports with retry).
	RTPPortStart int
	RTPPortEnd   int
	// SDPCodecPreferLocalOrder makes the answerer order common codecs by
	// local preference instead of the offerer order (RFC 3264 default).
	// Zero inherits media.SDPCodecPreferLocalOrder.
	SDPCodecPreferLocalOrder int
	// SDPSessionName overrides the SDP "s=" session-name line. Empty keeps
	// the library default "Sip Go Media".
	SDPSessionName string

	// MusicOnHold is the dialog-level default hold music: Hold() starts
	// looping it automatically once the hold re-INVITE succeeds. Zero value
	// (no segments) disables automatic hold music; Hold then behaves as
	// before. Per-call override: WithMusicOnHold; manual control:
	// DialogMedia.PlayMusicOnHold/StopMusicOnHold.
	MusicOnHold audio.Tone
}

type MediaProps

type MediaProps struct {
	Codec media.Codec
	Laddr string
	Raddr string
}

type MoHOption added in v0.0.5

type MoHOption func(*mohConfig) error

MoHOption tunes DialogMedia.PlayMusicOnHold.

func WithMoHTone added in v0.0.5

func WithMoHTone(tone audio.Tone) MoHOption

WithMoHTone sets the hold music source, overriding the dialog-level MediaConfig.MusicOnHold default for this loop.

type MusicOnHold added in v0.0.5

type MusicOnHold struct {
	// contains filtered or unexported fields
}

MusicOnHold is a handle to a running hold-music loop on one dialog. Create it with DialogMedia.PlayMusicOnHold and stop it with Stop (or DialogMedia.StopMusicOnHold). The handle is safe for concurrent use.

The loop re-resolves the dialog writer and negotiated codec every frame (docs/contracts.md §4), so it survives re-INVITEs and re-renders the tone at a changed sample rate without a resampler. It assumes the exclusive write path while active (docs/contracts.md §5 single-writer rule): stop other playback before starting hold music.

func (*MusicOnHold) Done added in v0.0.5

func (m *MusicOnHold) Done() <-chan struct{}

Done is closed when the loop exits — on Stop, on ctx cancellation, or when the loop fails on its own (query the error via Stop).

func (*MusicOnHold) Stop added in v0.0.5

func (m *MusicOnHold) Stop() error

Stop cancels the loop and waits for it to exit. It is idempotent and safe from any goroutine. A loop ended by Stop, by its context, or by the dialog closing returns nil; a loop that failed on its own returns that error.

type NewDialogOptions deprecated

type NewDialogOptions struct {
	// Transport or protocol that should be used
	Transport string
	// TransportID matches diago transport by ID instead protocol
	TransportID string
}

NewDialogOptions is the legacy option struct for NewDialog.

Deprecated: Use NewDialog with SignalOptions; convert existing structs with Options().

func (*NewDialogOptions) Options

func (o *NewDialogOptions) Options() []SignalOption

Options converts legacy options into SignalOptions

type OnReferDialogFunc

type OnReferDialogFunc func(referDialog *DialogClientSession) error

type PlaybackDTMFOption

type PlaybackDTMFOption func(*AudioPlaybackDTMF) error

PlaybackDTMFOption configures AudioPlaybackDTMF

func WithInterruptKeys

func WithInterruptKeys(keys string) PlaybackDTMFOption

WithInterruptKeys sets DTMF keys that interrupt playback. Default is any key interrupts. Empty string disables interrupting.

func WithOnDTMF

func WithOnDTMF(fn func(dtmf rune)) PlaybackDTMFOption

WithOnDTMF registers additional callback invoked on every received DTMF. It is executed on RTP reading goroutine and MUST NOT block.

func WithReplayKeys

func WithReplayKeys(keys string) PlaybackDTMFOption

WithReplayKeys sets DTMF keys that replay playback from the beginning. Default is no replay keys.

type PlaybackState

type PlaybackState uint32

PlaybackState represents current playback state of AudioPlaybackControl

const (
	PlaybackStateIdle PlaybackState = iota
	PlaybackStatePlaying
	PlaybackStatePaused
	PlaybackStateStopped
)

func (PlaybackState) String

func (s PlaybackState) String() string

type ProgressMediaOptions deprecated

type ProgressMediaOptions struct {
	// Codecs that will be used
	Codecs []media.Codec

	// RTPNAT exposes MediaSession property
	RTPNAT int
}

ProgressMediaOptions is the legacy option struct for ProgressMedia.

Deprecated: Use ProgressMedia with SignalOptions.

type RecordingOption added in v0.0.3

type RecordingOption func(*recordingConfig) error

RecordingOption configures DialogMedia.StartStereoRecording.

func WithRecordingFailOpen added in v0.0.3

func WithRecordingFailOpen(b bool) RecordingOption

WithRecordingFailOpen controls what a PCM write failure (disk full, IO degradation) does to the call. Default is fail-open: the tap stops taking writes, the media pipeline keeps flowing, and the error surfaces through StereoRecording.Err and Close. Pass false to propagate the write error into the reader/writer chain instead (the pre-existing tap behaviour), which lets a full disk interrupt bridged media.

func WithRecordingSpoolDir added in v0.0.3

func WithRecordingSpoolDir(dir string) RecordingOption

WithRecordingSpoolDir sets the directory holding the two per-direction raw PCM spool files the stereo monitor interleaves at Close. Empty (default) uses os.TempDir(). Point it at the same partition as the WAV output to keep recording IO local and to size disk headroom against a single spool.

type RegisterResponseError

type RegisterResponseError struct {
	RegisterReq *sip.Request
	RegisterRes *sip.Response

	Msg string
}

func (RegisterResponseError) Error

func (e RegisterResponseError) Error() string

func (*RegisterResponseError) StatusCode

func (e *RegisterResponseError) StatusCode() int

type RegisterTransaction

type RegisterTransaction struct {
	Origin *sip.Request
	// contains filtered or unexported fields
}

func (*RegisterTransaction) Qualify

func (t *RegisterTransaction) Qualify(ctx context.Context, opts ...SignalOption) error

Qualify refreshes the registration. Options allow customizing the request. Honors: msg (Headers, Contact, MutateRequest), dialog (Username, Password).

func (*RegisterTransaction) QualifyLoop

func (t *RegisterTransaction) QualifyLoop(ctx context.Context) error

func (*RegisterTransaction) Register

func (t *RegisterTransaction) Register(ctx context.Context, opts ...SignalOption) error

Register sends the initial REGISTER. Options allow customizing Contact, extra headers and the final request of this registration attempt. Honors: msg (Headers, Contact, MutateRequest), dialog (Username, Password); credentials fall back to the transaction ones when not provided via options.

func (*RegisterTransaction) Unregister

func (t *RegisterTransaction) Unregister(ctx context.Context, opts ...SignalOption) error

Unregister unregisters the contact. Options allow customizing the request. Honors: msg (Headers, Contact, MutateRequest), dialog (Username, Password).

type ServeDialogFunc

type ServeDialogFunc func(d *DialogServerSession)

type SignalDialogParams

type SignalDialogParams struct {
	// Transport selects the transport by name ("udp", "tcp", ...). NewDialog only.
	Transport string
	// TransportID selects the transport by its configured ID. NewDialog only.
	TransportID string

	// Originator reuses SDP/codecs of another dialog to avoid transcoding. Invite only.
	Originator DialogSession
	// Username/Password for digest authentication. Invite/Register only.
	Username string
	Password string
	// EarlyMediaDetect stops dialog establishment when 183 Session Progress
	// with SDP is received. ErrClientEarlyMedia is returned. Invite only.
	EarlyMediaDetect bool

	// OnResponse is invoked for responses during dialog establishment (client side).
	OnResponse func(res *sip.Response) error
	// OnMediaUpdate is called on media updates (re-INVITE).
	OnMediaUpdate func(d *DialogMedia)
	// OnRefer is called on successful REFER handling.
	OnRefer OnReferDialogFunc
	// OnReferNotify receives the transfer status (NOTIFY sipfrag code) of an
	// outgoing REFER sent with Refer. Refer only.
	OnReferNotify func(statusCode int)
}

SignalDialogParams controls dialog establishment and lifecycle callbacks.

type SignalMediaParams

type SignalMediaParams struct {
	Codecs          []media.Codec
	RTPNAT          *int
	MediaBindIP     net.IP
	MediaExternalIP net.IP
	MediaDTLSConf   *media.DTLSConfig
	// SDPSessionName overrides the SDP "s=" line for this call only. Empty
	// means "no per-call change"; check is the caller's responsibility.
	SDPSessionName string

	// MusicOnHold overrides the hold music consumed by Hold/Unhold. Non-nil
	// is an explicit per-call choice: a tone with segments replaces the
	// dialog-level default, a zero tone (no segments) disables music for the
	// call. Nil means "no per-call change". On Invite/Answer it is persisted
	// as the dialog-level default (via signalMediaConfig); on Hold it applies
	// to that hold only.
	MusicOnHold *audio.Tone

	// MediaSession allows passing a fully custom/pre-created media session.
	// When set the library skips its own media session creation and uses this one.
	MediaSession *media.MediaSession
}

SignalMediaParams overrides the per-call media configuration on top of the dialog media config. Precedence: MediaSession > granular options > dialog defaults. Granular fields are silently ignored when MediaSession is set, since the caller takes full ownership of the session.

type SignalMsgParams

type SignalMsgParams struct {
	// Headers are appended to the outgoing SIP message (request or response).
	// Nil headers are skipped.
	Headers []sip.Header

	// Contact replaces the default Contact header:
	// - on server responses (Trying/Ringing/ProgressMedia/Answer/AnswerLate/RespondSDP...)
	// - on client requests (Invite/ReInvite/Ack/Hangup...)
	// When set, the library default Contact header is NOT added.
	Contact *sip.ContactHeader

	// Body overrides the outgoing body (usually a custom SDP).
	// When the outgoing message carries a body and no Content-Type header is
	// provided, "application/sdp" is added automatically.
	// Body is only emitted by methods that send a body (ProgressMedia, Answer,
	// AnswerLate, RespondSDP); Trying/Ringing always ignore it.
	Body []byte

	// MutateRequest is the last-chance hook invoked just before a request is sent.
	MutateRequest func(req *sip.Request) error
	// MutateResponse is the last-chance hook invoked just before a response is sent.
	MutateResponse func(res *sip.Response) error
}

SignalMsgParams shapes the outgoing SIP message.

type SignalOption

type SignalOption func(*SignalParams) error

SignalOption configures per-call signaling behavior of diago APIs. It is accepted by all signaling methods (server, client, NewDialog and REGISTER).

func WithAuthCredentials

func WithAuthCredentials(username string, password string) SignalOption

WithAuthCredentials sets username/password used for digest authentication. Invite/Register only.

func WithBody

func WithBody(body []byte) SignalOption

WithBody overrides the outgoing body. Content-Type defaults to "application/sdp" unless provided within Headers.

func WithCodecs

func WithCodecs(codecs ...media.Codec) SignalOption

WithCodecs overrides the codecs offered in the SDP for this call.

func WithContact

func WithContact(contact *sip.ContactHeader) SignalOption

WithContact replaces the default Contact header of the outgoing message. On server side it applies to responses, on client side to requests.

func WithDialogTransport

func WithDialogTransport(name string) SignalOption

WithDialogTransport selects the transport used for a new dialog by name (udp, tcp, ...). NewDialog only.

func WithDialogTransportID

func WithDialogTransportID(id string) SignalOption

WithDialogTransportID selects the transport used for a new dialog by its configured ID. NewDialog only.

func WithEarlyMediaDetect

func WithEarlyMediaDetect() SignalOption

WithEarlyMediaDetect enables early media detection on outgoing INVITE. Invite returns ErrClientEarlyMedia when 183 Session Progress with SDP is received. Invite only.

func WithHeader

func WithHeader(name string, value string) SignalOption

WithHeader is a convenience wrapper appending a single header by name and value.

func WithHeaders

func WithHeaders(headers ...sip.Header) SignalOption

WithHeaders appends custom headers to the outgoing SIP message.

func WithMediaBindIP

func WithMediaBindIP(ip net.IP) SignalOption

WithMediaBindIP overrides the local RTP/RTCP bind IP for this call.

func WithMediaDTLS

func WithMediaDTLS(conf media.DTLSConfig) SignalOption

WithMediaDTLS overrides the DTLS configuration for this call.

func WithMediaExternalIP

func WithMediaExternalIP(ip net.IP) SignalOption

WithMediaExternalIP overrides the IP advertised in the SDP (c= line) for this call.

func WithMediaSDPSessionName added in v0.0.4

func WithMediaSDPSessionName(name string) SignalOption

WithMediaSDPSessionName overrides the SDP "s=" session-name line for this call only. Overlays MediaConfig.SDPSessionName; the empty string is rejected (it carries no information and is indistinguishable from "not set"), as are line breaks — an "s=" value must stay on one SDP line.

func WithMediaSession

func WithMediaSession(m *media.MediaSession) SignalOption

WithMediaSession passes a fully custom/pre-created media session. The library will use it as is instead of creating its own session.

func WithMusicOnHold added in v0.0.5

func WithMusicOnHold(tone audio.Tone) SignalOption

WithMusicOnHold sets the hold music played by Hold for this call, replacing the dialog-level MediaConfig.MusicOnHold. A zero tone (no segments) explicitly disables hold music for the call. On Invite/Answer it becomes the dialog-level default; on Hold it applies to that hold only.

func WithOnMediaUpdate

func WithOnMediaUpdate(fn func(d *DialogMedia)) SignalOption

WithOnMediaUpdate sets the media update callback (re-INVITE handling).

func WithOnRefer

func WithOnRefer(fn OnReferDialogFunc) SignalOption

WithOnRefer sets the REFER handler callback.

func WithOnReferNotify added in v0.0.6

func WithOnReferNotify(fn func(statusCode int)) SignalOption

WithOnReferNotify sets the callback receiving the transfer status (NOTIFY sipfrag code) of an outgoing REFER sent with Refer. Refer only.

func WithOnRegistered added in v0.0.6

func WithOnRegistered(fn func()) SignalOption

WithOnRegistered sets the callback invoked after a successful initial REGISTER. Register only.

func WithOnResponse

func WithOnResponse(fn func(res *sip.Response) error) SignalOption

WithOnResponse sets a response callback used during dialog establishment (client side).

func WithOriginator

func WithOriginator(o DialogSession) SignalOption

WithOriginator sets the originator dialog whose SDP/codecs are reused for the outgoing INVITE, avoiding media transcoding. Invite only.

func WithRTPNAT

func WithRTPNAT(n int) SignalOption

WithRTPNAT sets media.MediaSession.RTPNAT for this call. Check media.RTPNAT options.

func WithRegisterAllowHeaders added in v0.0.6

func WithRegisterAllowHeaders(headers ...string) SignalOption

WithRegisterAllowHeaders sets the Allow header values sent with REGISTER. Register only.

func WithRegisterExpiry added in v0.0.6

func WithRegisterExpiry(d time.Duration) SignalOption

WithRegisterExpiry sets the Expires header value of the REGISTER requests of this transaction. Zero (default) sends no Expires header unless the server provides one. Register only.

func WithRegisterProxyHost added in v0.0.6

func WithRegisterProxyHost(host string) SignalOption

WithRegisterProxyHost overrides the REGISTER request destination. Register only.

func WithRegisterRetryInterval added in v0.0.6

func WithRegisterRetryInterval(d time.Duration) SignalOption

WithRegisterRetryInterval sets a fixed interval before the next REGISTER is sent. Zero (default) derives it from the negotiated expiry. Register only.

func WithRequestMutator

func WithRequestMutator(fn func(req *sip.Request) error) SignalOption

WithRequestMutator registers a last-chance hook invoked with the outgoing request just before it is sent. Use it for anything not covered by dedicated options.

func WithResponseMutator

func WithResponseMutator(fn func(res *sip.Response) error) SignalOption

WithResponseMutator registers a last-chance hook invoked with the outgoing response just before it is sent. Use it for anything not covered by dedicated options.

type SignalParams

type SignalParams struct {
	// Msg shapes the outgoing SIP message (request or response).
	Msg SignalMsgParams
	// Media overrides the per-call media configuration.
	Media SignalMediaParams
	// Dialog controls dialog establishment and lifecycle callbacks.
	Dialog SignalDialogParams
	// Register configures REGISTER transactions. Register only.
	Register SignalRegisterParams
}

SignalParams carries per-call signaling customizations applied by SignalOption. It is constructed by the library (newSignalParams); user code customizes it through the With* constructors or custom option closures. A nil *SignalParams means "use defaults". Not all groups apply to every API; fields irrelevant to the called method are ignored, and each method's godoc states what it honors.

type SignalRegisterParams added in v0.0.6

type SignalRegisterParams struct {
	// Expiry is for Expire header
	Expiry time.Duration
	// RetryInterval is interval before next Register is sent
	RetryInterval time.Duration
	// AllowHeaders lists the Allow header values sent with REGISTER
	AllowHeaders []string
	// ProxyHost overrides the REGISTER request destination
	ProxyHost string

	// OnRegistered is called after a successful initial REGISTER
	OnRegistered func()
}

SignalRegisterParams configures a REGISTER transaction. All fields are construction-time: they are read when the transaction is created with Diago.Register/RegisterTransaction and have no effect when passed to the per-request methods (Register/Unregister/Qualify). Register only.

type StereoRecording added in v0.0.3

type StereoRecording struct {
	// contains filtered or unexported fields
}

StereoRecording is an active inline recording tap returned by DialogMedia.StartStereoRecording. All methods are safe for concurrent use.

func (*StereoRecording) Close added in v0.0.3

func (r *StereoRecording) Close() error

Close stops collection, flushes and interleaves the two PCM spools into the WAV, rewrites the WAV header, and uninstalls the tap from the audio pipeline. It does not close the caller's writer. Close is idempotent; the second call is a no-op returning nil.

Uninstall only removes the tap while it is still the outermost head. If a Bridge wrapped the chain on top after Start, the (now stopped, hence transparent) tap stays in place rather than surgically breaking the outer chain.

func (*StereoRecording) Err added in v0.0.3

func (r *StereoRecording) Err() error

Err returns the first recording write error swallowed by the fail-open policy, or nil. A non-nil Err means the recording is degraded (the call is unaffected); Close reports finalization errors too.

func (*StereoRecording) Pause added in v0.0.3

func (r *StereoRecording) Pause() error

Pause stops PCM collection on both directions while bridged media keeps flowing untouched. Resume re-enables it; the paused interval is padded with silence so both channels stay aligned with the call timeline - a paused interval appears as silence in the final WAV. Both return ErrRecordingClosed after Close.

func (*StereoRecording) Resume added in v0.0.3

func (r *StereoRecording) Resume() error

Resume continues PCM collection after Pause. See Pause.

type ToneOption added in v0.0.4

type ToneOption func(*toneConfig) error

ToneOption tunes DialogMedia.PlayTone.

func WithToneLoop added in v0.0.4

func WithToneLoop() ToneOption

WithToneLoop replays the tone until the context is canceled. Without it the tone plays once.

func WithToneVolume added in v0.0.4

func WithToneVolume(scale float64) ToneOption

WithToneVolume scales every segment volume (0..1, values above 1 are clamped). Default tone volume is applied when segments set none.

type Transport

type Transport struct {
	ID string

	// Transport must be udp,tcp or ws, or even forcing v4 like udp4, tcp4
	Transport string

	// BindHost sets IP to bind. If specified (not 0.0.0.0) it will be used same for media IP unless MediaExternalIP is set.
	BindHost string
	// BindPort sets port to bind. Leaving at 0 will use empheral port and apply on Contact addr
	BindPort int

	ExternalHost string // SIP signaling and media external addr
	ExternalPort int

	// MediaExternalIP changes SDP IP, by default it tries to use external host if it is IP defined
	MediaExternalIP net.IP
	// MediaSRTP offers SRTP. Values: 0-none, 1-sdes
	MediaSRTP int

	MediaDTLSConf media.DTLSConfig

	// In case TLS protocol
	TLSConf *tls.Config
	// Avoiding SIPS in contact uri https://datatracker.ietf.org/doc/html/rfc5630#section-3.3
	TLSURINoSIPS bool

	RewriteContact bool
	// contains filtered or unexported fields
}

Directories

Path Synopsis
183ringing command
SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024, Emir Aganovic
SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024, Emir Aganovic
auth_server command
bridge command
bridge_mix command
dtmf command
moh command
playback command
playback_dtmf command
readmedia command
register command
wav_record command
SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024, Emir Aganovic
SPDX-License-Identifier: MPL-2.0 SPDX-FileCopyrightText: Copyright (c) 2024, Emir Aganovic
sdp

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL