pub struct MediaSession { /* private fields */ }Expand description
A running media session.
Implementations§
Source§impl MediaSession
impl MediaSession
Sourcepub async fn start(bind: SocketAddr, config: Config) -> Result<Self, StartError>
pub async fn start(bind: SocketAddr, config: Config) -> Result<Self, StartError>
Bind a socket and start the session in one step.
Only for callers that already know the far end — an answerer, which has the offer in
hand. A caller making the offer needs MediaPort instead.
§Errors
Returns StartError::Setup before binding for invalid timing or codec construction,
and StartError::Io if the media sockets cannot be bound.
Sourcepub fn local_addr(&self) -> SocketAddr
pub fn local_addr(&self) -> SocketAddr
The address media arrives on, for the SDP.
Sourcepub fn local_ssrc(&self) -> u32
pub fn local_ssrc(&self) -> u32
This session generation’s local RTP synchronisation source.
Sourcepub fn set_rtcp_quality_hook(&self, hook: Option<RtcpQualityHook>)
pub fn set_rtcp_quality_hook(&self, hook: Option<RtcpQualityHook>)
Install or clear the application callback for peer RTCP quality reports.
The slot is shared by this session’s RTP/RTCP workers. Registering a callback does not
enable RTCP when Config::rtcp_interval is None.
Sourcepub fn rtcp_quality_hook(&self) -> Option<RtcpQualityHook>
pub fn rtcp_quality_hook(&self) -> Option<RtcpQualityHook>
The callback currently installed for peer RTCP quality reports.
Sourcepub fn runs_ice(&self) -> bool
pub fn runs_ice(&self) -> bool
Whether ICE is driving this stream’s path.
The signalling layer’s question before it builds any later description: RFC 8839 §4.4 makes the ICE attributes mandatory on every subsequent offer and answer for a stream doing ICE, and §6 makes their absence mean the peer has stopped. A session carrying no agent must therefore not grow ICE attributes on a re-offer, and one carrying an agent must not lose them.
Sourcepub fn ice_path(&self) -> IcePath
pub fn ice_path(&self) -> IcePath
The candidate path ICE actually selected for RTP.
Checking is honest intermediate state: an ICE exchange was negotiated, but no nominated
pair has replaced the default destination yet. A terminal diagnostic can therefore report
what happened without inferring it from the policy that was requested.
Sourcepub fn browser_component(&self) -> Option<BrowserComponentSnapshot>
pub fn browser_component(&self) -> Option<BrowserComponentSnapshot>
Security and nominated-pair facts for a browser-audio component.
Sourcepub async fn reconfigure(&mut self, config: Config) -> Result<bool, SetupError>
pub async fn reconfigure(&mut self, config: Config) -> Result<bool, SetupError>
Rebuild codec and packet workers on this session’s existing sockets.
Used when a later SDP exchange changes the remote address, codec, payload type, or keys. The local RTP/RTCP addresses do not change: they are already published to the peer, and a replacement that rebound an ephemeral port would make the new description false. Mute and encoded-relay policy survive the transition. The stopped generation remains owned until all of its workers have joined; if this future is cancelled during that join, the next reconfiguration or shutdown resumes the cleanup.
Returns false without changing the session when ICE owns the destinations. Rebuilding an
ICE-backed session requires the agent and its selected pair to move with the workers; a
caller must refuse that renegotiation rather than silently fall back to an unchecked path.
§Errors
Returns SetupError before stopping the current workers if the new timing or codec
cannot be constructed.
Sourcepub async fn renegotiate_ice(
&self,
local: Option<(Credentials, u64)>,
peer: Option<&Negotiation>,
) -> Option<Local>
pub async fn renegotiate_ice( &self, local: Option<(Credentials, u64)>, peer: Option<&Negotiation>, ) -> Option<Local>
Apply a later exchange’s ICE half, and read back what this side must now signal
(RFC 8839 §4.4; docs/specs/ice.md §13.5).
local carries fresh credentials and a fresh tiebreaker when this exchange is a restart —
§4.4.1.1.1 says a new ICE session, and the answer to one names the answerer’s own new
credentials rather than the ones the finished session keyed its checks with. None is
every ordinary re-offer: hold, resume, a codec change, a session refresh. peer is the
description that arrived, when one has.
Returns None when this stream is not running ICE, or when the driver has already stopped.
Both mean the same thing to a caller: answer without ICE attributes rather than block on a
session that is ending.
Whether the peer’s half is a restart is deliberately not asked here. That is §4.4.1.1.1’s question about the peer’s two credentials, the agent has always answered it, and a second implementation of it on this side would be a second thing to keep right.
Sourcepub async fn send(&self, samples: Vec<i16>) -> bool
pub async fn send(&self, samples: Vec<i16>) -> bool
Queue one packet’s worth of samples.
Queued rather than sent: the pacing timer decides when it goes out.
Sourcepub async fn send_digit(&self, digit: Digit, duration: Duration) -> bool
pub async fn send_digit(&self, digit: Digit, duration: Duration) -> bool
Send a DTMF digit, held for duration.
The packets go through the same paced queue as audio, so the tone occupies the slots audio would have. That is deliberate: RFC 4733 events replace the audio for their duration rather than being sent alongside it, and sending both means the far end hears the keypress twice.
Sourcepub async fn recv_digit(&self) -> Option<(Digit, Duration)>
pub async fn recv_digit(&self) -> Option<(Digit, Duration)>
Take the next DTMF digit the far end pressed, and how long it was held.
The duration comes from the RFC 4733 event itself (its duration field, converted from
the negotiated clock rate to wall-clock time), not from timing our own arrival: the event
carries the sender’s own clock, and measuring anything else would make the number depend
on jitter rather than on how long the key was actually down.
Sourcepub async fn collect_digits(&self, within: Duration, gap: Duration) -> String
pub async fn collect_digits(&self, within: Duration, gap: Duration) -> String
Collect the digits the far end presses, for at most within, stopping once it has been
quiet for gap.
Two questions, two bounds — the same split Self::record_at_least made on the audio
path, and the reason this takes two durations rather than one.
within bounds the wait for the first digit, and with it the whole collection. It is a
bound on failure: how long this side is prepared to wait before concluding no digits
are coming, so it belongs an order of magnitude above the honest answer — a whole call’s
worth, typically — rather than close to it. Nothing about it is a measurement: how long a
caller takes to press the first key is a property of the caller, and how long the keypress
takes to get here is a property of the machines between them.
gap is a definition of silence: how long a caller has to leave a hole for the
dialling to be treated as finished. It is the only question a fixed window can answer here,
and it can only be asked once a digit has arrived, because a caller who has not dialled is
not a caller who has stopped dialling.
Whatever was collected is returned, including nothing. A collection cut short by within
keeps the digits it already has.
§Inferring the end of the dialling (M-34)
RFC 4733 carries keypresses, not a completion signal: there is no “the caller is done”
event to wait for, so the digits ended is always this side’s inference from silence, and
gap is the whole of that inference. What makes it safe to draw is that the input it draws
on is exact rather than approximate. A digit is delivered here once, when the first
packet carrying that tone’s end bit arrives; the tone is identified by its own RTP
timestamp, which is constant across every packet of the tone, so the end retransmissions
RFC 4733 §2.5.1.3 asks for are absorbed rather than counted again, and “44” is told from a
single long “4” by the timestamp changing. So a gap that elapses means no keypress
completed in it — never that a packet was missed mid-tone.
A digit that arrives a millisecond after gap expires is not lost — up to the 32 the
keypress channel holds, past which the receive loop drops rather than blocks, deliberately
and by the same reasoning as every other queue here. Within that bound it stays queued and
is the first digit the next Self::recv_digit or collect_digits yields. It is in the
wrong collection, though, and no wall clock can fix that — which is why gap is set past
any plausible scheduling delay rather than close to the spacing digits actually arrive
with, and why an application that knows how many digits it wants should stop at that count
with Self::recv_digit instead of waiting for a silence at all.
§Why this takes two durations (M-34)
It used to take one, spent on both questions, and that is the defect X-40 measured one
layer up: a single window covering both “has it started” and “has it ended” is beaten by
whichever of the two is slower on the day, and the result is not a degraded collection but
an empty one, since the loop ends before its first iteration. sipx answer produced a
valid recording of zero samples that way. Widening the single window would have moved that
cliff rather than removed it, and left the same defect for a slower caller.
Sourcepub fn wire_payload_type(&self) -> u8
pub fn wire_payload_type(&self) -> u8
The payload type this negotiated stream puts on the wire.
Static codecs usually return their assigned number. Dynamic codecs return the number from the negotiated description, which need not be the number this endpoint prefers in an offer.
Sourcepub fn receive_payload_type(&self) -> u8
pub fn receive_payload_type(&self) -> u8
The payload type this negotiated stream accepts from the wire.
Sourcepub fn dtmf_payload_type(&self) -> Option<u8>
pub fn dtmf_payload_type(&self) -> Option<u8>
The negotiated RTP payload type for telephone events, when enabled.
Sourcepub fn is_encrypted(&self) -> bool
pub fn is_encrypted(&self) -> bool
Whether this session was constructed with SRTP key material.
The key bytes remain owned by the workers and are never exposed by this fact.
Sourcepub fn clock_rate(&self) -> u32
pub fn clock_rate(&self) -> u32
The RTP timestamp clock negotiated for this stream.
This is intentionally the wire clock rather than an inferred playback rate. In particular, RFC 7587 fixes Opus at 48 kHz on the RTP timeline.
Sourcepub fn set_relay(&self, relay: bool)
pub fn set_relay(&self, relay: bool)
Hand received packets on still encoded, rather than decoding them to samples.
Switchable at run time because a bridge is formed between calls that are already running: the decision belongs to whoever connects them, and it is not known when the session starts.
Sourcepub fn set_muted(&self, muted: bool) -> bool
pub fn set_muted(&self, muted: bool) -> bool
Gate this session’s outbound audio, or let it through again (M-18).
Returns what the gate was set to before, so a caller that only wants to report real transitions does not have to read the flag and then write it — two steps that race each other when a call is muted from more than one place.
What muting does to the stream. Every audio frame the send loop takes off the queue is replaced by the same number of samples of silence, encoded in this session’s own codec and sent on its own payload type. The stream keeps its pacing, its sequence numbers and its timestamps; what changes is only what the far end decodes. See the module documentation for why this rather than suppressing the packets, and for the RFC 3550 §6 consequence.
Reception is not affected in any way, and neither is DTMF: an RFC 4733 event is generated by this endpoint on purpose, the way a keypad tone is on a handset, so it goes out muted or not.
Sourcepub async fn recv_encoded(&self) -> Option<Encoded>
pub async fn recv_encoded(&self) -> Option<Encoded>
Take the next packet as it arrived, still encoded. Only ever yields under
Self::set_relay.
Sourcepub async fn send_encoded(&self, encoded: Encoded) -> bool
pub async fn send_encoded(&self, encoded: Encoded) -> bool
Put a payload on the wire exactly as given, bypassing the codec.
Sourcepub fn capture(&self, format: PcmFormat) -> Result<PcmCapture<'_>, PcmError>
pub fn capture(&self, format: PcmFormat) -> Result<PcmCapture<'_>, PcmError>
Receive this session as linear PCM at an application-chosen rate and depth.
§Errors
Returns sipx_audio::PcmError::UnsupportedSampleRate when the requested format’s rate
cannot be converted safely.
Sourcepub async fn record_until_idle(&self, idle: Duration) -> Vec<i16>
pub async fn record_until_idle(&self, idle: Duration) -> Vec<i16>
Take received samples until the session goes quiet for idle.
The idle timeout rather than a packet count, because the caller generally knows how long the far end will talk for and not how many packets that becomes.
idle answers one question — has the far end stopped talking — and it answers it by
wall clock. A caller that already knows how many samples it expects is asking a different
question and wants Self::record_at_least; see there for what goes wrong when the two
are confused (X-28).
Sourcepub async fn record_at_least(
&self,
samples: usize,
within: Duration,
) -> Vec<i16>
pub async fn record_at_least( &self, samples: usize, within: Duration, ) -> Vec<i16>
Take received samples until samples of them have arrived, or within elapses.
The wait for a caller that knows the size of what the far end was given — a test that
played a clip of its own, most often. within is a bound on failure, not a
measurement: it is how long this side is prepared to wait before concluding the audio is
not coming, so it should be far longer than the clip rather than close to it. Whatever
arrived is returned, so a caller that got fewer samples than it asked for can say so
itself.
§Why this exists (X-28)
Self::record_until_idle spends one duration on two different jobs: how long to wait
for the stream to start, and how long a gap means it has ended. Neither is a property
of the audio — both are properties of how fast the machine happens to be — so a caller
that knows the count and uses the idle window instead is racing a fixed wall clock
against a pipeline that is merely slow. On a loaded machine that pipeline is slow in
exactly the two places the single window covers: the first packet is the one that waits
out both jitter buffers filling, and a stalled scheduler opens mid-stream gaps wider than
any packet interval. The observed result is a recording of zero samples — not a
degraded one — because once the first frame lands the rest follow at the packet rate.
Widening the window would not have fixed that; it would have moved the cliff.
Sourcepub fn samples_per_packet(&self) -> usize
pub fn samples_per_packet(&self) -> usize
How many samples one packet of this session’s audio carries.
Settled once when the session started, from the negotiated codec’s clock rate and the packet duration. Exposed so a caller playing a clip does not have to recompute what the session already decided — and get it wrong for a codec whose rate is not 8 kHz.
Sourcepub async fn play(&self, samples: &[i16], samples_per_packet: usize) -> bool
pub async fn play(&self, samples: &[i16], samples_per_packet: usize) -> bool
Send a whole clip, paced by the send loop, and wait for it.
Returns whether the clip reached the end. false means it did not: the send queue closed
part way — the call ended, or the session was stopped, under a playback still running — or
something cut it short. The caller needs to be able to tell those apart: “the clip
finished” and “the clip was cut off” are different things to anything waiting on the
playback, and returning () made them indistinguishable.
This is Self::start_playback with the handle thrown away and the answer awaited
through Playback::play_out, so it stays cancel-on-drop: a caller that wraps it in a
timeout still stops the audio when the timeout fires. A caller that wants to stop the
clip explicitly, or to have a keypress stop it, needs the handle.
Sourcepub async fn play_pcm(&self, pcm: &Pcm) -> Result<bool, PcmError>
pub async fn play_pcm(&self, pcm: &Pcm) -> Result<bool, PcmError>
Convert and play an explicit linear-PCM buffer.
§Errors
Returns sipx_audio::PcmError before queuing anything when its rate or representation
cannot be converted.
Sourcepub fn start_pcm_playback(
&self,
pcm: &Pcm,
interrupt: Interrupt,
) -> Result<Playback, PcmError>
pub fn start_pcm_playback( &self, pcm: &Pcm, interrupt: Interrupt, ) -> Result<Playback, PcmError>
Convert an explicit PCM buffer and start it as a controllable playback.
§Errors
Returns sipx_audio::PcmError before creating a playback when conversion is refused.
Sourcepub fn start_playback(
&self,
samples: Vec<i16>,
interrupt: Interrupt,
) -> Playback
pub fn start_playback( &self, samples: Vec<i16>, interrupt: Interrupt, ) -> Playback
Start a clip and hand back a handle to it, without waiting (M-17).
The clip is played at this session’s own packet size, so it is right under a codec whose clock is not 8 kHz without the caller knowing the rate.
§Clips queue; they do not replace
Starting a second playback while one is running puts it behind the one playing, and it
begins when that one ends — however that one ends. This is the choice the story left open,
and it is recorded in docs/designs/app-sdk.md. The
reasoning in short: replacement would make “stop” an implicit side effect of “play”, so an
application that wanted a prompt followed by a menu would hear only the menu, and the first
clip’s cancellation would be an event nobody asked for. Replacement is still available and
still says what it means — Playback::stop the one playing, then start the next.
Queueing while a clip is stopping is the case worth naming, because it is what barge-in
does: stop the prompt, then immediately play something else. The clip being stopped
releases the queue at once and its unsent packets are discarded rather than played, so the
new clip starts within Playback::STOP_BOUND_PACKETS packets — it does not have to wait
out the backlog of the clip it replaced.
A queue Playback::QUEUE_DEPTH deep. A clip that arrives at a full queue is not played and its
handle resolves immediately as PlaybackEnd::Refused, rather than being silently
dropped or waiting for room that a live call may never have.
Sourcepub fn packets_sent(&self) -> u64
pub fn packets_sent(&self) -> u64
How many packets have been sent.
Sourcepub fn packets_received(&self) -> u64
pub fn packets_received(&self) -> u64
How many have been received.
Sourcepub fn discard_counts(&self) -> MediaDiscardCounts
pub fn discard_counts(&self) -> MediaDiscardCounts
A synchronous snapshot of everything this session’s media path has discarded.
This includes candidate-gathering losses on the MediaPort the session consumed.
Each field is monotonic, but independent workers can advance different fields while this
snapshot is read, so relationships across fields are exact only while the session is quiet.
Sourcepub async fn quality(&self) -> Quality
pub async fn quality(&self) -> Quality
How the call is going: loss, jitter, round-trip time and an estimated score.
Readable at any point, not only at the end. The round-trip time is None until a report
has come back from the far end carrying an echo of one of ours — which needs both a
control port on this side and a peer that answers, so it stays None against a peer
that does not do RTCP rather than being filled in with a guess.
Sourcepub async fn stats(&self) -> ReportBlock
pub async fn stats(&self) -> ReportBlock
The receiver report this session would send right now (RFC 3550 §6.4.1).
Safe to poll, as often as a dashboard likes: reading does not close the reporting
interval, so it cannot make the next RTCP report claim a clean interval that was in fact
lossy. That is a decision and not an accident (M-33) — §6.4.1 defines fraction_lost as
loss since the previous SR or RR packet, so the interval boundary is a report having been
sent, and a read is not one. The RTCP loop closes it, via
StreamStats::report_block; this reads with
pending_report_block.
fraction_lost is therefore whatever has accumulated since the last report went out, which
makes it a poor thing to display: it swings with each interval, and a poller sees whichever
interval it happened to catch. Self::quality is the figure for a caller to show.
The two echo fields are zero here. They are filled in by the sending loop, which is the only place that knows how long a peer’s sender report has been held.
Sourcepub async fn flush(&self, within: Duration)
pub async fn flush(&self, within: Duration)
Wait until everything queued has actually been sent.
Sending is paced, so play and send_digit return as soon as the packets are queued —
which is long before they are on the wire. Hanging up at that point discards the tail:
the last word of a clip, or the last digit of a PIN. Anything still queued after
within is given up on, so this cannot hold a caller open indefinitely.
Sourcepub async fn shutdown(&self)
pub async fn shutdown(&self)
Stop and join every worker owned by this session.
Handles stay in the registry until their await completes. Cancelling this future therefore leaves the current handle owned, and a later call resumes the same drain.
Sourcepub fn is_stopped(&self) -> bool
pub fn is_stopped(&self) -> bool
Whether the session has been stopped.
Trait Implementations§
Source§impl Debug for MediaSession
impl Debug for MediaSession
Source§impl Drop for MediaSession
impl Drop for MediaSession
Auto Trait Implementations§
impl !Freeze for MediaSession
impl !RefUnwindSafe for MediaSession
impl Send for MediaSession
impl Sync for MediaSession
impl Unpin for MediaSession
impl UnsafeUnpin for MediaSession
impl !UnwindSafe for MediaSession
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more