add very early datachannel support

This commit is contained in:
Bruno Windels 2022-04-11 15:53:34 +02:00
parent c02e1de001
commit b84c90891c
5 changed files with 32 additions and 15 deletions

View file

@ -22,7 +22,8 @@ export class LocalMedia {
constructor( constructor(
public readonly cameraTrack?: Track, public readonly cameraTrack?: Track,
public readonly screenShareTrack?: Track, public readonly screenShareTrack?: Track,
public readonly microphoneTrack?: AudioTrack public readonly microphoneTrack?: AudioTrack,
public readonly dataChannelOptions?: RTCDataChannelInit,
) {} ) {}
withTracks(tracks: Track[]) { withTracks(tracks: Track[]) {
@ -32,7 +33,11 @@ export class LocalMedia {
if (cameraTrack && microphoneTrack && cameraTrack.streamId !== microphoneTrack.streamId) { if (cameraTrack && microphoneTrack && cameraTrack.streamId !== microphoneTrack.streamId) {
throw new Error("The camera and audio track should have the same stream id"); throw new Error("The camera and audio track should have the same stream id");
} }
return new LocalMedia(cameraTrack, screenShareTrack, microphoneTrack as AudioTrack); return new LocalMedia(cameraTrack, screenShareTrack, microphoneTrack as AudioTrack, this.dataChannelOptions);
}
withDataChannel(options: RTCDataChannelInit): LocalMedia {
return new LocalMedia(this.cameraTrack, this.screenShareTrack, this.microphoneTrack as AudioTrack, options);
} }
get tracks(): Track[] { get tracks(): Track[] {

View file

@ -85,6 +85,8 @@ export class PeerCall implements IDisposable {
private sentEndOfCandidates: boolean = false; private sentEndOfCandidates: boolean = false;
private iceDisconnectedTimeout?: Timeout; private iceDisconnectedTimeout?: Timeout;
private _dataChannel?: any;
constructor( constructor(
private callId: string, private callId: string,
private readonly options: Options, private readonly options: Options,
@ -112,7 +114,12 @@ export class PeerCall implements IDisposable {
outer.options.emitUpdate(outer, undefined); outer.options.emitUpdate(outer, undefined);
}); });
}, },
onDataChannelChanged(dataChannel: DataChannel | undefined) {}, onRemoteDataChannel(dataChannel: any | undefined) {
outer.logItem.wrap("onRemoteDataChannel", log => {
outer._dataChannel = dataChannel;
outer.options.emitUpdate(outer, undefined);
});
},
onNegotiationNeeded() { onNegotiationNeeded() {
const promiseCreator = () => { const promiseCreator = () => {
return outer.logItem.wrap("onNegotiationNeeded", log => { return outer.logItem.wrap("onNegotiationNeeded", log => {
@ -127,6 +134,8 @@ export class PeerCall implements IDisposable {
}); });
} }
get dataChannel(): any | undefined { return this._dataChannel; }
get state(): CallState { return this._state; } get state(): CallState { return this._state; }
get remoteTracks(): Track[] { get remoteTracks(): Track[] {
@ -144,6 +153,9 @@ export class PeerCall implements IDisposable {
for (const t of this.localMedia.tracks) { for (const t of this.localMedia.tracks) {
this.peerConnection.addTrack(t); this.peerConnection.addTrack(t);
} }
if (this.localMedia.dataChannelOptions) {
this._dataChannel = this.peerConnection.createDataChannel(this.localMedia.dataChannelOptions);
}
// after adding the local tracks, and wait for handleNegotiation to be called, // after adding the local tracks, and wait for handleNegotiation to be called,
// or invite glare where we give up our invite and answer instead // or invite glare where we give up our invite and answer instead
await this.waitForState([CallState.InviteSent, CallState.CreateAnswer]); await this.waitForState([CallState.InviteSent, CallState.CreateAnswer]);
@ -160,7 +172,9 @@ export class PeerCall implements IDisposable {
for (const t of this.localMedia.tracks) { for (const t of this.localMedia.tracks) {
this.peerConnection.addTrack(t); this.peerConnection.addTrack(t);
} }
if (this.localMedia.dataChannelOptions) {
this._dataChannel = this.peerConnection.createDataChannel(this.localMedia.dataChannelOptions);
}
let myAnswer: RTCSessionDescriptionInit; let myAnswer: RTCSessionDescriptionInit;
try { try {
myAnswer = await this.peerConnection.createAnswer(); myAnswer = await this.peerConnection.createAnswer();

View file

@ -66,6 +66,10 @@ export class Member {
return this.callDeviceMembership.device_id; return this.callDeviceMembership.device_id;
} }
get dataChannel(): any | undefined {
return this.peerCall?.dataChannel;
}
/** @internal */ /** @internal */
connect(localMedia: LocalMedia) { connect(localMedia: LocalMedia) {
this.logItem.wrap("connect", () => { this.logItem.wrap("connect", () => {

View file

@ -26,21 +26,15 @@ export interface PeerConnectionHandler {
onLocalIceCandidate(candidate: RTCIceCandidate); onLocalIceCandidate(candidate: RTCIceCandidate);
onIceGatheringStateChange(state: RTCIceGatheringState); onIceGatheringStateChange(state: RTCIceGatheringState);
onRemoteTracksChanged(tracks: Track[]); onRemoteTracksChanged(tracks: Track[]);
onDataChannelChanged(dataChannel: DataChannel | undefined); onRemoteDataChannel(dataChannel: any | undefined);
onNegotiationNeeded(); onNegotiationNeeded();
// request the type of incoming stream // request the type of incoming stream
getPurposeForStreamId(streamId: string): SDPStreamMetadataPurpose; getPurposeForStreamId(streamId: string): SDPStreamMetadataPurpose;
} }
// does it make sense to wrap this?
export interface DataChannel {
close();
send();
}
export interface PeerConnection { export interface PeerConnection {
notifyStreamPurposeChanged(): void; notifyStreamPurposeChanged(): void;
get remoteTracks(): Track[]; get remoteTracks(): Track[];
get dataChannel(): DataChannel | undefined;
get iceGatheringState(): RTCIceGatheringState; get iceGatheringState(): RTCIceGatheringState;
get signalingState(): RTCSignalingState; get signalingState(): RTCSignalingState;
get localDescription(): RTCSessionDescription | undefined; get localDescription(): RTCSessionDescription | undefined;
@ -52,7 +46,7 @@ export interface PeerConnection {
addTrack(track: Track): void; addTrack(track: Track): void;
removeTrack(track: Track): boolean; removeTrack(track: Track): boolean;
replaceTrack(oldTrack: Track, newTrack: Track): Promise<boolean>; replaceTrack(oldTrack: Track, newTrack: Track): Promise<boolean>;
createDataChannel(): DataChannel; createDataChannel(options: RTCDataChannelInit): any;
dispose(): void; dispose(): void;
close(): void; close(): void;
} }

View file

@ -46,7 +46,6 @@ class DOMPeerConnection implements PeerConnection {
} }
get remoteTracks(): Track[] { return this._remoteTracks; } get remoteTracks(): Track[] { return this._remoteTracks; }
get dataChannel(): DataChannel | undefined { return undefined; }
get iceGatheringState(): RTCIceGatheringState { return this.peerConnection.iceGatheringState; } get iceGatheringState(): RTCIceGatheringState { return this.peerConnection.iceGatheringState; }
get localDescription(): RTCSessionDescription | undefined { return this.peerConnection.localDescription ?? undefined; } get localDescription(): RTCSessionDescription | undefined { return this.peerConnection.localDescription ?? undefined; }
get signalingState(): RTCSignalingState { return this.peerConnection.signalingState; } get signalingState(): RTCSignalingState { return this.peerConnection.signalingState; }
@ -119,8 +118,8 @@ class DOMPeerConnection implements PeerConnection {
} }
} }
createDataChannel(): DataChannel { createDataChannel(options: RTCDataChannelInit): any {
return undefined as any;// new DataChannel(this.peerConnection.createDataChannel()); return this.peerConnection.createDataChannel("channel", options);
} }
private registerHandler() { private registerHandler() {
@ -164,6 +163,7 @@ class DOMPeerConnection implements PeerConnection {
this.handler.onNegotiationNeeded(); this.handler.onNegotiationNeeded();
break; break;
case "datachannel": case "datachannel":
this.handler.onRemoteDataChannel((evt as RTCDataChannelEvent).channel);
break; break;
} }
} }