diff --git a/docs/design/owt_with_webrtc_apis.md b/docs/design/owt_with_webrtc_apis.md index 351a1c28..43131541 100644 --- a/docs/design/owt_with_webrtc_apis.md +++ b/docs/design/owt_with_webrtc_apis.md @@ -9,8 +9,8 @@ OWT(Open WebRTC Toolkit) Client SDKs provide convenient APIs to create, publish, - Set preferred codecs. - Disable or enable RTX / RED / FEC. #### API Changes -- A new method `getSender` will be added to `Publication`. It returns an `RTCRtpSender` for certain `Publication`. -- A new method `getReceiver` will be added to `Subscription`. It returns an `RTCRtpReceiver` for certain `Subscription`. +- A new member `rtpTransceivers` will be added to `TransportSettings`. It returns an array `RTCRtpReceiver`s for RTP transport. +- A new member `transport` will be added to `Publication` and `Subscription`. Developers could get `RTPTransceiver`s from its `rtpTransceivers` property. - A new method `addTransceiver(DOMString trackKind, sequence sendEncodings)` will be added to `ConferenceClient`. It invokes `RTCPeerConnection.addTransceiver(trackKind, {direction:inactive, sendEncodings:sendEncodings})`, returns an `RTCRtpTransceiver`. Please note that direction is `inactive` until a `publish` with return transceiver is called. - The second parameter of `ConferenceClient.publish` accepts an `RTCRtpTransceiver` created by `RTCPeerConnection.addTransceiver`. When this method is called, certain `RTCRtpTransceiver`'s direction is changed to `sendonly`, and its sender's `setStreams` is called with the first parameter's `mediaStream`. #### Server Requirements diff --git a/docs/mdfiles/changelog.md b/docs/mdfiles/changelog.md index 6d773b58..a4ded8a2 100644 --- a/docs/mdfiles/changelog.md +++ b/docs/mdfiles/changelog.md @@ -2,6 +2,8 @@ Change Log ========== # 5.1 * 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. +* Add a new property `transport` to `Publication` for getting `TransportSettings`. +* Add a new property `rtpTransceivers` to `TransportSettings` and `TransportConstraints`. # 5.0 * Add WebTransport support for conference mode, see [this design doc](../../design/webtransport.md) for detailed information. * All publications and subscriptions for the same conference use the same `PeerConnection`. diff --git a/src/sdk/base/publication.js b/src/sdk/base/publication.js index 9fb18a88..7ead125e 100644 --- a/src/sdk/base/publication.js +++ b/src/sdk/base/publication.js @@ -123,7 +123,7 @@ export class PublicationSettings { */ export class Publication extends EventDispatcher { // eslint-disable-next-line require-jsdoc - constructor(id, stop, getStats, mute, unmute) { + constructor(id, transport, stop, getStats, mute, unmute) { super(); /** * @member {string} id @@ -135,6 +135,18 @@ export class Publication extends EventDispatcher { writable: false, value: id ? id : Utils.createUuid(), }); + /** + * @member {Owt.Base.TransportSettings} transport + * @instance + * @readonly + * @desc Transport settings for the publication. + * @memberof Owt.Base.Publication + */ + Object.defineProperty(this, 'transport', { + configurable: false, + writable: false, + value: transport, + }); /** * @function stop * @instance diff --git a/src/sdk/base/transport.js b/src/sdk/base/transport.js index e79afbf4..3bb5a79a 100644 --- a/src/sdk/base/transport.js +++ b/src/sdk/base/transport.js @@ -56,18 +56,29 @@ export class TransportSettings { // eslint-disable-next-line require-jsdoc constructor(type, id) { /** - * @member {Array.} type + * @member {Owt.Base.TransportType} type * @instance - * @memberof Owt.Base.TransportConstraints + * @memberof Owt.Base.TransportSettings * @desc Transport type for publication and subscription. */ this.type = type; /** * @member {string} id * @instance - * @memberof Owt.Base.TransportConstraints + * @memberof Owt.Base.TransportSettings * @desc Transport ID. */ this.id = id; + + /** + * @member {?Array.} rtpTransceivers + * @instance + * @memberof Owt.Base.TransportSettings + * @desc A list of RTCRtpTransceiver associated with the publication or + * subscription. It's only available in conference mode when TransportType + * is webrtc. + * @see {@link https://w3c.github.io/webrtc-pc/#rtcrtptransceiver-interface|RTCRtpTransceiver Interface of WebRTC 1.0}. + */ + this.rtpTransceivers = undefined; } } diff --git a/src/sdk/conference/channel.js b/src/sdk/conference/channel.js index dcb35994..e142bfe7 100644 --- a/src/sdk/conference/channel.js +++ b/src/sdk/conference/channel.js @@ -21,6 +21,7 @@ import {Subscription} from './subscription.js'; import {ConferenceError} from './error.js'; import * as Utils from '../base/utils.js'; import * as SdpUtils from '../base/sdputils.js'; +import {TransportSettings, TransportType} from '../base/transport.js'; /** * @class ConferencePeerConnectionChannel @@ -876,12 +877,19 @@ export class ConferencePeerConnectionChannel extends EventDispatcher { const internalId = this._reverseIdMap.get(sessionId); if (this._subscribePromises.has(internalId)) { const mediaStream = this._remoteMediaStreams.get(sessionId); - const subscription = new Subscription(sessionId, mediaStream, () => { - this._unsubscribe(internalId); - }, () => this._getStats(), - (trackKind) => this._muteOrUnmute(sessionId, true, false, trackKind), - (trackKind) => this._muteOrUnmute(sessionId, false, false, trackKind), - (options) => this._applyOptions(sessionId, options)); + const transportSettings = + new TransportSettings(TransportType.WEBRTC, this._id); + transportSettings.rtpTransceivers = + this._subscribeTransceivers.get(internalId).transceivers; + const subscription = new Subscription( + sessionId, mediaStream, transportSettings, + () => { + this._unsubscribe(internalId); + }, + () => this._getStats(), + (trackKind) => this._muteOrUnmute(sessionId, true, false, trackKind), + (trackKind) => this._muteOrUnmute(sessionId, false, false, trackKind), + (options) => this._applyOptions(sessionId, options)); this._subscriptions.set(sessionId, subscription); // Resolve subscription if mediaStream is ready. if (this._subscriptions.get(sessionId).stream) { @@ -889,12 +897,20 @@ export class ConferencePeerConnectionChannel extends EventDispatcher { this._subscribePromises.delete(internalId); } } else if (this._publishPromises.has(internalId)) { - const publication = new Publication(sessionId, () => { - this._unpublish(internalId); - return Promise.resolve(); - }, () => this._getStats(), - (trackKind) => this._muteOrUnmute(sessionId, true, true, trackKind), - (trackKind) => this._muteOrUnmute(sessionId, false, true, trackKind)); + const transportSettings = + new TransportSettings(TransportType.WEBRTC, this._id); + transportSettings.transceivers = + this._publishTransceivers.get(internalId).transceivers; + const publication = new Publication( + sessionId, + transportSettings, + () => { + this._unpublish(internalId); + return Promise.resolve(); + }, + () => this._getStats(), + (trackKind) => this._muteOrUnmute(sessionId, true, true, trackKind), + (trackKind) => this._muteOrUnmute(sessionId, false, true, trackKind)); this._publications.set(sessionId, publication); this._publishPromises.get(internalId).resolve(publication); // Do not fire publication's ended event when associated stream is ended. diff --git a/src/sdk/conference/subscription.js b/src/sdk/conference/subscription.js index a8056dbe..16676f24 100644 --- a/src/sdk/conference/subscription.js +++ b/src/sdk/conference/subscription.js @@ -270,7 +270,8 @@ export class SubscriptionUpdateOptions { */ export class Subscription extends EventDispatcher { // eslint-disable-next-line require-jsdoc - constructor(id, stream, stop, getStats, mute, unmute, applyOptions) { + constructor( + id, stream, transport, stop, getStats, mute, unmute, applyOptions) { super(); if (!id) { throw new TypeError('ID cannot be null or undefined.'); @@ -298,6 +299,18 @@ export class Subscription extends EventDispatcher { writable: true, value: stream, }); + /** + * @member {Owt.Base.TransportSettings} transport + * @instance + * @readonly + * @desc Transport settings for the subscription. + * @memberof Owt.Base.Subscription + */ + Object.defineProperty(this, 'transport', { + configurable: false, + writable: false, + value: transport, + }); /** * @function stop * @instance diff --git a/src/sdk/p2p/peerconnection-channel.js b/src/sdk/p2p/peerconnection-channel.js index 3847ae71..76808c9b 100644 --- a/src/sdk/p2p/peerconnection-channel.js +++ b/src/sdk/p2p/peerconnection-channel.js @@ -305,8 +305,9 @@ class P2PPeerConnectionChannel extends EventDispatcher { (element) => element.mediaStream.id == mediaStreamId); const targetStream = this._publishingStreams[targetStreamIndex]; this._publishingStreams.splice(targetStreamIndex, 1); + // TODO: Set transceivers for Publication. const publication = new Publication( - id, () => { + id, undefined, () => { this._unpublish(targetStream).then(() => { publication.dispatchEvent(new OwtEvent('ended')); }, (err) => { diff --git a/test/unit/resources/scripts/base.js b/test/unit/resources/scripts/base.js index 6978aad0..faf6e921 100644 --- a/test/unit/resources/scripts/base.js +++ b/test/unit/resources/scripts/base.js @@ -8,9 +8,9 @@ import * as MediaStreamFactoryModule from '../../../../src/sdk/base/mediastream- import * as StreamModule from '../../../../src/sdk/base/stream.js'; import * as MediaFormatModule from '../../../../src/sdk/base/mediaformat.js' import * as SdpUtils from '../../../../src/sdk/base/sdputils.js' +import * as PublicationModule from '../../../../src/sdk/base/publication.js' const expect = chai.expect; -const screenSharingExtensionId = 'jniliohjdiikfjjdlpapmngebedgigjn'; chai.use(chaiAsPromised); describe('Unit tests for MediaStreamFactory', function() { function createMediaStream(constraints, audioTracksExpected, @@ -181,3 +181,15 @@ describe('Unit tests for SDP utils.', function() { done(); }); }); + +describe('Unit tests for Publication.', () => { + it('Get senders returns all transceivers\' sender.', () => { + it('Get transport returns correct TransportSettings.', () => { + const transportSettings = + new TransportSettings(TransportType.WEBRTC, 'randomId'); + const publication = + new PublicationModule.Publication('sessionId', transportSettings); + expect(publication.transport).to.equal(transportSettings); + }); + }); +}); \ No newline at end of file diff --git a/test/unit/resources/scripts/conference.js b/test/unit/resources/scripts/conference.js index b34aa596..1913e9be 100644 --- a/test/unit/resources/scripts/conference.js +++ b/test/unit/resources/scripts/conference.js @@ -8,9 +8,10 @@ import {ConferenceClient} from '../../../../src/sdk/conference/client.js'; import {ConferencePeerConnectionChannel} from '../../../../src/sdk/conference/channel.js'; import * as StreamModule from '../../../../src/sdk/base/stream.js'; import * as EventModule from '../../../../src/sdk/base/event.js' +import * as SubscriptionModule from '../../../../src/sdk/conference/subscription.js' +import { TransportSettings, TransportType } from '../../../../src/sdk/base/transport.js'; const expect = chai.expect; -const screenSharingExtensionId = 'jniliohjdiikfjjdlpapmngebedgigjn'; chai.use(chaiAsPromised); describe('Unit tests for ConferenceClient', function() { @@ -98,4 +99,14 @@ describe('Unit tests for ConferencePeerConnectionChannel.', () => { } }); }); +}); + +describe('Unit tests for Subscription.', () => { + it('Get transport returns correct TransportSettings.', () => { + const transportSettings = + new TransportSettings(TransportType.WEBRTC, 'randomId'); + const subscription = new SubscriptionModule.Subscription( + 'sessionId', undefined, transportSettings); + expect(subscription.transport).to.equal(transportSettings); + }); }); \ No newline at end of file