Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/source/guides/connections.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,9 @@ for stats in report.values():

## Closing

{meth}`~webrtc.RTCPeerConnection.close` stops the transports and the media at once. A closed connection can't be
reused, so create a new one to connect again.
{meth}`~webrtc.RTCPeerConnection.close` stops the transports and the media at once: its transceivers read stopped
right away, and remote tracks end with their `ended` event. A closed connection can't be reused, so create a new one
to connect again.

Calls pending when the connection closes are cancelled: they raise {obj}`asyncio.CancelledError`.

Expand Down
6 changes: 5 additions & 1 deletion python-webrtc/cpp/NATIVE.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ constructors.
first binding holds until that loop is released. Children the connection creates natively inherit its binding, so
their records queue behind the parent's event. Closing a connection unbinds its children (`Surfaced` reads
`IsBound()`). `HeldEvents` only orders events inside libwebrtc callbacks.
- A description operation holds its events (`HeldOperationEvents`) until it completes, taking the hold in its first
signaling task, which runs after the previous operation released its own.
- `DefineBinding(cls)` defines `_bind`, `_bound`, `_mailbox` and `_orphaned` (its mailbox closed) per class, not on a pybind base: a base method costs a derived-to-base cast per call (6–8 µs vs
0.1 µs).
- `NativeObject<T>` (`utils/native_object.h`) counts objects per `T::kName` (`AliveCounts()`) and exposes `_id`,
Expand Down Expand Up @@ -113,14 +115,16 @@ alive while it can still fire an event that has a handler, following the WebRTC
`_activity()`). Only the bound loop roots an object, so whatever makes it active binds it first, and whatever unbinds
it ends its activity first (`close()`) or orphans it (a release): an active object is bound or orphaned at every
instant. A closed or collected loop releases what it kept: its mailbox closes and its queued records are dropped,
pending operations are cancelled, a callback queued into it with `call_soon` runs its drop handler instead, and its
pending operations are cancelled, a callback posted into it with a drop handler runs that instead, and its
handlers are removed. Its objects with handlers left on another loop are bound to that loop. Everything else is plain
Python garbage.

- Every change of binding or activity ends in an update of the root on the loop that holds it. A change made from
Python goes through `_changing()` (a spent `once` handler too), so roots checks on other loops wait for it. An
unbind updates roots after unbinding, and `LoopState.update` re-reads the binding after storing a root
(`_recheck`): whichever runs last sees the other.
- An object is kept for the events still owed to it, not for the state it shows (an announced data channel reads
open before its open event).
- Each class's `_open()` is its delivery gate: once it's false, even queued events don't fire (a stopped track, a
detached transform, a closed connection). The base gate is "its connection isn't closed". `GATES` in
`tests/test_lifetime_model.py` lists every class's gate; a new class with events is added there.
Expand Down
16 changes: 11 additions & 5 deletions python-webrtc/cpp/src/interfaces/media_stream_track.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@ namespace python_webrtc {
webrtc::scoped_refptr<webrtc::MediaStreamTrackInterface> track)
: _factory(std::move(factory)), _track(std::move(track)),
_source(SourceControl::Find(_track.get(), _track->id())) {
// live until its ended event, unless read unbound
_surfacedEnded.Changed(true, false);
_factory->signalingThread()->PostTask(Guard([this]() {
_track->RegisterObserver(this);
_observing = true;
AttachMonitor();
if (_track->state() == webrtc::MediaStreamTrackInterface::TrackState::kEnded) {
EndOnSignalingThread();
}
}));
}

Expand Down Expand Up @@ -261,11 +266,12 @@ namespace python_webrtc {
}

webrtc::MediaStreamTrackInterface::TrackState MediaStreamTrack::GetReadyState() {
const bool ended = _ended || _track->state() == webrtc::MediaStreamTrackInterface::TrackState::kEnded;
// unbound (outside of an event loop), no event is going to surface it
const bool surfaced = _surfacedEnded.Shown(IsBound(), ended);
return surfaced ? webrtc::MediaStreamTrackInterface::TrackState::kEnded
: webrtc::MediaStreamTrackInterface::TrackState::kLive;
// unbound (outside of an event loop), no event is going to surface it: libwebrtc's, on the signaling thread
const bool ended = _surfacedEnded.ShownOr(IsBound(), [this]() {
return _ended || _track->state() == webrtc::MediaStreamTrackInterface::TrackState::kEnded;
});
return ended ? webrtc::MediaStreamTrackInterface::TrackState::kEnded
: webrtc::MediaStreamTrackInterface::TrackState::kLive;
}

bool MediaStreamTrack::GetMuted() {
Expand Down
7 changes: 7 additions & 0 deletions python-webrtc/cpp/src/interfaces/rtc_data_channel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ namespace python_webrtc {
pybind11::arg("data"), pybind11::arg("binary"))
.def("close", &RTCDataChannel::Close, nogil())
.def("_surfaceState", &RTCDataChannel::SurfaceState, nogil(), pybind11::arg("state"))
.def_property_readonly("_openDelivered", nogil_fn(&RTCDataChannel::GetOpenDelivered))
.def("_decreaseBufferedAmount", &RTCDataChannel::DecreaseBufferedAmount, nogil(), pybind11::arg("sent"));
}

Expand Down Expand Up @@ -245,9 +246,15 @@ namespace python_webrtc {
if (_closeRequested && state == DataState::kOpen) {
return;
}
_openDelivered = _openDelivered || state == DataState::kOpen;
_surfacedState.Surface(state);
}

bool RTCDataChannel::GetOpenDelivered() {
const std::scoped_lock lock(_closeMutex);
return _openDelivered;
}

uint64_t RTCDataChannel::GetBufferedAmount() {
return _bufferedAmount;
}
Expand Down
3 changes: 3 additions & 0 deletions python-webrtc/cpp/src/interfaces/rtc_data_channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ namespace python_webrtc {

void SurfaceState(DataState state);

bool GetOpenDelivered();

uint64_t GetBufferedAmount();

uint64_t GetBufferedAmountLowThreshold();
Expand Down Expand Up @@ -145,6 +147,7 @@ namespace python_webrtc {
// that depend on it
std::mutex _closeMutex;
bool _closeRequested = false;
bool _openDelivered = false;

LockedFunction<std::optional<double>()> _maxMessageSizeGetter;
LockedFunction<void()> _closedCallback;
Expand Down
65 changes: 39 additions & 26 deletions python-webrtc/cpp/src/interfaces/rtc_dtmf_sender.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,8 @@ namespace python_webrtc {
return registry.Get();
}

void
RTCDTMFSender::SetTransceiver(std::function<webrtc::scoped_refptr<webrtc::RtpTransceiverInterface>()> transceiver) {
_transceiver.Set(std::move(transceiver));
void RTCDTMFSender::SetTransceiverState(std::function<TransceiverState()> state) {
_transceiverState.Set(std::move(state));
}

void RTCDTMFSender::OnToneChange(const std::string &tone, const std::string &toneBuffer) {
Expand All @@ -64,40 +63,54 @@ namespace python_webrtc {
Emit("tonechange", tone, toneBuffer, insertion);
}

void RTCDTMFSender::CheckCanSend() {
auto lookup = _transceiver.Get();
auto transceiver = lookup ? lookup() : nullptr;
if (!transceiver || transceiver->stopping() || transceiver->stopped()) {
throw RTCException(webrtc::RTCErrorType::INVALID_STATE, "The transceiver of the sender is stopped");
std::optional<std::string> RTCDTMFSender::CannotSend() {
auto lookup = _transceiverState.Get();
auto state = lookup ? lookup() : TransceiverState::Gone();
if (state.stopping || state.stopped) {
return "The transceiver of the sender is stopped";
}
auto direction = transceiver->current_direction();
auto direction = state.currentDirection;
if (direction && (*direction == webrtc::RtpTransceiverDirection::kRecvOnly ||
*direction == webrtc::RtpTransceiverDirection::kInactive)) {
throw RTCException(webrtc::RTCErrorType::INVALID_STATE, "The transceiver of the sender doesn't send");
return "The transceiver of the sender doesn't send";
}
if (!_dtmf->CanInsertDtmf()) {
throw RTCException(webrtc::RTCErrorType::INVALID_STATE, "The DTMF sender can't send tones yet");
return "The DTMF sender can't send tones yet";
}
return std::nullopt;
}

void RTCDTMFSender::InsertDtmf(const std::string &tones, int duration, int interToneGap) {
CheckCanSend();
if (tones.empty() && _dtmf->tones().empty()) {
// nothing to play, nor to cancel
return;
void RTCDTMFSender::CheckCanSend() {
if (auto reason = CannotSend()) {
throw RTCException(webrtc::RTCErrorType::INVALID_STATE, *reason);
}
{
// tone changes of the tones inserted before don't change the buffer anymore
}

void RTCDTMFSender::InsertDtmf(const std::string &tones, int duration, int interToneGap) {
// the proxies run inline there; nothing may throw through libwebrtc's task
auto reason = BlockingCallOn(_factory->signalingThread(), [&]() -> std::optional<std::string> {
auto cannot = CannotSend();
if (cannot || (tones.empty() && _dtmf->tones().empty())) {
// or nothing to play, nor to cancel
return cannot;
}
{
// tone changes of the tones inserted before don't change the buffer anymore
const std::scoped_lock lock(_bufferMutex);
++_insertions;
}
if (!_dtmf->InsertDtmf(tones, duration, interToneGap) && !tones.empty()) {
return "The DTMF sender can't send tones yet";
}
const std::scoped_lock lock(_bufferMutex);
++_insertions;
}
if (!_dtmf->InsertDtmf(tones, duration, interToneGap) && !tones.empty()) {
throw RTCException(webrtc::RTCErrorType::INVALID_STATE, "The DTMF sender can't send tones yet");
_surfacedBuffer = tones;
// a cancel (no tones) ends with an empty tonechange too
_playing = true;
return std::nullopt;
});
if (reason) {
throw RTCException(webrtc::RTCErrorType::INVALID_STATE, *reason);
}
const std::scoped_lock lock(_bufferMutex);
_surfacedBuffer = tones;
// a cancel (no tones) ends with an empty tonechange too
_playing = true;
}

std::string RTCDTMFSender::GetToneBuffer() {
Expand Down
12 changes: 8 additions & 4 deletions python-webrtc/cpp/src/interfaces/rtc_dtmf_sender.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
#include <string>

#include <api/dtmf_sender_interface.h>
#include <api/rtp_transceiver_interface.h>

#include <pybind11/pybind11.h>

Expand All @@ -24,6 +23,7 @@
#include "../utils/native_object.h"
#include "../utils/registry.h"
#include "peer_connection_factory.h"
#include "transceiver_state.h"

namespace python_webrtc {

Expand All @@ -46,14 +46,16 @@ namespace python_webrtc {

static Registry<RTCDTMFSender, webrtc::DtmfSenderInterface> &registry();

// the transceiver of the sender, which tells whether tones can be sent; set by the sender
void SetTransceiver(std::function<webrtc::scoped_refptr<webrtc::RtpTransceiverInterface>()> transceiver);
// the state of the sender's transceiver as Python sees it, which tells whether tones can be sent; set by the
// sender
void SetTransceiverState(std::function<TransceiverState()> state);

// DtmfSenderObserverInterface, on the signaling thread
void OnToneChange(const std::string &tone, const std::string &toneBuffer) override;

// "determine if DTMF can be sent"
void CheckCanSend();

void InsertDtmf(const std::string &tones, int duration, int interToneGap);

// the tones not played yet, as Python sees them: set by insertDTMF(), shortened along with tonechange events
Expand All @@ -67,9 +69,11 @@ namespace python_webrtc {
bool GetCanInsertDtmf();

private:
std::optional<std::string> CannotSend();

std::shared_ptr<PeerConnectionFactory> _factory;
webrtc::scoped_refptr<webrtc::DtmfSenderInterface> _dtmf;
LockedFunction<webrtc::scoped_refptr<webrtc::RtpTransceiverInterface>()> _transceiver;
LockedFunction<TransceiverState()> _transceiverState;

std::mutex _bufferMutex;
std::optional<std::string> _surfacedBuffer;
Expand Down
Loading
Loading