diff --git a/src/sdk/conference/streamutils.js b/src/sdk/conference/streamutils.js index 16c9f35e..5b9f8bf8 100644 --- a/src/sdk/conference/streamutils.js +++ b/src/sdk/conference/streamutils.js @@ -129,23 +129,28 @@ export function convertToSubscriptionCapabilities(mediaInfo) { } } videoCodecs.sort(); - const resolutions = Array.from( - track.optional.parameters.resolution, - (r) => new MediaFormatModule.Resolution(r.width, r.height)); - resolutions.sort(sortResolutions); - const bitrates = Array.from( - track.optional.parameters.bitrate, - (bitrate) => extractBitrateMultiplier(bitrate)); - bitrates.push(1.0); - bitrates.sort(sortNumbers); - const frameRates = JSON.parse( - JSON.stringify(track.optional.parameters.framerate)); - frameRates.sort(sortNumbers); - const keyFrameIntervals = JSON.parse( - JSON.stringify(track.optional.parameters.keyFrameInterval)); - keyFrameIntervals.sort(sortNumbers); - video = new SubscriptionModule.VideoSubscriptionCapabilities( - videoCodecs, resolutions, frameRates, bitrates, keyFrameIntervals); + if (!track.optional?.parameters) { + video = new SubscriptionModule.VideoSubscriptionCapabilities( + videoCodecs, [], [], [], []); + } else { + const resolutions = Array.from( + track.optional.parameters.resolution, + (r) => new MediaFormatModule.Resolution(r.width, r.height)); + resolutions.sort(sortResolutions); + const bitrates = Array.from( + track.optional.parameters.bitrate, + (bitrate) => extractBitrateMultiplier(bitrate)); + bitrates.push(1.0); + bitrates.sort(sortNumbers); + const frameRates = + JSON.parse(JSON.stringify(track.optional.parameters.framerate)); + frameRates.sort(sortNumbers); + const keyFrameIntervals = JSON.parse( + JSON.stringify(track.optional.parameters.keyFrameInterval)); + keyFrameIntervals.sort(sortNumbers); + video = new SubscriptionModule.VideoSubscriptionCapabilities( + videoCodecs, resolutions, frameRates, bitrates, keyFrameIntervals); + } } } return new SubscriptionModule.SubscriptionCapabilities(audio, video); diff --git a/test/unit/resources/scripts/conference.js b/test/unit/resources/scripts/conference.js index 1913e9be..00a46b4e 100644 --- a/test/unit/resources/scripts/conference.js +++ b/test/unit/resources/scripts/conference.js @@ -6,25 +6,24 @@ 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 StreamUtils from '../../../../src/sdk/conference/streamutils.js'; import * as SubscriptionModule from '../../../../src/sdk/conference/subscription.js' -import { TransportSettings, TransportType } from '../../../../src/sdk/base/transport.js'; +import {TransportSettings, TransportType} from '../../../../src/sdk/base/transport.js'; const expect = chai.expect; chai.use(chaiAsPromised); describe('Unit tests for ConferenceClient', function() { describe('Create a ConferenceClient.', function() { - it( - 'Create a ConferenceClient with or without configuration should success.', - done => { - let confclient = new ConferenceClient({}); - expect(confclient).to.be.an.instanceof(EventModule.EventDispatcher); - confclient = new ConferenceClient({}); - expect(confclient).to.be.an.instanceof(EventModule.EventDispatcher); - done(); - }); + it('Create a ConferenceClient with or without configuration should success.', + done => { + let confclient = new ConferenceClient({}); + expect(confclient).to.be.an.instanceof(EventModule.EventDispatcher); + confclient = new ConferenceClient({}); + expect(confclient).to.be.an.instanceof(EventModule.EventDispatcher); + done(); + }); it('Event should be fired on correct target.', done => { let conf1 = new ConferenceClient({}); @@ -109,4 +108,162 @@ describe('Unit tests for Subscription.', () => { 'sessionId', undefined, transportSettings); expect(subscription.transport).to.equal(transportSettings); }); +}); + +describe('Unit tests for StreamUtils.', () => { + it('Convert server messages to subscription capabilities.', () => { + const messages = [ + [ + { + 'tracks': [ + { + 'id': '35951ba82cf14a8cb0348ab64517fb80-2', + 'type': 'audio', + 'source': 'mic', + 'format': {'channelNum': 2, 'sampleRate': 48000, 'codec': 'opus'}, + 'optional': { + 'format': [ + {'sampleRate': 16000, 'codec': 'isac'}, + {'sampleRate': 32000, 'codec': 'isac'}, + {'channelNum': 1, 'sampleRate': 16000, 'codec': 'g722'}, + {'codec': 'pcma'}, {'codec': 'pcmu'}, + {'channelNum': 2, 'sampleRate': 48000, 'codec': 'aac'}, + {'codec': 'ac3'}, {'codec': 'nellymoser'}, {'codec': 'ilbc'} + ] + }, + 'status': 'active', + 'mid': '2' + }, + { + 'id': '35951ba82cf14a8cb0348ab64517fb80-3', + 'type': 'video', + 'source': 'camera', + 'format': {'codec': 'vp8'}, + 'optional': { + 'format': [ + {'profile': 'CB', 'codec': 'h264'}, + {'profile': 'B', 'codec': 'h264'}, {'codec': 'vp9'} + ], + 'parameters': { + 'resolution': [ + {'width': 480, 'height': 360}, + {'width': 426, 'height': 320}, + {'width': 320, 'height': 240}, + {'width': 212, 'height': 160}, + {'width': 160, 'height': 120}, {'width': 352, 'height': 288} + ], + 'bitrate': ['x0.8', 'x0.6', 'x0.4', 'x0.2'], + 'framerate': [6, 12, 15, 24], + 'keyFrameInterval': [100, 30, 5, 2, 1] + } + }, + 'status': 'active', + 'mid': '3' + } + ] + }, + { + 'audio': { + 'codecs': [ + {'name': 'isac', 'clockRate': 16000}, + {'name': 'isac', 'clockRate': 32000}, + {'name': 'g722', 'channelCount': 1, 'clockRate': 16000}, + {'name': 'pcma'}, {'name': 'pcmu'}, + {'name': 'aac', 'channelCount': 2, 'clockRate': 48000}, + {'name': 'ac3'}, {'name': 'nellymoser'}, {'name': 'ilbc'} + ] + }, + 'video': { + 'codecs': [ + {'name': 'h264', 'profile': 'CB'}, + {'name': 'h264', 'profile': 'B'}, {'name': 'vp9'} + ], + 'resolutions': [ + {'width': 160, 'height': 120}, {'width': 212, 'height': 160}, + {'width': 320, 'height': 240}, {'width': 352, 'height': 288}, + {'width': 426, 'height': 320}, {'width': 480, 'height': 360} + ], + 'frameRates': [6, 12, 15, 24], + 'bitrateMultipliers': [0.2, 0.4, 0.6, 0.8, 1], + 'keyFrameIntervals': [1, 2, 5, 30, 100] + } + } + ], + [ + // Audio only. + { + 'tracks': [{ + 'id': '35951ba82cf14a8cb0348ab64517fb80-2', + 'type': 'audio', + 'source': 'mic', + 'format': {'channelNum': 2, 'sampleRate': 48000, 'codec': 'opus'}, + 'optional': { + 'format': [ + {'sampleRate': 16000, 'codec': 'isac'}, + {'sampleRate': 32000, 'codec': 'isac'}, + {'channelNum': 1, 'sampleRate': 16000, 'codec': 'g722'}, + {'codec': 'pcma'}, {'codec': 'pcmu'}, + {'channelNum': 2, 'sampleRate': 48000, 'codec': 'aac'}, + {'codec': 'ac3'}, {'codec': 'nellymoser'}, {'codec': 'ilbc'} + ] + }, + 'status': 'active', + 'mid': '2' + }] + }, + { + 'audio': { + 'codecs': [ + {'name': 'isac', 'clockRate': 16000}, + {'name': 'isac', 'clockRate': 32000}, + {'name': 'g722', 'channelCount': 1, 'clockRate': 16000}, + {'name': 'pcma'}, {'name': 'pcmu'}, + {'name': 'aac', 'channelCount': 2, 'clockRate': 48000}, + {'name': 'ac3'}, {'name': 'nellymoser'}, {'name': 'ilbc'} + ] + } + } + ], + [ + // Transcoding is disabled. + { + 'tracks': [ + { + 'type': 'audio', + 'format': {'codec': 'opus', 'sampleRate': 48000, 'channelNum': 2}, + 'optional': {}, + 'status': 'active' + }, + { + 'type': 'video', + 'format': {'codec': 'vp8'}, + 'parameters': { + 'resolution': {'width': 640, 'height': 480}, + 'framerate': 24, + 'keyFrameInterval': 100 + }, + 'optional': {}, + 'status': 'active' + } + ] + }, + { + 'audio': {'codecs': []}, + 'video': { + 'codecs': [], + 'resolutions': [], + 'frameRates': [], + 'bitrateMultipliers': [], + 'keyFrameIntervals': [] + } + } + ] + ]; + for (const [message, capabilities] of messages) { + // Stringify to avoid type comparison. + expect(JSON.stringify( + StreamUtils.convertToSubscriptionCapabilities(message))) + .to.equal(JSON.stringify(capabilities)); + } + }); }); \ No newline at end of file