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

Commit 7d31677

Browse files
authored
Subscribed streams are associated with subscriptions. (#488)
When subscribe a stream in conference mode, the subscribed stream no longer associated with RemoteStream. It allows a RemoteStream to be subscribed multiple times. It also allows a subscription has audio and video track from different remote streams. Since the signaling protocol defined by server side does not provide an ID for a track, the SDK usually use stream ID + track kind to identify a track. The stream ID and track ID mentioned in conference mode indicate the ID assigned by conference sever, they could be different from MediaStream ID and MediaStreamTrack ID.
1 parent d7ab0bd commit 7d31677

File tree

5 files changed

+55
-50
lines changed

5 files changed

+55
-50
lines changed

docs/mdfiles/changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
Change Log
22
==========
3+
# 5.1
4+
* When subscribe a stream in conference mode, the subscribed MediaStream or BidirectionalStream is associated with a `Owt.Conference.Subscription` instead of a `Owt.Base.RemoteStream`. The `stream` property of a RemoteStream in conference mode is always undefined, while a new property `stream` is added to `Subscription`. It allows a RemoteStream to be subscribed multiple times, as well as subscribing audio and video tracks from different streams.
35
# 5.0
46
* Add WebTransport support for conference mode, see [this design doc](../../design/webtransport.md) for detailed information.
57
* All publications and subscriptions for the same conference use the same `PeerConnection`.

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,15 @@ const runSocketIOSample = function() {
9494
}).then((
9595
subscription) => {
9696
subscirptionLocal = subscription;
97-
$(`#${stream.id}`).get(0).srcObject = stream.mediaStream;
97+
$(`#${stream.id}`).get(0).srcObject = subscription.stream;
9898
});
9999
}
100100
let $p = createResolutionButtons(stream, subscribeDifferentResolution);
101101
conference.subscribe(stream)
102102
.then((subscription)=>{
103103
subscirptionLocal = subscription;
104104
let $video = $(`<video controls autoplay id=${stream.id} style="display:block" >this browser does not supported video tag</video>`);
105-
$video.get(0).srcObject = stream.mediaStream;
105+
$video.get(0).srcObject = subscription.stream;
106106
$p.append($video);
107107
}, (err)=>{ console.log('subscribe failed', err);
108108
});

src/sdk/base/stream.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,9 @@ export class LocalStream extends Stream {
149149
}
150150
/**
151151
* @class RemoteStream
152-
* @classDesc Stream sent from a remote endpoint.
152+
* @classDesc Stream sent from a remote endpoint. In conference mode,
153+
* RemoteStream's stream is always undefined. Please get MediaStream or
154+
* ReadableStream from subscription's stream property.
153155
* Events:
154156
*
155157
* | Event Name | Argument Type | Fired when |

src/sdk/conference/channel.js

Lines changed: 29 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
4343
this._pendingCandidates = [];
4444
this._subscribePromises = new Map(); // internalId => { resolve, reject }
4545
this._publishPromises = new Map(); // internalId => { resolve, reject }
46-
this._subscribedStreams = new Map(); // intenalId => RemoteStream
4746
this._publications = new Map(); // PublicationId => Publication
4847
this._subscriptions = new Map(); // SubscriptionId => Subscription
4948
this._publishTransceivers = new Map(); // internalId => { id, transceivers: [Transceiver] }
@@ -52,7 +51,6 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
5251
// Timer for PeerConnection disconnected. Will stop connection after timer.
5352
this._disconnectTimer = null;
5453
this._ended = false;
55-
this._stopped = false;
5654
// Channel ID assigned by conference
5755
this._id = undefined;
5856
// Used to create internal ID for publication/subscription
@@ -61,6 +59,7 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
6159
this._sdpResolverMap = new Map(); // internalId => {finish, resolve, reject}
6260
this._sdpResolvers = []; // [{finish, resolve, reject}]
6361
this._sdpResolveNum = 0;
62+
this._remoteMediaStreams = new Map(); // Key is subscription ID, value is MediaStream.
6463
}
6564

6665
/**
@@ -483,7 +482,6 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
483482
offerOptions.offerToReceiveVideo = !!options.video;
484483
}
485484
this._subscribeTransceivers.set(internalId, {transceivers});
486-
this._subscribedStreams.set(internalId, stream);
487485

488486
let localDesc;
489487
this._pc.createOffer(offerOptions).then((desc) => {
@@ -674,11 +672,6 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
674672
new ConferenceError('Failed to subscribe'));
675673
}
676674
}
677-
// Clear media stream
678-
if (this._subscribedStreams.has(internalId)) {
679-
this._subscribedStreams.get(internalId).mediaStream = null;
680-
this._subscribedStreams.delete(internalId);
681-
}
682675
if (this._sdpResolverMap.has(internalId)) {
683676
const resolver = this._sdpResolverMap.get(internalId);
684677
if (!resolver.finish) {
@@ -732,31 +725,24 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
732725

733726
_onRemoteStreamAdded(event) {
734727
Logger.debug('Remote stream added.');
735-
let find = false;
736728
for (const [internalId, sub] of this._subscribeTransceivers) {
737-
const subscriptionId = sub.id;
738729
if (sub.transceivers.find((t) => t.transceiver === event.transceiver)) {
739-
find = true;
740-
const subscribedStream = this._subscribedStreams.get(internalId);
741-
if (!subscribedStream.mediaStream) {
742-
this._subscribedStreams.get(internalId).mediaStream =
743-
event.streams[0];
744-
// Resolve subscription if ready handler has been called
745-
const subscription = this._subscriptions.get(subscriptionId);
746-
if (subscription) {
730+
if (this._subscriptions.has(sub.id)) {
731+
const subscription = this._subscriptions.get(sub.id);
732+
subscription.stream = event.streams[0];
733+
if (this._subscribePromises.has(internalId)) {
747734
this._subscribePromises.get(internalId).resolve(subscription);
735+
this._subscribePromises.delete(internalId);
748736
}
749737
} else {
750-
// Add track to the existing stream
751-
subscribedStream.mediaStream.addTrack(event.track);
738+
this._remoteMediaStreams.set(sub.id, event.streams[0]);
752739
}
740+
return;
753741
}
754742
}
755-
if (!find) {
756-
// This is not expected path. However, this is going to happen on Safari
757-
// because it does not support setting direction of transceiver.
758-
Logger.warning('Received remote stream without subscription.');
759-
}
743+
// This is not expected path. However, this is going to happen on Safari
744+
// because it does not support setting direction of transceiver.
745+
Logger.warning('Received remote stream without subscription.');
760746
}
761747

762748
_onLocalIceCandidate(event) {
@@ -889,23 +875,18 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
889875
_readyHandler(sessionId) {
890876
const internalId = this._reverseIdMap.get(sessionId);
891877
if (this._subscribePromises.has(internalId)) {
892-
const subscription = new Subscription(sessionId, () => {
878+
const mediaStream = this._remoteMediaStreams.get(sessionId);
879+
const subscription = new Subscription(sessionId, mediaStream, () => {
893880
this._unsubscribe(internalId);
894881
}, () => this._getStats(),
895882
(trackKind) => this._muteOrUnmute(sessionId, true, false, trackKind),
896883
(trackKind) => this._muteOrUnmute(sessionId, false, false, trackKind),
897884
(options) => this._applyOptions(sessionId, options));
898885
this._subscriptions.set(sessionId, subscription);
899-
// Fire subscription's ended event when associated stream is ended.
900-
this._subscribedStreams.get(internalId).addEventListener('ended', () => {
901-
if (this._subscriptions.has(sessionId)) {
902-
this._subscriptions.get(sessionId).dispatchEvent(
903-
'ended', new OwtEvent('ended'));
904-
}
905-
});
906-
// Resolve subscription if mediaStream is ready
907-
if (this._subscribedStreams.get(internalId).mediaStream) {
886+
// Resolve subscription if mediaStream is ready.
887+
if (this._subscriptions.get(sessionId).stream) {
908888
this._subscribePromises.get(internalId).resolve(subscription);
889+
this._subscribePromises.delete(internalId);
909890
}
910891
} else if (this._publishPromises.has(internalId)) {
911892
const publication = new Publication(sessionId, () => {
@@ -1056,21 +1037,23 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
10561037
return sdp;
10571038
}
10581039

1059-
// Handle stream event sent from MCU. Some stream events should be publication
1060-
// event or subscription event. It will be handled here.
1040+
// Handle stream event sent from MCU. Some stream update events sent from
1041+
// server, more specifically audio.status and video.status events should be
1042+
// publication event or subscription events. They don't change MediaStream's
1043+
// status. See
1044+
// https://github.com/open-webrtc-toolkit/owt-server/blob/master/doc/Client-Portal%20Protocol.md#339-participant-is-notified-on-streams-update-in-room
1045+
// for more information.
10611046
_onStreamEvent(message) {
10621047
const eventTargets = [];
10631048
if (this._publications.has(message.id)) {
10641049
eventTargets.push(this._publications.get(message.id));
10651050
}
1066-
for (const [internalId, subscribedStream] of this._subscribedStreams) {
1067-
if (message.id === subscribedStream.id) {
1068-
const subscriptionId = this._subscribeTransceivers.get(internalId).id;
1069-
eventTargets.push(this._subscriptions.get(subscriptionId));
1070-
break;
1051+
for (const subscription of this._subscriptions) {
1052+
if (message.id === subscription._audioTrackId ||
1053+
message.id === subscription._videoTrackId) {
1054+
eventTargets.push(subscription);
10711055
}
10721056
}
1073-
10741057
if (!eventTargets.length) {
10751058
return;
10761059
}
@@ -1100,9 +1083,9 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
11001083
// Only check the first one.
11011084
const param = obj[0];
11021085
return !!(
1103-
param.codecPayloadType || param.dtx || param.active || param.ptime ||
1104-
param.maxFramerate || param.scaleResolutionDownBy || param.rid ||
1105-
param.scalabilityMode);
1086+
param.codecPayloadType || param.dtx || param.active || param.ptime ||
1087+
param.maxFramerate || param.scaleResolutionDownBy || param.rid ||
1088+
param.scalabilityMode);
11061089
}
11071090

11081091
_isOwtEncodingParameters(obj) {

src/sdk/conference/subscription.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ export class SubscriptionUpdateOptions {
270270
*/
271271
export class Subscription extends EventDispatcher {
272272
// eslint-disable-next-line require-jsdoc
273-
constructor(id, stop, getStats, mute, unmute, applyOptions) {
273+
constructor(id, stream, stop, getStats, mute, unmute, applyOptions) {
274274
super();
275275
if (!id) {
276276
throw new TypeError('ID cannot be null or undefined.');
@@ -285,6 +285,19 @@ export class Subscription extends EventDispatcher {
285285
writable: false,
286286
value: id,
287287
});
288+
/**
289+
* @member {MediaStream | BidirectionalStream} stream
290+
* @instance
291+
* @memberof Owt.Conference.Subscription
292+
*/
293+
Object.defineProperty(this, 'stream', {
294+
configurable: false,
295+
// TODO: It should be a readonly property, but current implementation
296+
// creates Subscription after receiving 'ready' from server. At this time,
297+
// MediaStream may not be available.
298+
writable: true,
299+
value: stream,
300+
});
288301
/**
289302
* @function stop
290303
* @instance
@@ -328,5 +341,10 @@ export class Subscription extends EventDispatcher {
328341
* @returns {Promise<undefined, Error>}
329342
*/
330343
this.applyOptions = applyOptions;
344+
345+
// Track is not defined in server protocol. So these IDs are equal to their
346+
// stream's ID at this time.
347+
this._audioTrackId = undefined;
348+
this._videoTrackId = undefined;
331349
}
332350
}

0 commit comments

Comments
 (0)