Skip to content
This repository was archived by the owner on Oct 25, 2024. It is now read-only.

Commit 7c7cd4c

Browse files
committed
Add data channel.
1 parent 8436248 commit 7c7cd4c

File tree

4 files changed

+236
-32
lines changed

4 files changed

+236
-32
lines changed

src/samples/conference/public/scripts/index.js

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const runSocketIOSample = function() {
3636
let myId;
3737
let subscriptionForMixedStream;
3838
let myRoom;
39+
let bidirectionalStream;
3940

4041
function getParameterByName(name) {
4142
name = name.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]');
@@ -143,6 +144,9 @@ const runSocketIOSample = function() {
143144
conference.join(token).then(resp => {
144145
myId = resp.self.id;
145146
myRoom = resp.id;
147+
conference.createDataStream().then(stream=>{
148+
bidirectionalStream=stream;
149+
});
146150
if(mediaUrl){
147151
startStreamingIn(myRoom, mediaUrl);
148152
}
@@ -191,33 +195,8 @@ const runSocketIOSample = function() {
191195
if(!subscribeForward){
192196
if (stream.source.audio === 'mixed' || stream.source.video ===
193197
'mixed') {
194-
<<<<<<< HEAD
195198
subscribeAndRenderVideo(stream);
196-
=======
197-
conference.subscribe(stream, {
198-
audio: {codecs:[{name:'opus'}]},
199-
video: true,
200-
transport: {protocol:'quic'}
201-
}).then((subscription) => {
202-
subscriptionForMixedStream = subscription;
203-
let $video = $(`<video controls autoplay id=${stream.id} style='display:block'>this browser does not supported video tag</video>`);
204-
$video.get(0).srcObject = stream.mediaStream;
205-
$('body').append($video);
206-
subscription.addEventListener('error', (err) => {
207-
console.log('Subscription error: ' + err.error.message);
208-
})
209-
});
210-
for (const resolution of stream.capabilities.video.resolutions) {
211-
const button = $('<button/>', {
212-
text: resolution.width + 'x' +
213-
resolution.height,
214-
click: () => {
215-
subscribeDifferentResolution(stream, resolution);
216-
}
217-
});
218-
button.appendTo($('body'));
219-
};
220-
>>>>>>> d4a3a3c... Initialize QUIC support.
199+
return;
221200
}
222201
} else if (stream.source.audio !== 'mixed') {
223202
subscribeAndRenderVideo(stream);

src/samples/conference/samplertcservice.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ app.use(function(req, res, next) {
6363
}
6464
});
6565

66-
icsREST.API.init('_service_ID_', '_service_KEY_', 'https://localhost:3000/', false);
66+
icsREST.API.init('5c9ee9f852f121d3c541674f', 'IjI073vAiP2t6EYu1paQOE7NrdDIsIutQ0u2Ee6U2sKVOeBgCrJlW3HJeYiCP1YRHudAhtFQQlmwDgZstHQp8V3f1X1ziQ/Bp8RqP8EpEAheubrRTPDqNUi9SNOwOLu2aVoJt8pxVkJAyH9vOvUlvGG+79wiE34VuKKpV2jdRzM=', 'https://jianjun-ubuntu.sh.intel.com:3000/', false);
6767

6868
var sampleRoom;
6969
var pageOption = { page: 1, per_page: 100 };

src/sdk/conference/client.js

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ import * as StreamModule from '../base/stream.js'
1616
import { Participant } from './participant.js'
1717
import { ConferenceInfo } from './info.js'
1818
import { ConferencePeerConnectionChannel, ConferenceQuicChannel } from './channel.js'
19+
import { ConferenceDataChannel } from './datachannel.js'
1920
import { RemoteMixedStream, ActiveAudioInputChangeEvent, LayoutChangeEvent } from './mixedstream.js'
2021
import * as StreamUtilsModule from './streamutils.js'
21-
import { timingSafeEqual } from 'crypto';
22+
import { timingSafeEqual, createSign } from 'crypto';
2223

2324
const SignalingState = {
2425
READY: 1,
@@ -112,6 +113,9 @@ export const ConferenceClient = function(config, signalingImpl) {
112113
const participants = new Map(); // Key is participant ID, value is a Participant object.
113114
const publishChannels = new Map(); // Key is MediaStream's ID, value is pc channel.
114115
const channels = new Map(); // Key is channel's internal ID, value is channel.
116+
let dataChannel = null;
117+
let dataChannelId = null;
118+
let incomingDataChannels = new Map(); // TODO: Mutiplexing.
115119

116120
/**
117121
* @function onSignalingMessage
@@ -122,11 +126,14 @@ export const ConferenceClient = function(config, signalingImpl) {
122126
*/
123127
function onSignalingMessage(notification, data) {
124128
if (notification === 'soac' || notification === 'progress') {
125-
if (!channels.has(data.id)) {
129+
if (channels.has(data.id)) {
130+
channels.get(data.id).onMessage(notification, data);
131+
} else if (dataChannelId === data.id) {
132+
dataChannel.onMessage(notification, data);
133+
} else {
126134
Logger.warning('Cannot find a channel for incoming data.');
127-
return;
128135
}
129-
channels.get(data.id).onMessage(notification, data);
136+
return;
130137
} else if (notification === 'stream') {
131138
if (data.status === 'add') {
132139
fireStreamAdded(data.data);
@@ -294,10 +301,16 @@ export const ConferenceClient = function(config, signalingImpl) {
294301
}
295302

296303
// eslint-disable-next-line require-jsdoc
297-
function createPeerConnectionChannel(transport) {
304+
function createSignalingForChannel() {
298305
// Construct an signaling sender/receiver for ConferencePeerConnection.
299306
const signalingForChannel = Object.create(EventModule.EventDispatcher);
300307
signalingForChannel.sendSignalingMessage = sendSignalingMessage;
308+
return signalingForChannel;
309+
}
310+
311+
// eslint-disable-next-line require-jsdoc
312+
function createPeerConnectionChannel(transport) {
313+
const signalingForChannel = createSignalingForChannel();
301314
let channel;
302315
if (transport && transport.protocol === 'quic') {
303316
channel = new ConferenceQuicChannel(config, signalingForChannel);
@@ -316,6 +329,11 @@ export const ConferenceClient = function(config, signalingImpl) {
316329
remoteStreams.clear();
317330
}
318331

332+
function createDataChannel() {
333+
const signalingForChannel = createSignalingForChannel();
334+
return new ConferenceDataChannel(config, signalingForChannel);
335+
}
336+
319337
Object.defineProperty(this, 'info', {
320338
configurable: false,
321339
get: () => {
@@ -423,6 +441,13 @@ export const ConferenceClient = function(config, signalingImpl) {
423441
if (!(stream instanceof StreamModule.RemoteStream)) {
424442
return Promise.reject(new ConferenceError('Invalid stream.'));
425443
}
444+
if(!stream.source.audio&&!stream.source.video){ // QUIC stream.
445+
const dataChannel=createDataChannel();
446+
dataChannel.addEventListener('id',messageEvent=>{
447+
incomingDataChannels.set(messageEvent.message, dataChannel);
448+
});
449+
return dataChannel.subscribe(stream);
450+
}
426451
const channel = createPeerConnectionChannel(options.transport);
427452
return channel.subscribe(stream, options);
428453
};
@@ -456,4 +481,19 @@ export const ConferenceClient = function(config, signalingImpl) {
456481
signalingState = SignalingState.READY;
457482
});
458483
};
484+
485+
/**
486+
* @function createDataStream
487+
* @memberOf Owt.Conference.ConferenceClient
488+
* @instance
489+
* @desc Create a bidirectional stream.
490+
* @return {Promise<void, Error>} Returned promise will be resolved with a bidirectional stream once stream is created.
491+
*/
492+
this.createDataStream = function() {
493+
dataChannel = createDataChannel();
494+
dataChannel.addEventListener('id', (messageEvent) => {
495+
dataChannelId = messageEvent.message;
496+
});
497+
return dataChannel.createStream();
498+
};
459499
};

src/sdk/conference/datachannel.js

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// Copyright (C) <2018> Intel Corporation
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
/* eslint-disable require-jsdoc */
6+
/* global Promise */
7+
8+
'use strict';
9+
10+
import Logger from '../base/logger.js';
11+
import {
12+
EventDispatcher,
13+
MessageEvent,
14+
} from '../base/event.js';
15+
16+
/**
17+
* @class ConferenceDataChannel
18+
* @classDesc A channel for a QUIC transport between client and conference server.
19+
* @hideconstructor
20+
* @private
21+
*/
22+
export class ConferenceDataChannel extends EventDispatcher {
23+
constructor(config, signaling) {
24+
super();
25+
this._config = config;
26+
this._signaling = signaling;
27+
this._iceTransport = null;
28+
this._quicTransport = null;
29+
this._internalId = null; // It's publication ID or subscription ID.
30+
this._pendingCandidates = [];
31+
this._ended = false;
32+
this._quicStreams = [];
33+
this._createStreamPromise = null;
34+
this._streamSubscribed = null;
35+
this._outgoing =
36+
true; // This is actually a bidirectional channel. But server side cannot handle mutiplexing at this time.
37+
}
38+
39+
/**
40+
* @function onMessage
41+
* @desc Received a message from conference portal. Defined in client-server protocol.
42+
* @param {string} notification Notification type.
43+
* @param {object} message Message received.
44+
* @private
45+
*/
46+
onMessage(notification, message) {
47+
switch (notification) {
48+
case 'progress':
49+
if (message.status === 'soac') {
50+
this._soacHandler(message.data);
51+
} else if (message.status === 'ready') {
52+
this._readyHandler();
53+
} else if (message.status === 'error') {
54+
this._errorHandler(message.data);
55+
}
56+
break;
57+
case 'stream':
58+
this._onStreamEvent(message);
59+
break;
60+
default:
61+
Logger.warning('Unknown notification from MCU.');
62+
}
63+
}
64+
65+
/**
66+
* @function createStream
67+
* @desc Create a bidirectional stream.
68+
* @private
69+
*/
70+
createStream() {
71+
if (this._quicTransport && this._quicTransport.state == 'connected') {
72+
const stream = this._quicTransport.createStream()
73+
this._quicStreams.push(stream);
74+
return Promise.resolve(stream);
75+
}
76+
if (!this._quicTransport) {
77+
this._initialize();
78+
}
79+
return new Promise((resolve, reject) => {
80+
this._createStreamPromise = {
81+
resolve: resolve,
82+
reject: reject
83+
};
84+
});
85+
}
86+
87+
subscribe(stream) {
88+
this._outgoing=false;
89+
this._streamSubscribed = stream;
90+
if (!this._quicTransport) {
91+
this.createStream();
92+
}
93+
}
94+
95+
_onicecandidate(event) {
96+
if (event.candidate) {
97+
this._signaling.sendSignalingMessage('soac', {
98+
id: this._internalId,
99+
signaling: {
100+
type: 'candidate',
101+
candidate: {
102+
candidate: event.candidate.candidate,
103+
sdpMid: 0,
104+
sdpMLineIndex: 0
105+
}
106+
}
107+
});
108+
}
109+
}
110+
111+
_onicestatechange(event) {
112+
Logger.info('onstatechange: ' + this._iceTransport.state);
113+
if (this._iceTransport.state === 'connected' && this._quicTransport
114+
.state === 'new') {
115+
this._quicTransport.connect();
116+
}
117+
}
118+
119+
_onquicstatechange(event) {
120+
Logger.info('on QUIC state change: ' + this._quicTransport.state);
121+
if (this._quicTransport.state === 'connected') {
122+
if (this._quicStreams.length === 0) {
123+
this._quicStreams.push(this._quicTransport.createStream());
124+
if (this._createStreamPromise) {
125+
this._createStreamPromise.resolve(this._quicStreams[0]);
126+
}
127+
}
128+
}
129+
}
130+
131+
_createQuicTransport() {
132+
this._iceTransport = new RTCIceTransport(this._config.rtcConfiguration ||
133+
{});
134+
this._iceTransport.onicecandidate = (event) => {
135+
this._onicecandidate.apply(this, [event]);
136+
};
137+
this._iceTransport.onstatechange = (event) => {
138+
this._onicestatechange.apply(this, [event]);
139+
};
140+
this._quicTransport = new RTCQuicTransport(this._iceTransport);
141+
this._quicTransport.onstatechange = (event) => {
142+
this._onquicstatechange.apply(this, [event]);
143+
}
144+
Logger.info('QUIC key: ' + Array.from(new Uint8Array(this._quicTransport
145+
.getKey())));
146+
this._signaling.sendSignalingMessage('soac', {
147+
id: this._internalId,
148+
signaling: {
149+
type: 'quic-p2p-client-parameters',
150+
quicKey: Array.from(new Uint8Array(this._quicTransport.getKey())),
151+
iceParameters: this._iceTransport.getLocalParameters()
152+
}
153+
});
154+
this._iceTransport.gather({});
155+
}
156+
157+
_initialize() {
158+
this._signaling.sendSignalingMessage(this._outgoing ? 'publish' :
159+
'subscribe', {
160+
media: false,
161+
//media: {audio:{source:"mic"},video:{source:"camera",parameters:{resolution:{width:640,height:480},framerate:20}}}, // Fake media info here because server requires this info.
162+
data: this._outgoing ? true : {
163+
from: this._streamSubscribed.id
164+
}
165+
}).then((data) => {
166+
const messageEvent = new MessageEvent('id', {
167+
message: data.id,
168+
origin: undefined,
169+
});
170+
this.dispatchEvent(messageEvent);
171+
this._internalId = data.id;
172+
this._createQuicTransport();
173+
});
174+
}
175+
176+
_soacHandler(soacMessage) {
177+
if (soacMessage.type === 'quic-p2p-server-parameters') {
178+
this._iceTransport.start(soacMessage.iceParameters);
179+
}
180+
}
181+
182+
_readyHandler() {
183+
Logger.info('Ready.');
184+
}
185+
};

0 commit comments

Comments
 (0)