rtc-link
Version:
A library for managing WebRTC connections with multiplexed streams, anti-glare handling, and streamlined data channel setup.
67 lines (62 loc) • 3.06 kB
JavaScript
import {asPlex, connectAndSend, listenAndConnectionAndRead$} from "rxprotoplex";
import {ReplaySubject, switchMap, take, tap, timeout, EMPTY} from "rxjs";
import {CHANNEL} from "./constants.js";
/**
* Establishes a signaling connection between two streams, enabling WebRTC offer, answer, and ICE message
* exchange via multiplexed channels.
*
* @param {Stream} stream1 - The first data stream to be connected.
* @param {Stream} stream2 - The second data stream to be connected.
* @param {Object} [config={}] - Optional configuration object.
* @param {number} [config.iceCandidateTimeout=30000] - The timeout duration in milliseconds for ICE candidate messages. The timeout only starts after an offer or answer has been received.
*
* @description
* The function uses the provided streams to create multiplexed (`plex`) connections that listen for
* specific WebRTC signaling messages (offer, answer, and ICE candidates) on designated channels.
* When a message is received on one channel, it is forwarded to the corresponding channel on the
* target stream, effectively establishing a bi-directional signaling pipeline between the two streams.
*
* A close event from either stream triggers the closure of both signaling channels to avoid
* unexpected behavior.
*
* - **Channels Used**:
* - `CHANNEL.WEBRTC_OFFER` - For WebRTC offer messages.
* - `CHANNEL.WEBRTC_ANSWER` - For WebRTC answer messages.
* - `CHANNEL.WEBRTC_ICE` - For WebRTC ICE candidates.
*
* @example
* establishSignalStream(stream1, stream2, { iceCandidateTimeout: 20000 });
*
* @returns {Function} - A function that, when called, closes the signaling streams.
*/
const establishSignalStream = (stream1, stream2, config = {}) => {
const { iceCandidateTimeout = 30000 } = config;
const plex1 = asPlex(stream1);
const plex2 = asPlex(stream2);
const cleanups = [];
[[plex1, plex2], [plex2, plex1]].forEach(([from, target]) => {
const triggered = new ReplaySubject(1);
cleanups.push(
listenAndConnectionAndRead$(from, CHANNEL.WEBRTC_OFFER)
.pipe(take(1), tap(() => triggered.next()))
.subscribe(connectAndSend(target, CHANNEL.WEBRTC_OFFER)),
listenAndConnectionAndRead$(from, CHANNEL.WEBRTC_ANSWER)
.pipe(take(1), tap(() => triggered.next()))
.subscribe(connectAndSend(target, CHANNEL.WEBRTC_ANSWER)),
triggered.pipe(
take(1),
switchMap(() =>
listenAndConnectionAndRead$(from, CHANNEL.WEBRTC_ICE)
.pipe(
timeout({
each: iceCandidateTimeout, // Configurable timeout for ICE candidates
with: () => EMPTY
})
)
)
).subscribe(connectAndSend(target, CHANNEL.WEBRTC_ICE))
);
});
return () => cleanups.forEach((obs) => obs.unsubscribe());
}
export {establishSignalStream};