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

Commit 7a2e07f

Browse files
authored
Merge pull request #200 from jianjunz/sendparam
Simulcast support.
2 parents b9bd1a6 + 64125ee commit 7a2e07f

File tree

9 files changed

+155
-95
lines changed

9 files changed

+155
-95
lines changed

docs/mdfiles/changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
Change Log
22
==========
3+
# 4.3
4+
* The type of `conferenceClient.publish` method's option is changed from `AudioEncodingParameters` and `VideoEncodingParameters` to `RTCRtpSendParameters`.
5+
* `audio` and `video` of `PublicationSettings` is changed from `AudioPublicationSettings` and `VideoPublicationSettings` to `AudioPublicationSettings` array and `VideoPublicationSettings` array.
6+
* Add `rid` to `VideoSubscriptionConstraints`. When `rid` is specified, other constraints will be ignored.
7+
38
# 4.0.2
49
* Fix a compatibility issue for Chrome 69 beta.
510

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ const runSocketIOSample = function() {
6565
});
6666
}
6767
let $p = $(`<div id=${stream.id}resolutions> </div>`)
68-
for (const resolution of stream.capabilities.video.resolutions) {
68+
// TODO: Add resolutions from settings.
69+
for (const resolution of stream.extraCapabilities.video.resolutions) {
6970
const button = $('<button/>', {
7071
text: resolution.width + 'x' +
7172
resolution.height,
@@ -147,7 +148,11 @@ const runSocketIOSample = function() {
147148
mediaStream, new Owt.Base.StreamSourceInfo(
148149
'mic', 'camera'));
149150
$('.local video').get(0).srcObject = stream;
150-
conference.publish(localStream).then(publication => {
151+
conference.publish(localStream, {video:[
152+
{rid: 'q', active: true, scaleResolutionDownBy: 4.0},
153+
{rid: 'h', active: true, scaleResolutionDownBy: 2.0},
154+
{rid: 'f', active: true}
155+
]}).then(publication => {
151156
publicationGlobal = publication;
152157
mixStream(myRoom, publication.id, 'common')
153158
publication.addEventListener('error', (err) => {
@@ -176,7 +181,8 @@ const runSocketIOSample = function() {
176181
console.log('Subscription error: ' + err.error.message);
177182
})
178183
});
179-
for (const resolution of stream.capabilities.video.resolutions) {
184+
// TODO: Add resolutions from settings.
185+
for (const resolution of stream.extraCapabilities.video.resolutions) {
180186
const button = $('<button/>', {
181187
text: resolution.width + 'x' +
182188
resolution.height,

src/sdk/base/publication.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class AudioPublicationSettings {
3434
*/
3535
export class VideoPublicationSettings {
3636
// eslint-disable-next-line require-jsdoc
37-
constructor(codec, resolution, frameRate, bitrate, keyFrameInterval) {
37+
constructor(codec, resolution, frameRate, bitrate, keyFrameInterval, rid) {
3838
/**
3939
* @member {?Owt.Base.VideoCodecParameters} codec
4040
* @instance
@@ -67,6 +67,13 @@ export class VideoPublicationSettings {
6767
* @memberof Owt.Base.VideoPublicationSettings
6868
*/
6969
this.keyFrameInterval=keyFrameInterval;
70+
/**
71+
* @member {?number} rid
72+
* @instance
73+
* @classDesc Restriction identifier to identify the RTP Streams within an RTP session.
74+
* @memberof Owt.Base.VideoPublicationSettings
75+
*/
76+
this.rid=rid;
7077
}
7178
}
7279

@@ -80,13 +87,13 @@ export class PublicationSettings {
8087
// eslint-disable-next-line require-jsdoc
8188
constructor(audio, video) {
8289
/**
83-
* @member {Owt.Base.AudioPublicationSettings} audio
90+
* @member {Owt.Base.AudioPublicationSettings[]} audio
8491
* @instance
8592
* @memberof Owt.Base.PublicationSettings
8693
*/
8794
this.audio=audio;
8895
/**
89-
* @member {Owt.Base.VideoPublicationSettings} video
96+
* @member {Owt.Base.VideoPublicationSettings[]} video
9097
* @instance
9198
* @memberof Owt.Base.PublicationSettings
9299
*/
@@ -171,15 +178,17 @@ export class PublishOptions {
171178
// eslint-disable-next-line require-jsdoc
172179
constructor(audio, video) {
173180
/**
174-
* @member {?Array<Owt.Base.AudioEncodingParameters>} audio
181+
* @member {?Array<Owt.Base.AudioEncodingParameters> | ?Array<RTCRtpEncodingParameters>} audio
175182
* @instance
176183
* @memberof Owt.Base.PublishOptions
184+
* @desc Parameters for audio RtpSender. Publishing with RTCRtpEncodingParameters is an experimental feature. It is subject to change.
177185
*/
178186
this.audio = audio;
179187
/**
180-
* @member {?Array<Owt.Base.VideoEncodingParameters>} video
188+
* @member {?Array<Owt.Base.VideoEncodingParameters> | ?Array<RTCRtpEncodingParameters>} video
181189
* @instance
182190
* @memberof Owt.Base.PublishOptions
191+
* @desc Parameters for video RtpSender. Publishing with RTCRtpEncodingParameters is an experimental feature. It is subject to change.
183192
*/
184193
this.video = video;
185194
}

src/sdk/base/stream.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,12 @@ export class RemoteStream extends Stream {
168168
*/
169169
this.settings = undefined;
170170
/**
171-
* @member {Owt.Conference.SubscriptionCapabilities} capabilities
171+
* @member {Owt.Conference.SubscriptionCapabilities} extraCapabilities
172172
* @instance
173173
* @memberof Owt.Base.RemoteStream
174-
* @desc Capabilities remote endpoint provides for subscription. This property is only valid in conference mode.
174+
* @desc Extra capabilities remote endpoint provides for subscription. Extra capabilities don't include original settings. This property is only valid in conference mode.
175175
*/
176-
this.capabilities = undefined;
176+
this.extraCapabilities = undefined;
177177
}
178178
}
179179

src/sdk/conference/channel.js

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
3434
super();
3535
this._config = config;
3636
this._options = null;
37+
this._videoCodecs = undefined;
3738
this._signaling = signaling;
3839
this._pc = null;
3940
this._internalId = null; // It's publication ID or subscription ID.
@@ -75,14 +76,21 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
7576
}
7677
}
7778

78-
publish(stream, options) {
79+
publish(stream, options, videoCodecs) {
7980
if (options === undefined) {
8081
options = {audio: !!stream.mediaStream.getAudioTracks().length, video: !!stream
8182
.mediaStream.getVideoTracks().length};
8283
}
8384
if (typeof options !== 'object') {
8485
return Promise.reject(new TypeError('Options should be an object.'));
8586
}
87+
if ((this._isRtpEncodingParameters(options.audio) &&
88+
this._isOwtEncodingParameters(options.video)) ||
89+
(this._isOwtEncodingParameters(options.audio) &&
90+
this._isRtpEncodingParameters(options.video))) {
91+
return Promise.reject(new ConferenceError(
92+
'Mixing RTCRtpEncodingParameters and AudioEncodingParameters/VideoEncodingParameters is not allowed.'))
93+
}
8694
if (options.audio === undefined) {
8795
options.audio = !!stream.mediaStream.getAudioTracks().length;
8896
}
@@ -115,18 +123,20 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
115123
}
116124
}
117125
}
118-
if (typeof options.video === 'object') {
119-
if (!Array.isArray(options.video)) {
120-
return Promise.reject(new TypeError(
121-
'options.video should be a boolean or an array.'));
122-
}
126+
if (typeof options.video === 'object' && !Array.isArray(options.video)) {
127+
return Promise.reject(new TypeError(
128+
'options.video should be a boolean or an array.'));
129+
}
130+
if (this._isOwtEncodingParameters(options.video)) {
123131
for (const parameters of options.video) {
124-
if (!parameters.codec || typeof parameters.codec.name !== 'string' || (
125-
parameters.maxBitrate !== undefined && typeof parameters.maxBitrate
126-
!== 'number') || (parameters.codec.profile !== undefined
127-
&& typeof parameters.codec.profile !== 'string')) {
132+
if (!parameters.codec || typeof parameters.codec.name !== 'string' ||
133+
(
134+
parameters.maxBitrate !== undefined && typeof parameters
135+
.maxBitrate !==
136+
'number') || (parameters.codec.profile !== undefined &&
137+
typeof parameters.codec.profile !== 'string')) {
128138
return Promise.reject(new TypeError(
129-
'options.video has incorrect parameters.'));
139+
'options.video has incorrect parameters.'));
130140
}
131141
}
132142
}
@@ -149,9 +159,6 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
149159
}
150160
mediaOptions.audio = {};
151161
mediaOptions.audio.source = stream.source.audio;
152-
for (const track of stream.mediaStream.getAudioTracks()) {
153-
this._pc.addTrack(track, stream.mediaStream);
154-
}
155162
} else {
156163
mediaOptions.audio = false;
157164
}
@@ -174,9 +181,6 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
174181
},
175182
framerate: trackSettings.frameRate,
176183
};
177-
for (const track of stream.mediaStream.getVideoTracks()) {
178-
this._pc.addTrack(track, stream.mediaStream);
179-
}
180184
} else {
181185
mediaOptions.video = false;
182186
}
@@ -193,11 +197,28 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
193197
this._internalId = data.id;
194198
if (typeof this._pc.addTransceiver === 'function') {
195199
// |direction| seems not working on Safari.
196-
if (mediaOptions.audio && stream.mediaStream.getAudioTracks() > 0) {
197-
this._pc.addTransceiver('audio', {direction: 'sendonly'});
200+
if (mediaOptions.audio && stream.mediaStream.getAudioTracks().length >
201+
0) {
202+
const transceiverInit = {
203+
direction: 'sendonly'
204+
};
205+
if (this._isRtpEncodingParameters(options.audio)) {
206+
transceiverInit.sendEncodings = options.audio;
207+
}
208+
this._pc.addTransceiver(stream.mediaStream.getAudioTracks()[0],
209+
transceiverInit);
198210
}
199-
if (mediaOptions.video && stream.mediaStream.getVideoTracks() > 0) {
200-
this._pc.addTransceiver('video', {direction: 'sendonly'});
211+
if (mediaOptions.video && stream.mediaStream.getVideoTracks().length >
212+
0) {
213+
const transceiverInit = {
214+
direction: 'sendonly'
215+
};
216+
if (this._isRtpEncodingParameters(options.video)) {
217+
transceiverInit.sendEncodings = options.video;
218+
this._videoCodecs = videoCodecs;
219+
}
220+
this._pc.addTransceiver(stream.mediaStream.getVideoTracks()[0],
221+
transceiverInit);
201222
}
202223
}
203224
let localDesc;
@@ -235,27 +256,27 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
235256
subscribe(stream, options) {
236257
if (options === undefined) {
237258
options = {
238-
audio: !!stream.capabilities.audio,
239-
video: !!stream.capabilities.video,
259+
audio: !!stream.settings.audio,
260+
video: !!stream.settings.video,
240261
};
241262
}
242263
if (typeof options !== 'object') {
243264
return Promise.reject(new TypeError('Options should be an object.'));
244265
}
245266
if (options.audio === undefined) {
246-
options.audio = !!stream.capabilities.audio;
267+
options.audio = !!stream.settings.audio;
247268
}
248269
if (options.video === undefined) {
249-
options.video = !!stream.capabilities.video;
270+
options.video = !!stream.settings.video;
250271
}
251272
if ((options.audio !== undefined && typeof options.audio !== 'object' &&
252273
typeof options.audio !== 'boolean' && options.audio !== null) || (
253274
options.video !== undefined && typeof options.video !== 'object' &&
254275
typeof options.video !== 'boolean' && options.video !== null)) {
255276
return Promise.reject(new TypeError('Invalid options type.'));
256277
}
257-
if (options.audio && !stream.capabilities.audio || (options.video &&
258-
!stream.capabilities.video)) {
278+
if (options.audio && !stream.settings.audio || (options.video &&
279+
!stream.settings.video)) {
259280
return Promise.reject(new ConferenceError(
260281
'options.audio/video cannot be true or an object if there is no '
261282
+ 'audio/video track in remote stream.'
@@ -299,6 +320,7 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
299320
bitrate: options.video.bitrateMultiplier ? 'x'
300321
+ options.video.bitrateMultiplier.toString() : undefined,
301322
keyFrameInterval: options.video.keyFrameInterval,
323+
simulcastRid: options.video.rid
302324
};
303325
}
304326
} else {
@@ -637,11 +659,25 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
637659
}
638660

639661
_setRtpSenderOptions(sdp, options) {
662+
// SDP mugling is deprecated, moving to `setParameters`.
663+
if (this._isRtpEncodingParameters(options.audio) ||
664+
this._isRtpEncodingParameters(options.video)) {
665+
return sdp;
666+
}
640667
sdp = this._setMaxBitrate(sdp, options);
641668
return sdp;
642669
}
643670

644671
_setRtpReceiverOptions(sdp, options) {
672+
// _videoCodecs is a workaround for setting video codecs. It will be moved to RTCRtpSendParameters.
673+
if (this._isRtpEncodingParameters(options.video) && this._videoCodecs) {
674+
sdp = SdpUtils.reorderCodecs(sdp, 'video', this._videoCodecs);
675+
return sdp;
676+
}
677+
if (this._isRtpEncodingParameters(options.audio) ||
678+
this._isRtpEncodingParameters(options.video)) {
679+
return sdp;
680+
}
645681
sdp = this._setCodecOrder(sdp, options);
646682
return sdp;
647683
}
@@ -675,4 +711,23 @@ export class ConferencePeerConnectionChannel extends EventDispatcher {
675711
Logger.warning('Invalid data value for stream update info.');
676712
}
677713
}
714+
715+
_isRtpEncodingParameters(obj) {
716+
if (!Array.isArray(obj)) {
717+
return false;
718+
}
719+
// Only check the first one.
720+
const param = obj[0];
721+
return param.codecPayloadType || param.dtx || param.active || param
722+
.ptime || param.maxFramerate || param.scaleResolutionDownBy || param.rid;
723+
}
724+
725+
_isOwtEncodingParameters(obj) {
726+
if (!Array.isArray(obj)) {
727+
return false;
728+
}
729+
// Only check the first one.
730+
const param = obj[0];
731+
return !!param.codec;
732+
}
678733
}

src/sdk/conference/client.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const SignalingState = {
2929
CONNECTED: 3,
3030
};
3131

32-
const protocolVersion = '1.0';
32+
const protocolVersion = '1.1';
3333

3434
/* eslint-disable valid-jsdoc */
3535
/**
@@ -260,7 +260,8 @@ export const ConferenceClient = function(config, signalingImpl) {
260260
const stream = remoteStreams.get(streamInfo.id);
261261
stream.settings = StreamUtilsModule.convertToPublicationSettings(streamInfo
262262
.media);
263-
stream.capabilities = StreamUtilsModule.convertToSubscriptionCapabilities(
263+
stream.extraCapabilities = StreamUtilsModule
264+
.convertToSubscriptionCapabilities(
264265
streamInfo.media);
265266
const streamEvent = new EventModule.OwtEvent('updated');
266267
stream.dispatchEvent(streamEvent);
@@ -283,7 +284,8 @@ export const ConferenceClient = function(config, signalingImpl) {
283284
audioSourceInfo, videoSourceInfo), streamInfo.info.attributes);
284285
stream.settings = StreamUtilsModule.convertToPublicationSettings(
285286
streamInfo.media);
286-
stream.capabilities = StreamUtilsModule.convertToSubscriptionCapabilities(
287+
stream.extraCapabilities = StreamUtilsModule
288+
.convertToSubscriptionCapabilities(
287289
streamInfo.media);
288290
return stream;
289291
}
@@ -392,9 +394,10 @@ export const ConferenceClient = function(config, signalingImpl) {
392394
* @desc Publish a LocalStream to conference server. Other participants will be able to subscribe this stream when it is successfully published.
393395
* @param {Owt.Base.LocalStream} stream The stream to be published.
394396
* @param {Owt.Base.PublishOptions} options Options for publication.
397+
* @param {string[]} videoCodecs Video codec names for publishing. Valid values are 'VP8', 'VP9' and 'H264'. This parameter only valid when options.video is RTCRtpEncodingParameters. Publishing with RTCRtpEncodingParameters is an experimental feature. This parameter is subject to change.
395398
* @returns {Promise<Publication, Error>} Returned promise will be resolved with a newly created Publication once specific stream is successfully published, or rejected with a newly created Error if stream is invalid or options cannot be satisfied. Successfully published means PeerConnection is established and server is able to process media data.
396399
*/
397-
this.publish = function(stream, options) {
400+
this.publish = function(stream, options, videoCodecs) {
398401
if (!(stream instanceof StreamModule.LocalStream)) {
399402
return Promise.reject(new ConferenceError('Invalid stream.'));
400403
}
@@ -403,7 +406,7 @@ export const ConferenceClient = function(config, signalingImpl) {
403406
'Cannot publish a published stream.'));
404407
}
405408
const channel = createPeerConnectionChannel();
406-
return channel.publish(stream, options);
409+
return channel.publish(stream, options, videoCodecs);
407410
};
408411

409412
/**

src/sdk/conference/mixedstream.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ export class RemoteMixedStream extends StreamModule.RemoteStream {
3333

3434
this.settings = StreamUtilsModule.convertToPublicationSettings(info.media);
3535

36-
this.capabilities = new StreamUtilsModule.convertToSubscriptionCapabilities(
36+
this.extraCapabilities = new StreamUtilsModule
37+
.convertToSubscriptionCapabilities(
3738
info.media);
3839
}
3940
}

0 commit comments

Comments
 (0)