@fishjam-cloud/webrtc-client 0.25.0-rc.2 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +100 -1
- package/dist/index.mjs +383 -4
- package/package.json +2 -3
package/dist/index.d.mts
CHANGED
|
@@ -2,6 +2,7 @@ import { BinaryWriter, BinaryReader } from '@bufbuild/protobuf/wire';
|
|
|
2
2
|
import TypedEmitter from 'typed-emitter';
|
|
3
3
|
|
|
4
4
|
declare const getLogger: (enableLogging: boolean) => {
|
|
5
|
+
readonly debug: (arg: unknown, ...args: unknown[]) => void;
|
|
5
6
|
readonly warn: (arg: unknown, ...args: unknown[]) => void;
|
|
6
7
|
readonly error: (arg: unknown, ...args: unknown[]) => void;
|
|
7
8
|
};
|
|
@@ -417,6 +418,20 @@ interface WebRTCEndpointEvents {
|
|
|
417
418
|
trackId: string;
|
|
418
419
|
metadata: unknown;
|
|
419
420
|
}) => void;
|
|
421
|
+
/**
|
|
422
|
+
* Emitted when data channels (for both reliable and lossy) are created and ready to send data.
|
|
423
|
+
* This event is fired after calling connectDataChannels().
|
|
424
|
+
*/
|
|
425
|
+
dataChannelsReady: () => void;
|
|
426
|
+
/**
|
|
427
|
+
* Emitted when data is received on any data channel.
|
|
428
|
+
* The payload includes the channel type (reliable/lossy) and the binary data.
|
|
429
|
+
*/
|
|
430
|
+
dataChannelPayload: (payload: DataChannelMessagePayload) => void;
|
|
431
|
+
/**
|
|
432
|
+
* Emitted when any data channel errors.
|
|
433
|
+
*/
|
|
434
|
+
dataChannelsError: (error: Error) => void;
|
|
420
435
|
}
|
|
421
436
|
/**
|
|
422
437
|
* Interface describing Endpoint.
|
|
@@ -439,6 +454,39 @@ interface Endpoint {
|
|
|
439
454
|
*/
|
|
440
455
|
tracks: Map<string, TrackContext>;
|
|
441
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* Options for publishing or subscribing to data.
|
|
459
|
+
*/
|
|
460
|
+
interface DataChannelOptions {
|
|
461
|
+
/**
|
|
462
|
+
* If true, uses the reliable data channel (ordered, guaranteed delivery).
|
|
463
|
+
* If false, uses the lossy data channel (unordered, low latency).
|
|
464
|
+
*/
|
|
465
|
+
reliable: boolean;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Callback type for receiving data from a data channel.
|
|
469
|
+
* @param data - The received data as a Uint8Array
|
|
470
|
+
*/
|
|
471
|
+
type DataCallback = (data: Uint8Array) => void;
|
|
472
|
+
/**
|
|
473
|
+
* Internal type for channel classification
|
|
474
|
+
* @internal
|
|
475
|
+
*/
|
|
476
|
+
type DataChannelType = 'reliable' | 'lossy';
|
|
477
|
+
/**
|
|
478
|
+
* Payload for data received events from data channels.
|
|
479
|
+
*/
|
|
480
|
+
interface DataChannelMessagePayload {
|
|
481
|
+
/**
|
|
482
|
+
* The type of channel the data was received on.
|
|
483
|
+
*/
|
|
484
|
+
channelType: DataChannelType;
|
|
485
|
+
/**
|
|
486
|
+
* The binary payload data.
|
|
487
|
+
*/
|
|
488
|
+
data: Uint8Array;
|
|
489
|
+
}
|
|
442
490
|
type WebRTCEndpointProps = {
|
|
443
491
|
/**
|
|
444
492
|
* Enables Fishjam SDK's debug logs in the console.
|
|
@@ -460,6 +508,7 @@ declare class ConnectionManager {
|
|
|
460
508
|
removeTrack: (sender: RTCRtpSender) => void;
|
|
461
509
|
findSender: (mediaStreamTrackId: MediaStreamTrackId) => RTCRtpSender;
|
|
462
510
|
addIceCandidate: (iceCandidate: RTCIceCandidate) => Promise<void>;
|
|
511
|
+
createDataChannel: (label: string, config: RTCDataChannelInit) => RTCDataChannel;
|
|
463
512
|
}
|
|
464
513
|
|
|
465
514
|
declare const TrackContextImpl_base: new () => TypedEmitter<Required<TrackContextEvents>>;
|
|
@@ -493,6 +542,7 @@ declare class WebRTCEndpoint extends WebRTCEndpoint_base {
|
|
|
493
542
|
private readonly remote;
|
|
494
543
|
private readonly local;
|
|
495
544
|
private readonly commandsQueue;
|
|
545
|
+
private readonly dataChannelManager;
|
|
496
546
|
private proposedIceServers;
|
|
497
547
|
private logger;
|
|
498
548
|
bandwidthEstimation: bigint;
|
|
@@ -556,6 +606,7 @@ declare class WebRTCEndpoint extends WebRTCEndpoint_base {
|
|
|
556
606
|
getRemoteEndpoints(): Record<string, EndpointWithTrackContext>;
|
|
557
607
|
getLocalEndpoint(): EndpointWithTrackContext;
|
|
558
608
|
getBandwidthEstimation(): bigint;
|
|
609
|
+
getDataChannelsReadiness(): boolean;
|
|
559
610
|
private handleMediaEvent;
|
|
560
611
|
private onSdpAnswer;
|
|
561
612
|
/**
|
|
@@ -760,6 +811,54 @@ declare class WebRTCEndpoint extends WebRTCEndpoint_base {
|
|
|
760
811
|
* that endpoint was removed in {@link WebRTCEndpointEvents.endpointRemoved},
|
|
761
812
|
*/
|
|
762
813
|
disconnect: () => void;
|
|
814
|
+
/**
|
|
815
|
+
* Create both reliable and lossy data channel.
|
|
816
|
+
* This method must be called before publishData() can be used.
|
|
817
|
+
* Emits the 'dataChannelsReady' event when both channels are open and ready.
|
|
818
|
+
*
|
|
819
|
+
* @example
|
|
820
|
+
* ```ts
|
|
821
|
+
* const webrtc = new WebRTCEndpoint();
|
|
822
|
+
*
|
|
823
|
+
* webrtc.on('dataChannelsReady', () => {
|
|
824
|
+
* console.log('Data channels ready, can now send data');
|
|
825
|
+
* webrtc.publishData(new TextEncoder().encode('Hello'), { reliable: true });
|
|
826
|
+
* });
|
|
827
|
+
*
|
|
828
|
+
* webrtc.connectDataChannels();
|
|
829
|
+
* ```
|
|
830
|
+
*/
|
|
831
|
+
connectDataChannels: () => Promise<void>;
|
|
832
|
+
/**
|
|
833
|
+
* Publish data through a data channel.
|
|
834
|
+
* The data channels must be created first by calling connectDataChannels() or enabling negotiateOnConnect.
|
|
835
|
+
* Throws an error if the channel doesn't exist or isn't ready yet.
|
|
836
|
+
*
|
|
837
|
+
* @param data - The data to send as Uint8Array
|
|
838
|
+
* @param options - Options specifying which channel to use (reliable or lossy)
|
|
839
|
+
* @throws Error if the channel doesn't exist or isn't ready
|
|
840
|
+
*
|
|
841
|
+
* @example
|
|
842
|
+
* ```ts
|
|
843
|
+
* // Subscribe to incoming data
|
|
844
|
+
* webrtc.on('dataReceived', ({ channelType, data }) => {
|
|
845
|
+
* console.log(`Received on ${channelType}:`, new TextDecoder().decode(data));
|
|
846
|
+
* });
|
|
847
|
+
*
|
|
848
|
+
* webrtc.on('dataChannelsReady', () => {
|
|
849
|
+
* // Send reliable data
|
|
850
|
+
* const data = new TextEncoder().encode('Hello World');
|
|
851
|
+
* webrtc.publishData(data, { reliable: true });
|
|
852
|
+
*
|
|
853
|
+
* // Send lossy data for low-latency updates
|
|
854
|
+
* const gameState = new Uint8Array([1, 2, 3, 4, 5]);
|
|
855
|
+
* webrtc.publishData(gameState, { reliable: false });
|
|
856
|
+
* });
|
|
857
|
+
*
|
|
858
|
+
* webrtc.connectDataChannels();
|
|
859
|
+
* ```
|
|
860
|
+
*/
|
|
861
|
+
publishData: (data: Uint8Array, options: DataChannelOptions) => void;
|
|
763
862
|
/**
|
|
764
863
|
* Cleans up {@link WebRTCEndpoint} instance.
|
|
765
864
|
*/
|
|
@@ -777,4 +876,4 @@ declare class WebRTCEndpoint extends WebRTCEndpoint_base {
|
|
|
777
876
|
private onIceConnectionStateChange;
|
|
778
877
|
}
|
|
779
878
|
|
|
780
|
-
export { type BandwidthLimit, type EncodingReason, type Endpoint, type Logger, type MediaEvent, type SerializedMediaEvent, type SimulcastBandwidthLimit, MediaEvent_Track_SimulcastConfig as SimulcastConfig, type TrackBandwidthLimit, type TrackContext, type TrackContextEvents, type TrackKind, type VadStatus, Variant, WebRTCEndpoint, type WebRTCEndpointEvents, getLogger };
|
|
879
|
+
export { type BandwidthLimit, type DataCallback, type DataChannelMessagePayload, type DataChannelOptions, type DataChannelType, type EncodingReason, type Endpoint, type Logger, type MediaEvent, type SerializedMediaEvent, type SimulcastBandwidthLimit, MediaEvent_Track_SimulcastConfig as SimulcastConfig, type TrackBandwidthLimit, type TrackContext, type TrackContextEvents, type TrackKind, type VadStatus, Variant, WebRTCEndpoint, type WebRTCEndpointEvents, getLogger };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// src/logger.ts
|
|
2
2
|
var FISHJAM_PREFIX = "[FISHJAM]";
|
|
3
3
|
var getLogger = (enableLogging) => ({
|
|
4
|
+
debug: (arg, ...args) => {
|
|
5
|
+
if (enableLogging) console.debug(FISHJAM_PREFIX, arg, ...args);
|
|
6
|
+
},
|
|
4
7
|
warn: (arg, ...args) => {
|
|
5
8
|
if (enableLogging) console.warn(FISHJAM_PREFIX, arg, ...args);
|
|
6
9
|
},
|
|
@@ -1393,7 +1396,7 @@ function isSet2(value) {
|
|
|
1393
1396
|
}
|
|
1394
1397
|
|
|
1395
1398
|
// src/webRTCEndpoint.ts
|
|
1396
|
-
import
|
|
1399
|
+
import EventEmitter3 from "events";
|
|
1397
1400
|
import { v4 as uuidv4 } from "uuid";
|
|
1398
1401
|
|
|
1399
1402
|
// src/CommandsQueue.ts
|
|
@@ -1527,6 +1530,306 @@ var ConnectionManager = class {
|
|
|
1527
1530
|
addIceCandidate = async (iceCandidate) => {
|
|
1528
1531
|
await this.connection.addIceCandidate(iceCandidate);
|
|
1529
1532
|
};
|
|
1533
|
+
createDataChannel = (label, config) => {
|
|
1534
|
+
return this.connection.createDataChannel(label, config);
|
|
1535
|
+
};
|
|
1536
|
+
};
|
|
1537
|
+
|
|
1538
|
+
// src/dataChannels/DataChannelManager.ts
|
|
1539
|
+
import { EventEmitter } from "events";
|
|
1540
|
+
|
|
1541
|
+
// src/dataChannels/DataChannel.ts
|
|
1542
|
+
var DataChannel = class {
|
|
1543
|
+
constructor(type, logger) {
|
|
1544
|
+
this.type = type;
|
|
1545
|
+
this.logger = logger;
|
|
1546
|
+
}
|
|
1547
|
+
channel = null;
|
|
1548
|
+
dataCallback = null;
|
|
1549
|
+
openCallback = null;
|
|
1550
|
+
errorCallback = null;
|
|
1551
|
+
_status = "init";
|
|
1552
|
+
/**
|
|
1553
|
+
* Get the current status of the data channel
|
|
1554
|
+
*/
|
|
1555
|
+
get status() {
|
|
1556
|
+
return this._status;
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Set the underlying RTCDataChannel and set up event listeners
|
|
1560
|
+
*/
|
|
1561
|
+
setChannel(channel) {
|
|
1562
|
+
this._status = "creating";
|
|
1563
|
+
this.channel = channel;
|
|
1564
|
+
this.setupListeners();
|
|
1565
|
+
}
|
|
1566
|
+
/**
|
|
1567
|
+
* Set callback to be called when the channel is opened
|
|
1568
|
+
*/
|
|
1569
|
+
setOnOpen(callback) {
|
|
1570
|
+
this.openCallback = callback;
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Set callback to be called when the channel errors
|
|
1574
|
+
*/
|
|
1575
|
+
setOnError(callback) {
|
|
1576
|
+
this.errorCallback = callback;
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Send data through the channel
|
|
1580
|
+
* @param data - The data to send as Uint8Array
|
|
1581
|
+
*/
|
|
1582
|
+
send(data) {
|
|
1583
|
+
if (!this.channel || this._status !== "open") {
|
|
1584
|
+
this.logger.warn(`Cannot send data on ${this.type} channel: channel not ready (status: ${this._status})`);
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
try {
|
|
1588
|
+
this.channel.send(data);
|
|
1589
|
+
} catch (error) {
|
|
1590
|
+
this.logger.error(`Error sending data on ${this.type} channel:`, error);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
/**
|
|
1594
|
+
* Set the callback for receiving data
|
|
1595
|
+
* @param callback - Function to call when data is received
|
|
1596
|
+
*/
|
|
1597
|
+
setCallback(callback) {
|
|
1598
|
+
this.dataCallback = callback;
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Close the data channel
|
|
1602
|
+
*/
|
|
1603
|
+
close() {
|
|
1604
|
+
if (this.channel) {
|
|
1605
|
+
this.channel.close();
|
|
1606
|
+
}
|
|
1607
|
+
this._status = "closed";
|
|
1608
|
+
this.dataCallback = null;
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* Set up event listeners for the RTCDataChannel
|
|
1612
|
+
* @private
|
|
1613
|
+
*/
|
|
1614
|
+
setupListeners() {
|
|
1615
|
+
if (!this.channel) return;
|
|
1616
|
+
this.channel.onopen = () => {
|
|
1617
|
+
this._status = "open";
|
|
1618
|
+
this.logger.debug(`Data channel ${this.type} opened`);
|
|
1619
|
+
this.openCallback?.();
|
|
1620
|
+
};
|
|
1621
|
+
this.channel.onclose = () => {
|
|
1622
|
+
this._status = "closed";
|
|
1623
|
+
this.logger.debug(`Data channel ${this.type} closed`);
|
|
1624
|
+
};
|
|
1625
|
+
this.channel.onerror = (event) => {
|
|
1626
|
+
this.logger.error(`Data channel ${this.type} error:`, event);
|
|
1627
|
+
this.errorCallback?.(event);
|
|
1628
|
+
};
|
|
1629
|
+
this.channel.onmessage = (event) => {
|
|
1630
|
+
this.handleMessage(event.data);
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Handle incoming message data
|
|
1635
|
+
* @private
|
|
1636
|
+
*/
|
|
1637
|
+
handleMessage(data) {
|
|
1638
|
+
if (!this.dataCallback) {
|
|
1639
|
+
this.logger.warn(`Received data on ${this.type} channel but no callback is set`);
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
try {
|
|
1643
|
+
let uint8Data;
|
|
1644
|
+
if (data instanceof ArrayBuffer) {
|
|
1645
|
+
uint8Data = new Uint8Array(data);
|
|
1646
|
+
} else if (data instanceof Uint8Array) {
|
|
1647
|
+
uint8Data = data;
|
|
1648
|
+
} else if (typeof data === "string") {
|
|
1649
|
+
uint8Data = new TextEncoder().encode(data);
|
|
1650
|
+
} else if (data instanceof Blob) {
|
|
1651
|
+
const reader = new FileReader();
|
|
1652
|
+
reader.onload = () => {
|
|
1653
|
+
if (reader.result instanceof ArrayBuffer) {
|
|
1654
|
+
this.dataCallback?.(new Uint8Array(reader.result));
|
|
1655
|
+
}
|
|
1656
|
+
};
|
|
1657
|
+
reader.readAsArrayBuffer(data);
|
|
1658
|
+
return;
|
|
1659
|
+
} else {
|
|
1660
|
+
this.logger.warn(`Received unsupported data type on ${this.type} channel:`, typeof data);
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
this.dataCallback(uint8Data);
|
|
1664
|
+
} catch (error) {
|
|
1665
|
+
this.logger.error(`Error handling message on ${this.type} channel:`, error);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
};
|
|
1669
|
+
|
|
1670
|
+
// src/dataChannels/DataChannelManager.ts
|
|
1671
|
+
var DataChannelManager = class extends EventEmitter {
|
|
1672
|
+
constructor(createDataChannel, triggerRenegotiation, logger) {
|
|
1673
|
+
super();
|
|
1674
|
+
this.createDataChannel = createDataChannel;
|
|
1675
|
+
this.triggerRenegotiation = triggerRenegotiation;
|
|
1676
|
+
this.logger = logger;
|
|
1677
|
+
}
|
|
1678
|
+
reliableChannel = null;
|
|
1679
|
+
lossyChannel = null;
|
|
1680
|
+
/**
|
|
1681
|
+
* Initialize channels and trigger renegotiation.
|
|
1682
|
+
* Returns when both channels are open or throws if any channel errors.
|
|
1683
|
+
*/
|
|
1684
|
+
async connect() {
|
|
1685
|
+
if (this.getChannelsReadiness()) return;
|
|
1686
|
+
this.createChannels();
|
|
1687
|
+
this.triggerRenegotiation();
|
|
1688
|
+
await this.waitForChannelsReady();
|
|
1689
|
+
}
|
|
1690
|
+
/**
|
|
1691
|
+
* Wait for both channels to be ready or throw on error.
|
|
1692
|
+
* @private
|
|
1693
|
+
*/
|
|
1694
|
+
waitForChannelsReady() {
|
|
1695
|
+
if (this.getChannelsReadiness()) return Promise.resolve();
|
|
1696
|
+
return new Promise((resolve, reject) => {
|
|
1697
|
+
const onReady = () => {
|
|
1698
|
+
cleanup();
|
|
1699
|
+
resolve();
|
|
1700
|
+
};
|
|
1701
|
+
const onError = (type, error) => {
|
|
1702
|
+
cleanup();
|
|
1703
|
+
reject(new Error(`Data channel ${type} error: ${error}`));
|
|
1704
|
+
};
|
|
1705
|
+
const cleanup = () => {
|
|
1706
|
+
this.removeListener("ready", onReady);
|
|
1707
|
+
this.removeListener("error", onError);
|
|
1708
|
+
};
|
|
1709
|
+
this.on("ready", onReady);
|
|
1710
|
+
this.on("error", onError);
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Publish data through a data channel.
|
|
1715
|
+
* Throws an error if the channel doesn't exist or isn't ready.
|
|
1716
|
+
* @param data - The data to send as Uint8Array
|
|
1717
|
+
* @param options - Options specifying which channel to use
|
|
1718
|
+
* @throws Error if the channel doesn't exist or isn't open
|
|
1719
|
+
*/
|
|
1720
|
+
publishData(data, options) {
|
|
1721
|
+
const type = options.reliable ? "reliable" : "lossy";
|
|
1722
|
+
const channel = type === "reliable" ? this.reliableChannel : this.lossyChannel;
|
|
1723
|
+
if (!channel) {
|
|
1724
|
+
throw new Error(
|
|
1725
|
+
`Cannot publish data: ${type} channel not created. Call connectDataPublishers() first or enable negotiateOnConnect.`
|
|
1726
|
+
);
|
|
1727
|
+
}
|
|
1728
|
+
if (channel.status !== "open") {
|
|
1729
|
+
throw new Error(
|
|
1730
|
+
`Cannot publish data: ${type} channel not ready (status: ${channel.status}). Wait for dataChannelsReady event.`
|
|
1731
|
+
);
|
|
1732
|
+
}
|
|
1733
|
+
channel.send(data);
|
|
1734
|
+
}
|
|
1735
|
+
/**
|
|
1736
|
+
* Close all data channels and remove all event listeners.
|
|
1737
|
+
* Called during cleanup/disconnect.
|
|
1738
|
+
*/
|
|
1739
|
+
cleanup() {
|
|
1740
|
+
this.logger.warn("Cleaning up data channels");
|
|
1741
|
+
if (this.reliableChannel) {
|
|
1742
|
+
this.reliableChannel.close();
|
|
1743
|
+
this.reliableChannel = null;
|
|
1744
|
+
}
|
|
1745
|
+
if (this.lossyChannel) {
|
|
1746
|
+
this.lossyChannel.close();
|
|
1747
|
+
this.lossyChannel = null;
|
|
1748
|
+
}
|
|
1749
|
+
this.removeAllListeners();
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Check if both data channels are open and ready to send data.
|
|
1753
|
+
* @returns true if both channels are open and ready, false otherwise
|
|
1754
|
+
*/
|
|
1755
|
+
getChannelsReadiness() {
|
|
1756
|
+
return this.reliableChannel?.status === "open" && this.lossyChannel?.status === "open";
|
|
1757
|
+
}
|
|
1758
|
+
createChannels() {
|
|
1759
|
+
this.createChannel("reliable");
|
|
1760
|
+
this.createChannel("lossy");
|
|
1761
|
+
}
|
|
1762
|
+
/**
|
|
1763
|
+
* Create a single data channel.
|
|
1764
|
+
* @private
|
|
1765
|
+
*/
|
|
1766
|
+
createChannel(type) {
|
|
1767
|
+
const channel = this.getOrCreateChannelWrapper(type);
|
|
1768
|
+
if (channel.status !== "init") return;
|
|
1769
|
+
const label = type;
|
|
1770
|
+
const config = type === "reliable" ? { ordered: true } : { ordered: false, maxRetransmits: 0 };
|
|
1771
|
+
const rtcChannel = this.createDataChannel(label, config);
|
|
1772
|
+
channel.setOnOpen(() => this.onChannelOpen(type));
|
|
1773
|
+
channel.setOnError((error) => this.onChannelError(type, error));
|
|
1774
|
+
channel.setChannel(rtcChannel);
|
|
1775
|
+
this.logger.warn(`Created ${type} data channel`);
|
|
1776
|
+
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Called when a data channel opens.
|
|
1779
|
+
* Emits channelOpen event and checks if both channels are open.
|
|
1780
|
+
* @private
|
|
1781
|
+
*/
|
|
1782
|
+
onChannelOpen(type) {
|
|
1783
|
+
this.logger.warn(`Data channel ${type} opened`);
|
|
1784
|
+
this.emit("channelOpen", type);
|
|
1785
|
+
const bothReady = this.reliableChannel && this.reliableChannel.status === "open" && this.lossyChannel && this.lossyChannel.status === "open";
|
|
1786
|
+
if (bothReady) {
|
|
1787
|
+
this.logger.warn("All data publishers ready");
|
|
1788
|
+
this.emit("ready");
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Called when a data channel errors.
|
|
1793
|
+
* @private
|
|
1794
|
+
*/
|
|
1795
|
+
onChannelError(type, error) {
|
|
1796
|
+
this.logger.error(`Data channel ${type} error:`, error);
|
|
1797
|
+
this.emit("error", type, error);
|
|
1798
|
+
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Handle data received from a channel and emit the dataReceived event.
|
|
1801
|
+
* @param type - The type of channel the data was received on
|
|
1802
|
+
* @param data - The received binary data
|
|
1803
|
+
* @private
|
|
1804
|
+
*/
|
|
1805
|
+
onDataReceived(type, data) {
|
|
1806
|
+
const payload = {
|
|
1807
|
+
channelType: type,
|
|
1808
|
+
data
|
|
1809
|
+
};
|
|
1810
|
+
this.emit("data", payload);
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Get existing channel wrapper or create a new one (without RTCDataChannel).
|
|
1814
|
+
* @param type - The type of channel
|
|
1815
|
+
* @returns The DataChannel wrapper instance
|
|
1816
|
+
* @private
|
|
1817
|
+
*/
|
|
1818
|
+
getOrCreateChannelWrapper(type) {
|
|
1819
|
+
if (type === "reliable") {
|
|
1820
|
+
if (!this.reliableChannel) {
|
|
1821
|
+
this.reliableChannel = new DataChannel(type, this.logger);
|
|
1822
|
+
this.reliableChannel.setCallback((data) => this.onDataReceived(type, data));
|
|
1823
|
+
}
|
|
1824
|
+
return this.reliableChannel;
|
|
1825
|
+
} else {
|
|
1826
|
+
if (!this.lossyChannel) {
|
|
1827
|
+
this.lossyChannel = new DataChannel(type, this.logger);
|
|
1828
|
+
this.lossyChannel.setCallback((data) => this.onDataReceived(type, data));
|
|
1829
|
+
}
|
|
1830
|
+
return this.lossyChannel;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1530
1833
|
};
|
|
1531
1834
|
|
|
1532
1835
|
// src/deferred.ts
|
|
@@ -3609,9 +3912,9 @@ function deserializeServerMediaEvent(serializedMediaEvent) {
|
|
|
3609
3912
|
}
|
|
3610
3913
|
|
|
3611
3914
|
// src/internal.ts
|
|
3612
|
-
import
|
|
3915
|
+
import EventEmitter2 from "events";
|
|
3613
3916
|
var isTrackKind = (kind) => kind === "audio" || kind === "video";
|
|
3614
|
-
var TrackContextImpl = class extends
|
|
3917
|
+
var TrackContextImpl = class extends EventEmitter2 {
|
|
3615
3918
|
constructor(endpoint, trackId, metadata, simulcastConfig = { enabled: false, enabledVariants: [], disabledVariants: [] }) {
|
|
3616
3919
|
super();
|
|
3617
3920
|
this.endpoint = endpoint;
|
|
@@ -4422,11 +4725,12 @@ var Remote = class {
|
|
|
4422
4725
|
};
|
|
4423
4726
|
|
|
4424
4727
|
// src/webRTCEndpoint.ts
|
|
4425
|
-
var WebRTCEndpoint = class extends
|
|
4728
|
+
var WebRTCEndpoint = class extends EventEmitter3 {
|
|
4426
4729
|
localTrackManager;
|
|
4427
4730
|
remote;
|
|
4428
4731
|
local;
|
|
4429
4732
|
commandsQueue;
|
|
4733
|
+
dataChannelManager;
|
|
4430
4734
|
proposedIceServers = [];
|
|
4431
4735
|
logger;
|
|
4432
4736
|
bandwidthEstimation = BigInt(0);
|
|
@@ -4443,6 +4747,25 @@ var WebRTCEndpoint = class extends EventEmitter2 {
|
|
|
4443
4747
|
this.local = new Local(emit, sendEvent);
|
|
4444
4748
|
this.localTrackManager = new LocalTrackManager(this.local, sendEvent);
|
|
4445
4749
|
this.commandsQueue = new CommandsQueue(this.localTrackManager);
|
|
4750
|
+
const createDataChannelFn = (label, init) => {
|
|
4751
|
+
if (!this.connectionManager) {
|
|
4752
|
+
this.connectionManager = new ConnectionManager(this.proposedIceServers);
|
|
4753
|
+
}
|
|
4754
|
+
return this.connectionManager.createDataChannel(label, init);
|
|
4755
|
+
};
|
|
4756
|
+
const triggerRenegotiationFn = () => {
|
|
4757
|
+
this.sendMediaEvent({ renegotiateTracks: MediaEvent_RenegotiateTracks.create() });
|
|
4758
|
+
};
|
|
4759
|
+
this.dataChannelManager = new DataChannelManager(createDataChannelFn, triggerRenegotiationFn, this.logger);
|
|
4760
|
+
this.dataChannelManager.on("ready", () => {
|
|
4761
|
+
this.emit("dataChannelsReady");
|
|
4762
|
+
});
|
|
4763
|
+
this.dataChannelManager.on("data", (payload) => {
|
|
4764
|
+
this.emit("dataChannelPayload", payload);
|
|
4765
|
+
});
|
|
4766
|
+
this.dataChannelManager.on("error", (_, event) => {
|
|
4767
|
+
this.emit("dataChannelsError", new Error(`Data channel error event: ${event.type}`));
|
|
4768
|
+
});
|
|
4446
4769
|
}
|
|
4447
4770
|
/**
|
|
4448
4771
|
* Tries to connect to the RTC Engine. If user is successfully connected then {@link WebRTCEndpointEvents.connected}
|
|
@@ -4545,6 +4868,9 @@ var WebRTCEndpoint = class extends EventEmitter2 {
|
|
|
4545
4868
|
getBandwidthEstimation() {
|
|
4546
4869
|
return this.bandwidthEstimation;
|
|
4547
4870
|
}
|
|
4871
|
+
getDataChannelsReadiness() {
|
|
4872
|
+
return this.dataChannelManager?.getChannelsReadiness() ?? false;
|
|
4873
|
+
}
|
|
4548
4874
|
handleMediaEvent = async (event) => {
|
|
4549
4875
|
if (event.offerData) {
|
|
4550
4876
|
await this.onOfferData(event.offerData);
|
|
@@ -4913,6 +5239,58 @@ var WebRTCEndpoint = class extends EventEmitter2 {
|
|
|
4913
5239
|
this.emit("disconnectRequested", {});
|
|
4914
5240
|
this.cleanUp();
|
|
4915
5241
|
};
|
|
5242
|
+
/**
|
|
5243
|
+
* Create both reliable and lossy data channel.
|
|
5244
|
+
* This method must be called before publishData() can be used.
|
|
5245
|
+
* Emits the 'dataChannelsReady' event when both channels are open and ready.
|
|
5246
|
+
*
|
|
5247
|
+
* @example
|
|
5248
|
+
* ```ts
|
|
5249
|
+
* const webrtc = new WebRTCEndpoint();
|
|
5250
|
+
*
|
|
5251
|
+
* webrtc.on('dataChannelsReady', () => {
|
|
5252
|
+
* console.log('Data channels ready, can now send data');
|
|
5253
|
+
* webrtc.publishData(new TextEncoder().encode('Hello'), { reliable: true });
|
|
5254
|
+
* });
|
|
5255
|
+
*
|
|
5256
|
+
* webrtc.connectDataChannels();
|
|
5257
|
+
* ```
|
|
5258
|
+
*/
|
|
5259
|
+
connectDataChannels = () => {
|
|
5260
|
+
return this.dataChannelManager.connect();
|
|
5261
|
+
};
|
|
5262
|
+
/**
|
|
5263
|
+
* Publish data through a data channel.
|
|
5264
|
+
* The data channels must be created first by calling connectDataChannels() or enabling negotiateOnConnect.
|
|
5265
|
+
* Throws an error if the channel doesn't exist or isn't ready yet.
|
|
5266
|
+
*
|
|
5267
|
+
* @param data - The data to send as Uint8Array
|
|
5268
|
+
* @param options - Options specifying which channel to use (reliable or lossy)
|
|
5269
|
+
* @throws Error if the channel doesn't exist or isn't ready
|
|
5270
|
+
*
|
|
5271
|
+
* @example
|
|
5272
|
+
* ```ts
|
|
5273
|
+
* // Subscribe to incoming data
|
|
5274
|
+
* webrtc.on('dataReceived', ({ channelType, data }) => {
|
|
5275
|
+
* console.log(`Received on ${channelType}:`, new TextDecoder().decode(data));
|
|
5276
|
+
* });
|
|
5277
|
+
*
|
|
5278
|
+
* webrtc.on('dataChannelsReady', () => {
|
|
5279
|
+
* // Send reliable data
|
|
5280
|
+
* const data = new TextEncoder().encode('Hello World');
|
|
5281
|
+
* webrtc.publishData(data, { reliable: true });
|
|
5282
|
+
*
|
|
5283
|
+
* // Send lossy data for low-latency updates
|
|
5284
|
+
* const gameState = new Uint8Array([1, 2, 3, 4, 5]);
|
|
5285
|
+
* webrtc.publishData(gameState, { reliable: false });
|
|
5286
|
+
* });
|
|
5287
|
+
*
|
|
5288
|
+
* webrtc.connectDataChannels();
|
|
5289
|
+
* ```
|
|
5290
|
+
*/
|
|
5291
|
+
publishData = (data, options) => {
|
|
5292
|
+
this.dataChannelManager.publishData(data, options);
|
|
5293
|
+
};
|
|
4916
5294
|
/**
|
|
4917
5295
|
* Cleans up {@link WebRTCEndpoint} instance.
|
|
4918
5296
|
*/
|
|
@@ -4922,6 +5300,7 @@ var WebRTCEndpoint = class extends EventEmitter2 {
|
|
|
4922
5300
|
this.connectionManager?.getConnection().close();
|
|
4923
5301
|
this.commandsQueue.cleanUp();
|
|
4924
5302
|
this.localTrackManager.cleanUp();
|
|
5303
|
+
this.dataChannelManager?.cleanup();
|
|
4925
5304
|
}
|
|
4926
5305
|
this.connectionManager = void 0;
|
|
4927
5306
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fishjam-cloud/webrtc-client",
|
|
3
|
-
"version": "0.25.0
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Typescript client library for ExWebRTC/WebRTC endpoint in Membrane RTC Engine",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Fishjam Team",
|
|
@@ -75,6 +75,5 @@
|
|
|
75
75
|
"*.(js|ts|tsx)": [
|
|
76
76
|
"yarn lint"
|
|
77
77
|
]
|
|
78
|
-
}
|
|
79
|
-
"stableVersion": "0.24.0"
|
|
78
|
+
}
|
|
80
79
|
}
|