mediasoup-client
Advanced tools
Comparing version 0.3.1 to 0.4.0
@@ -120,3 +120,3 @@ module.exports = | ||
'no-multi-str': 2, | ||
'no-multiple-empty-lines': 2, | ||
'no-multiple-empty-lines': [ 2, { max: 1, maxEOF: 0, maxBOF: 0 } ], | ||
'no-native-reassign': 2, | ||
@@ -123,0 +123,0 @@ 'no-negated-in-lhs': 2, |
import Logger from './Logger'; | ||
import EnhancedEventEmitter from './EnhancedEventEmitter'; | ||
import { InvalidStateError } from './errors'; | ||
@@ -16,9 +17,7 @@ const logger = new Logger('Consumer'); | ||
* | ||
* @emits {[appData]: Any} @pause | ||
* @emits {[appData]: Any} @resume | ||
* @emits {originator: String} @close | ||
* @emits @close | ||
*/ | ||
constructor(id, kind, rtpParameters, peer, appData) | ||
{ | ||
super(); | ||
super(logger); | ||
@@ -53,5 +52,5 @@ // Id. | ||
// Whether this Consumer is being handled by a Transport. | ||
// @type {Boolean} | ||
this._handled = false; | ||
// Associated Transport. | ||
// @type {Transport} | ||
this._transport = null; | ||
@@ -72,12 +71,2 @@ // Remote track. | ||
/** | ||
* Class name. | ||
* | ||
* @return {String} | ||
*/ | ||
get klass() | ||
{ | ||
return 'Consumer'; | ||
} | ||
/** | ||
* Consumer id. | ||
@@ -153,9 +142,9 @@ * | ||
/** | ||
* Whether this is being handled by a Transport. | ||
* Associated Transport. | ||
* | ||
* @return {Boolean} | ||
* @return {Transport} | ||
*/ | ||
get handled() | ||
get transport() | ||
{ | ||
return Boolean(this._handled); | ||
return this._transport; | ||
} | ||
@@ -210,3 +199,3 @@ | ||
{ | ||
return (!this._closed && this.handled === true && !this.paused); | ||
return (!this._closed && this._transport === true && !this.paused); | ||
} | ||
@@ -229,3 +218,3 @@ | ||
this.emit('@close', 'local'); | ||
this.emit('@close'); | ||
this.safeEmit('close', 'local'); | ||
@@ -253,6 +242,8 @@ | ||
this.emit('@close', 'remote'); | ||
this.safeEmit('close', 'remote', appData); | ||
if (this._transport) | ||
this._transport.removeConsumer(this, appData); | ||
this._destroy(); | ||
this.safeEmit('close', 'remote', appData); | ||
} | ||
@@ -262,3 +253,3 @@ | ||
{ | ||
this._handled = false; | ||
this._transport = null; | ||
@@ -272,2 +263,52 @@ try { this._track.stop(); } | ||
/** | ||
* Receives RTP. | ||
* | ||
* @param {transport} Transport instance. | ||
* | ||
* @return {Promise} Resolves with a remote MediaStreamTrack. | ||
*/ | ||
receive(transport) | ||
{ | ||
logger.debug('receive() [transport:%o]', transport); | ||
if (this._closed) | ||
return Promise.reject(new InvalidStateError('Consumer closed')); | ||
else if (!this._supported) | ||
return Promise.reject(new Error('unsupported codecs')); | ||
else if (this._transport) | ||
return Promise.reject(new Error('already handled by a Transport')); | ||
else if (typeof transport !== 'object') | ||
return Promise.reject(new TypeError('invalid Transport')); | ||
this._transport = transport; | ||
return transport.addConsumer(this) | ||
.then((track) => | ||
{ | ||
transport.once('@close', () => | ||
{ | ||
if (this._closed || this._transport !== transport) | ||
return; | ||
this._transport = null; | ||
try { this._track.stop(); } | ||
catch (error) {} | ||
this._track = null; | ||
this.safeEmit('unhandled'); | ||
}); | ||
this.safeEmit('handled'); | ||
return track; | ||
}) | ||
.catch((error) => | ||
{ | ||
this._transport = null; | ||
throw error; | ||
}); | ||
} | ||
/** | ||
* Pauses receiving media. | ||
@@ -294,2 +335,5 @@ * | ||
if (this._transport) | ||
this._transport.pauseConsumer(this, appData); | ||
this._locallyPaused = true; | ||
@@ -300,5 +344,2 @@ | ||
if (this._handled) | ||
this.emit('@pause', appData); | ||
this.safeEmit('pause', 'local', appData); | ||
@@ -355,2 +396,5 @@ | ||
if (this._transport) | ||
this._transport.resumeConsumer(this, appData); | ||
this._locallyPaused = false; | ||
@@ -361,5 +405,2 @@ | ||
if (this._handled) | ||
this.emit('@resume', appData); | ||
this.safeEmit('resume', 'local', appData); | ||
@@ -407,33 +448,12 @@ | ||
/** | ||
* Set this Consumer as handled or unhandled by a Transport. | ||
* Set remote MediaStreamTrack. | ||
* | ||
* @private | ||
* | ||
* @param {Boolean|String} flag - If 'tmp' it's considered temporal. | ||
* @param {track} MediaStreamTrack | ||
* @param {MediaStreamTrack} track | ||
*/ | ||
setHandled(flag, track) | ||
setTrack(track) | ||
{ | ||
if (this._closed) | ||
return; | ||
const previous = this._handled; | ||
this._handled = flag; | ||
this._track = track || null; | ||
if (track && this.paused) | ||
this._track.enabled = false; | ||
if (flag === false || flag === 'tmp') | ||
{ | ||
try { this._track.stop(); } | ||
catch (error) {} | ||
this._track = null; | ||
} | ||
if (previous === true && flag === false) | ||
this.safeEmit('unhandled'); | ||
this._track = track; | ||
} | ||
} |
import { EventEmitter } from 'events'; | ||
import Logger from './Logger'; | ||
const logger = new Logger('EnhancedEventEmitter'); | ||
export default class EnhancedEventEmitter extends EventEmitter | ||
{ | ||
constructor() | ||
constructor(logger) | ||
{ | ||
super(); | ||
this.setMaxListeners(Infinity); | ||
this._logger = logger || new Logger('EnhancedEventEmitter'); | ||
} | ||
@@ -22,3 +22,4 @@ | ||
{ | ||
logger.error('event listener threw an error [event:%s]: %o', | ||
this._logger.error( | ||
'safeEmit() | event listener threw an error [event:%s]:%o', | ||
event, error); | ||
@@ -28,3 +29,3 @@ } | ||
safeEmitAsPromise(...args) | ||
safeEmitAsPromise(event, ...args) | ||
{ | ||
@@ -40,8 +41,12 @@ return new Promise((resolve, reject) => | ||
{ | ||
this._logger.error( | ||
'safeEmitAsPromise() | errback called [event:%s]:%o', | ||
event, error); | ||
reject(error); | ||
}; | ||
this.safeEmit(...args, callback, errback); | ||
this.safeEmit(event, ...args, callback, errback); | ||
}); | ||
} | ||
} |
@@ -15,3 +15,3 @@ import sdpTransform from 'sdp-transform'; | ||
{ | ||
super(); | ||
super(logger); | ||
@@ -18,0 +18,0 @@ // RTCPeerConnection instance. |
@@ -28,2 +28,4 @@ /* global RTCIceGatherer, RTCIceTransport, RTCDtlsTransport, RTCRtpReceiver, RTCRtpSender */ | ||
{ | ||
super(logger); | ||
logger.debug( | ||
@@ -33,4 +35,2 @@ 'constructor() [direction:%s, extendedRtpCapabilities:%o]', | ||
super(); | ||
// Generic sending RTP parameters for audio and video. | ||
@@ -37,0 +37,0 @@ // @type {Object} |
@@ -15,3 +15,3 @@ import sdpTransform from 'sdp-transform'; | ||
{ | ||
super(); | ||
super(logger); | ||
@@ -178,4 +178,6 @@ // RTCPeerConnection instance. | ||
// at least a fake-active sending track. | ||
if (this._stream.getTracks().length === 0) | ||
return; | ||
// NOTE: This does not fix the problem. | ||
// ISSUE: https://github.com/versatica/mediasoup-client/issues/2 | ||
// if (this._stream.getTracks().length === 0) | ||
// return; | ||
@@ -188,4 +190,6 @@ return Promise.resolve() | ||
{ | ||
if (this._pc.signalingState === 'stable') | ||
return; | ||
// TODO: This may be removed. | ||
// ISSUE: https://github.com/versatica/mediasoup-client/issues/2 | ||
// if (this._pc.signalingState === 'stable') | ||
// return; | ||
@@ -192,0 +196,0 @@ const localSdpObj = sdpTransform.parse(this._pc.localDescription.sdp); |
@@ -34,2 +34,9 @@ /* global RTCRtpReceiver */ | ||
codec.parameters.apt = Number(codec.parameters.apt); | ||
// Delete emty parameter String in rtcpFeedback. | ||
for (const feedback of codec.rtcpFeedback || []) | ||
{ | ||
if (!feedback.parameter) | ||
delete feedback.parameter; | ||
} | ||
} | ||
@@ -36,0 +43,0 @@ |
@@ -15,3 +15,3 @@ import sdpTransform from 'sdp-transform'; | ||
{ | ||
super(); | ||
super(logger); | ||
@@ -18,0 +18,0 @@ // RTCPeerConnection instance. |
@@ -95,5 +95,8 @@ import sdpTransform from 'sdp-transform'; | ||
type : fb.type, | ||
parameter : fb.subtype || '' | ||
parameter : fb.subtype | ||
}; | ||
if (!feedback.parameter) | ||
delete feedback.parameter; | ||
codec.rtcpFeedback.push(feedback); | ||
@@ -100,0 +103,0 @@ } |
@@ -68,3 +68,2 @@ import sdpTransform from 'sdp-transform'; | ||
const localDtlsParameters = this._transportLocalParameters.dtlsParameters; | ||
const remoteIceParameters = this._transportRemoteParameters.iceParameters; | ||
@@ -107,6 +106,9 @@ const remoteIceCandidates = this._transportRemoteParameters.iceCandidates; | ||
// NOTE: We take the latest fingerprint. | ||
const numFingerprints = remoteDtlsParameters.fingerprints.length; | ||
sdpObj.fingerprint = | ||
{ | ||
type : remoteDtlsParameters.fingerprints[0].algorithm, | ||
hash : remoteDtlsParameters.fingerprints[0].value | ||
type : remoteDtlsParameters.fingerprints[numFingerprints - 1].algorithm, | ||
hash : remoteDtlsParameters.fingerprints[numFingerprints - 1].value | ||
}; | ||
@@ -156,3 +158,3 @@ | ||
switch (localDtlsParameters.role) | ||
switch (remoteDtlsParameters.role) | ||
{ | ||
@@ -225,3 +227,3 @@ case 'client': | ||
type : fb.type, | ||
subtype : fb.parameter | ||
subtype : fb.parameter || '' | ||
}); | ||
@@ -318,6 +320,9 @@ } | ||
// NOTE: We take the latest fingerprint. | ||
const numFingerprints = remoteDtlsParameters.fingerprints.length; | ||
sdpObj.fingerprint = | ||
{ | ||
type : remoteDtlsParameters.fingerprints[0].algorithm, | ||
hash : remoteDtlsParameters.fingerprints[0].value | ||
type : remoteDtlsParameters.fingerprints[numFingerprints - 1].algorithm, | ||
hash : remoteDtlsParameters.fingerprints[numFingerprints - 1].value | ||
}; | ||
@@ -419,3 +424,3 @@ | ||
type : fb.type, | ||
subtype : fb.parameter | ||
subtype : fb.parameter || '' | ||
}); | ||
@@ -422,0 +427,0 @@ } |
@@ -68,3 +68,2 @@ import sdpTransform from 'sdp-transform'; | ||
const localDtlsParameters = this._transportLocalParameters.dtlsParameters; | ||
const remoteIceParameters = this._transportRemoteParameters.iceParameters; | ||
@@ -113,6 +112,9 @@ const remoteIceCandidates = this._transportRemoteParameters.iceCandidates; | ||
// NOTE: We take the latest fingerprint. | ||
const numFingerprints = remoteDtlsParameters.fingerprints.length; | ||
sdpObj.fingerprint = | ||
{ | ||
type : remoteDtlsParameters.fingerprints[0].algorithm, | ||
hash : remoteDtlsParameters.fingerprints[0].value | ||
type : remoteDtlsParameters.fingerprints[numFingerprints - 1].algorithm, | ||
hash : remoteDtlsParameters.fingerprints[numFingerprints - 1].value | ||
}; | ||
@@ -163,3 +165,3 @@ | ||
switch (localDtlsParameters.role) | ||
switch (remoteDtlsParameters.role) | ||
{ | ||
@@ -232,3 +234,3 @@ case 'client': | ||
type : fb.type, | ||
subtype : fb.parameter | ||
subtype : fb.parameter || '' | ||
}); | ||
@@ -335,6 +337,9 @@ } | ||
// NOTE: We take the latest fingerprint. | ||
const numFingerprints = remoteDtlsParameters.fingerprints.length; | ||
sdpObj.fingerprint = | ||
{ | ||
type : remoteDtlsParameters.fingerprints[0].algorithm, | ||
hash : remoteDtlsParameters.fingerprints[0].value | ||
type : remoteDtlsParameters.fingerprints[numFingerprints - 1].algorithm, | ||
hash : remoteDtlsParameters.fingerprints[numFingerprints - 1].value | ||
}; | ||
@@ -439,3 +444,3 @@ | ||
type : fb.type, | ||
subtype : fb.parameter | ||
subtype : fb.parameter || '' | ||
}); | ||
@@ -442,0 +447,0 @@ } |
@@ -14,7 +14,7 @@ import Logger from './Logger'; | ||
* | ||
* @emits {originator: String} @close | ||
* @emits @close | ||
*/ | ||
constructor(name, appData) | ||
{ | ||
super(); | ||
super(logger); | ||
@@ -93,3 +93,3 @@ // Name. | ||
this.emit('@close', 'local'); | ||
this.emit('@close'); | ||
this.safeEmit('close', 'local'); | ||
@@ -121,3 +121,3 @@ | ||
this.emit('@close', 'remote'); | ||
this.emit('@close'); | ||
this.safeEmit('close', 'remote', appData); | ||
@@ -124,0 +124,0 @@ |
@@ -18,4 +18,2 @@ import Logger from './Logger'; | ||
* | ||
* @emits {[appData]: Any} @pause | ||
* @emits {[appData]: Any} @resume | ||
* @emits {originator: String, [appData]: Any} @close | ||
@@ -26,3 +24,3 @@ * | ||
{ | ||
super(); | ||
super(logger); | ||
@@ -49,5 +47,5 @@ // Id. | ||
// Whether this Producer is being handled by a Transport. | ||
// @type {Boolean} | ||
this._handled = false; | ||
// Associated Transport. | ||
// @type {Transport} | ||
this._transport = null; | ||
@@ -68,12 +66,2 @@ // RTP parameters. | ||
/** | ||
* Class name. | ||
* | ||
* @return {String} | ||
*/ | ||
get klass() | ||
{ | ||
return 'Producer'; | ||
} | ||
/** | ||
* Producer id. | ||
@@ -139,9 +127,9 @@ * | ||
/** | ||
* Whether this is being handled by a Transport. | ||
* Associated Transport. | ||
* | ||
* @return {Boolean} | ||
* @return {Transport} | ||
*/ | ||
get handled() | ||
get transport() | ||
{ | ||
return Boolean(this._handled); | ||
return this._transport; | ||
} | ||
@@ -196,3 +184,3 @@ | ||
{ | ||
return (!this._closed && this.handled === true && !this.paused); | ||
return (!this._closed && this._transport === true && !this.paused); | ||
} | ||
@@ -214,6 +202,12 @@ | ||
if (this._transport) | ||
{ | ||
this._transport.removeProducer( | ||
this, appData, { locallyClosed: true }); | ||
} | ||
this._destroy(); | ||
this.emit('@close', 'local', appData); | ||
this.safeEmit('close', 'local', appData); | ||
this._destroy(); | ||
} | ||
@@ -238,6 +232,12 @@ | ||
if (this._transport) | ||
{ | ||
this._transport.removeProducer( | ||
this, appData, { locallyClosed: false }); | ||
} | ||
this._destroy(); | ||
this.emit('@close', 'remote', appData); | ||
this.safeEmit('close', 'remote', appData); | ||
this._destroy(); | ||
} | ||
@@ -247,4 +247,3 @@ | ||
{ | ||
this._closed = true; | ||
this._handled = false; | ||
this._transport = false; | ||
this._rtpParameters = null; | ||
@@ -257,2 +256,46 @@ | ||
/** | ||
* Sends RTP. | ||
* | ||
* @param {transport} Transport instance. | ||
* | ||
* @return {Promise} | ||
*/ | ||
send(transport) | ||
{ | ||
logger.debug('send() [transport:%o]', transport); | ||
if (this._closed) | ||
return Promise.reject(new InvalidStateError('Producer closed')); | ||
else if (this._transport) | ||
return Promise.reject(new Error('already handled by a Transport')); | ||
else if (typeof transport !== 'object') | ||
return Promise.reject(new TypeError('invalid Transport')); | ||
this._transport = transport; | ||
return transport.addProducer(this) | ||
.then(() => | ||
{ | ||
transport.once('@close', () => | ||
{ | ||
if (this._closed || this._transport !== transport) | ||
return; | ||
this._transport = null; | ||
this._rtpParameters = null; | ||
this.safeEmit('unhandled'); | ||
}); | ||
this.safeEmit('handled'); | ||
}) | ||
.catch((error) => | ||
{ | ||
this._transport = null; | ||
throw error; | ||
}); | ||
} | ||
/** | ||
* Pauses sending media. | ||
@@ -279,8 +322,8 @@ * | ||
if (this._transport) | ||
this._transport.pauseProducer(this, appData); | ||
this._locallyPaused = true; | ||
this._track.enabled = false; | ||
if (this._handled) | ||
this.emit('@pause', appData); | ||
this.safeEmit('pause', 'local', appData); | ||
@@ -335,2 +378,5 @@ | ||
if (this._transport) | ||
this._transport.resumeProducer(this, appData); | ||
this._locallyPaused = false; | ||
@@ -341,5 +387,2 @@ | ||
if (this._handled) | ||
this.emit('@resume', appData); | ||
this.safeEmit('resume', 'local', appData); | ||
@@ -397,4 +440,4 @@ | ||
// the new track. | ||
if (this._handled) | ||
return this.safeEmitAsPromise('@replacetrack', track); | ||
if (this._transport) | ||
return this._transport.replaceProducerTrack(this, track); | ||
}) | ||
@@ -407,5 +450,11 @@ .then(() => | ||
// If this Producer was locally paused/resumed and the state of the new | ||
// track does not match, fix it. | ||
if (this.paused && track.enabled) | ||
track.enabled = false; | ||
else if (!this.paused && !track.enabled) | ||
track.enabled = true; | ||
// Set the new track. | ||
this._track = track; | ||
this._locallyPaused = !track.enabled; | ||
@@ -418,37 +467,12 @@ // Return the new track. | ||
/** | ||
* Set this Producer as handled or unhandled by a Transport. | ||
* Set/update RTP parameters. | ||
* | ||
* @private | ||
* | ||
* @param {Boolean|String} flag - If 'tmp' it's considered temporal. | ||
* @param {RTCRtpParameters} rtpParameters | ||
*/ | ||
setHandled(flag, rtpParameters) | ||
setRtpParameters(rtpParameters) | ||
{ | ||
if (this._closed) | ||
return; | ||
const previous = this._handled; | ||
this._handled = flag; | ||
this._rtpParameters = rtpParameters; | ||
if (flag === false || flag === 'tmp') | ||
this._rtpParameters = null; | ||
if (previous === true && flag === false) | ||
this.safeEmit('unhandled'); | ||
} | ||
/** | ||
* Update RTP parameters. | ||
* | ||
* @private | ||
* | ||
* @param {RTCRtpParameters} rtpParameters | ||
*/ | ||
updateRtpParameters(rtpParameters) | ||
{ | ||
this._rtpParameters = rtpParameters; | ||
} | ||
} |
278
lib/Room.js
@@ -48,6 +48,6 @@ import Logger from './Logger'; | ||
{ | ||
super(logger); | ||
logger.debug('constructor() [options:%o]', options); | ||
super(); | ||
if (!Device.isSupported()) | ||
@@ -518,189 +518,189 @@ throw new Error('current browser/device not supported'); | ||
{ | ||
try | ||
{ | ||
if (this.closed) | ||
throw new InvalidStateError('Room closed'); | ||
else if (typeof notification !== 'object') | ||
throw new TypeError('wrong notification Object'); | ||
else if (notification.notification !== true) | ||
throw new TypeError('not a notification'); | ||
else if (typeof notification.method !== 'string') | ||
throw new TypeError('wrong/missing notification method'); | ||
if (this.closed) | ||
return Promise.reject(new InvalidStateError('Room closed')); | ||
else if (typeof notification !== 'object') | ||
return Promise.reject(new TypeError('wrong notification Object')); | ||
else if (notification.notification !== true) | ||
return Promise.reject(new TypeError('not a notification')); | ||
else if (typeof notification.method !== 'string') | ||
return Promise.reject(new TypeError('wrong/missing notification method')); | ||
const method = notification.method; | ||
const { method } = notification; | ||
logger.debug( | ||
'receiveNotification() [method:%s, notification:%o]', | ||
method, notification); | ||
logger.debug( | ||
'receiveNotification() [method:%s, notification:%o]', | ||
method, notification); | ||
switch (method) | ||
return Promise.resolve() | ||
.then(() => | ||
{ | ||
case 'closed': | ||
switch (method) | ||
{ | ||
const { appData } = notification; | ||
case 'closed': | ||
{ | ||
const { appData } = notification; | ||
this.remoteClose(appData); | ||
this.remoteClose(appData); | ||
break; | ||
} | ||
break; | ||
} | ||
case 'transportClosed': | ||
{ | ||
const { id, appData } = notification; | ||
const transport = this._transports.get(id); | ||
case 'transportClosed': | ||
{ | ||
const { id, appData } = notification; | ||
const transport = this._transports.get(id); | ||
if (!transport) | ||
throw new Error(`Transport does not exist [id:"${id}"]`); | ||
if (!transport) | ||
throw new Error(`Transport does not exist [id:"${id}"]`); | ||
transport.remoteClose(appData); | ||
transport.remoteClose(appData); | ||
break; | ||
} | ||
break; | ||
} | ||
case 'newPeer': | ||
{ | ||
const { name } = notification; | ||
case 'newPeer': | ||
{ | ||
const { name } = notification; | ||
if (this._peers.has(name)) | ||
throw new Error(`Peer already exists [name:"${name}"]`); | ||
if (this._peers.has(name)) | ||
throw new Error(`Peer already exists [name:"${name}"]`); | ||
const peerData = notification; | ||
const peerData = notification; | ||
this._handlePeerData(peerData); | ||
this._handlePeerData(peerData); | ||
break; | ||
} | ||
break; | ||
} | ||
case 'peerClosed': | ||
{ | ||
const peerName = notification.name; | ||
const { appData } = notification; | ||
const peer = this._peers.get(peerName); | ||
case 'peerClosed': | ||
{ | ||
const peerName = notification.name; | ||
const { appData } = notification; | ||
const peer = this._peers.get(peerName); | ||
if (!peer) | ||
throw new Error(`no Peer found [name:"${peerName}"]`); | ||
if (!peer) | ||
throw new Error(`no Peer found [name:"${peerName}"]`); | ||
peer.remoteClose(appData); | ||
peer.remoteClose(appData); | ||
break; | ||
} | ||
break; | ||
} | ||
case 'producerClosed': | ||
{ | ||
const { id, appData } = notification; | ||
const producer = this._producers.get(id); | ||
case 'producerClosed': | ||
{ | ||
const { id, appData } = notification; | ||
const producer = this._producers.get(id); | ||
if (!producer) | ||
throw new Error(`Producer not found [id:${id}]`); | ||
if (!producer) | ||
throw new Error(`Producer not found [id:${id}]`); | ||
producer.remoteClose(appData); | ||
producer.remoteClose(appData); | ||
break; | ||
} | ||
break; | ||
} | ||
case 'producerPaused': | ||
{ | ||
const { id, appData } = notification; | ||
const producer = this._producers.get(id); | ||
case 'producerPaused': | ||
{ | ||
const { id, appData } = notification; | ||
const producer = this._producers.get(id); | ||
if (!producer) | ||
throw new Error(`Producer not found [id:${id}]`); | ||
if (!producer) | ||
throw new Error(`Producer not found [id:${id}]`); | ||
producer.remotePause(appData); | ||
producer.remotePause(appData); | ||
break; | ||
} | ||
break; | ||
} | ||
case 'producerResumed': | ||
{ | ||
const { id, appData } = notification; | ||
const producer = this._producers.get(id); | ||
case 'producerResumed': | ||
{ | ||
const { id, appData } = notification; | ||
const producer = this._producers.get(id); | ||
if (!producer) | ||
throw new Error(`Producer not found [id:${id}]`); | ||
if (!producer) | ||
throw new Error(`Producer not found [id:${id}]`); | ||
producer.remoteResume(appData); | ||
producer.remoteResume(appData); | ||
break; | ||
} | ||
break; | ||
} | ||
case 'newConsumer': | ||
{ | ||
const { peerName } = notification; | ||
const peer = this._peers.get(peerName); | ||
case 'newConsumer': | ||
{ | ||
const { peerName } = notification; | ||
const peer = this._peers.get(peerName); | ||
if (!peer) | ||
throw new Error(`no Peer found [name:"${peerName}"]`); | ||
if (!peer) | ||
throw new Error(`no Peer found [name:"${peerName}"]`); | ||
const consumerData = notification; | ||
const consumerData = notification; | ||
this._handleConsumerData(consumerData, peer); | ||
this._handleConsumerData(consumerData, peer); | ||
break; | ||
} | ||
break; | ||
} | ||
case 'consumerClosed': | ||
{ | ||
const { id, peerName, appData } = notification; | ||
const peer = this._peers.get(peerName); | ||
case 'consumerClosed': | ||
{ | ||
const { id, peerName, appData } = notification; | ||
const peer = this._peers.get(peerName); | ||
if (!peer) | ||
throw new Error(`no Peer found [name:"${peerName}"]`); | ||
if (!peer) | ||
throw new Error(`no Peer found [name:"${peerName}"]`); | ||
const consumer = peer.getConsumerById(id); | ||
const consumer = peer.getConsumerById(id); | ||
if (!consumer) | ||
throw new Error(`Consumer not found [id:${id}]`); | ||
if (!consumer) | ||
throw new Error(`Consumer not found [id:${id}]`); | ||
consumer.remoteClose(appData); | ||
consumer.remoteClose(appData); | ||
break; | ||
} | ||
break; | ||
} | ||
case 'consumerPaused': | ||
{ | ||
const { id, peerName, appData } = notification; | ||
const peer = this._peers.get(peerName); | ||
case 'consumerPaused': | ||
{ | ||
const { id, peerName, appData } = notification; | ||
const peer = this._peers.get(peerName); | ||
if (!peer) | ||
throw new Error(`no Peer found [name:"${peerName}"]`); | ||
if (!peer) | ||
throw new Error(`no Peer found [name:"${peerName}"]`); | ||
const consumer = peer.getConsumerById(id); | ||
const consumer = peer.getConsumerById(id); | ||
if (!consumer) | ||
throw new Error(`Consumer not found [id:${id}]`); | ||
if (!consumer) | ||
throw new Error(`Consumer not found [id:${id}]`); | ||
consumer.remotePause(appData); | ||
consumer.remotePause(appData); | ||
break; | ||
} | ||
break; | ||
} | ||
case 'consumerResumed': | ||
{ | ||
const { id, peerName, appData } = notification; | ||
const peer = this._peers.get(peerName); | ||
case 'consumerResumed': | ||
{ | ||
const { id, peerName, appData } = notification; | ||
const peer = this._peers.get(peerName); | ||
if (!peer) | ||
throw new Error(`no Peer found [name:"${peerName}"]`); | ||
if (!peer) | ||
throw new Error(`no Peer found [name:"${peerName}"]`); | ||
const consumer = peer.getConsumerById(id); | ||
const consumer = peer.getConsumerById(id); | ||
if (!consumer) | ||
throw new Error(`Consumer not found [id:${id}]`); | ||
if (!consumer) | ||
throw new Error(`Consumer not found [id:${id}]`); | ||
consumer.remoteResume(appData); | ||
consumer.remoteResume(appData); | ||
break; | ||
break; | ||
} | ||
default: | ||
throw new Error(`unknown notification method "${method}"`); | ||
} | ||
default: | ||
throw new Error(`unknown notification method "${method}"`); | ||
} | ||
} | ||
catch (error) | ||
{ | ||
logger.error( | ||
'receiveNotification() failed [notification:%o]: %s', | ||
notification, error.toString()); | ||
} | ||
}) | ||
.catch((error) => | ||
{ | ||
logger.error( | ||
'receiveNotification() failed [notification:%o]: %s', notification, error); | ||
}); | ||
} | ||
@@ -762,3 +762,3 @@ | ||
const errback = (message) => | ||
const errback = (error) => | ||
{ | ||
@@ -781,9 +781,9 @@ if (done) | ||
// Make sure message is a String. | ||
message = String(message); | ||
// Make sure message is an Error. | ||
if (!(error instanceof Error)) | ||
error = new Error(String(error)); | ||
logger.error( | ||
'request failed [method:%s]: %s', method, message); | ||
logger.error('request failed [method:%s]:%o', method, error); | ||
reject(new Error(message)); | ||
reject(error); | ||
}; | ||
@@ -790,0 +790,0 @@ |
import Logger from './Logger'; | ||
import EnhancedEventEmitter from './EnhancedEventEmitter'; | ||
import { InvalidStateError } from './errors'; | ||
import * as utils from './utils'; | ||
@@ -19,11 +20,11 @@ import Device from './Device'; | ||
* @emits {method: String, [data]: Object} @notify | ||
* @emits {originator: String} @close | ||
* @emits @close | ||
*/ | ||
constructor(direction, extendedRtpCapabilities, settings, appData) | ||
{ | ||
super(logger); | ||
logger.debug('constructor() [direction:%s, extendedRtpCapabilities:%o]', | ||
direction, extendedRtpCapabilities); | ||
super(); | ||
// Id. | ||
@@ -49,10 +50,2 @@ // @type {Number} | ||
// Map of Producers indexed by id. | ||
// @type {map<Number, Producer>} | ||
this._producers = new Map(); | ||
// Map of Consumers indexed by id. | ||
// @type {map<Number, Consumer>} | ||
this._consumers = new Map(); | ||
// Commands handler. | ||
@@ -71,2 +64,3 @@ // @type {CommandQueue} | ||
this._commandQueue.on('exec', this._execCommand.bind(this)); | ||
this._handleHandler(); | ||
@@ -142,3 +136,3 @@ } | ||
this.emit('@close', 'local'); | ||
this.emit('@close'); | ||
this.safeEmit('close', 'local', appData); | ||
@@ -153,2 +147,4 @@ | ||
* | ||
* @private | ||
* | ||
* @param {Any} [appData] - App custom data. | ||
@@ -165,3 +161,3 @@ */ | ||
this.emit('@close', 'remote'); | ||
this.emit('@close'); | ||
this.safeEmit('close', 'remote', appData); | ||
@@ -179,68 +175,4 @@ | ||
this._handler.close(); | ||
// Unhandle all the Producers. | ||
for (const producer of this._producers.values()) | ||
{ | ||
producer.setHandled(false); | ||
} | ||
// Unhandle all the Consumers. | ||
for (const consumer of this._consumers.values()) | ||
{ | ||
consumer.setHandled(false); | ||
} | ||
} | ||
/** | ||
* Send the given Producer over this Transport. | ||
* | ||
* @param {Producer} producer | ||
* | ||
* @return {Promise} | ||
* | ||
* @example | ||
* transport.send(videoProducer) | ||
* .then(() => { | ||
* // Done | ||
* }); | ||
*/ | ||
send(producer) | ||
{ | ||
logger.debug('send() [producer:%o]', producer); | ||
if (this._direction !== 'send') | ||
return Promise.reject(new Error('cannot send on a receiving Transport')); | ||
else if (!producer || producer.klass !== 'Producer') | ||
return Promise.reject(new TypeError('wrong Producer')); | ||
// Enqueue command. | ||
return this._commandQueue.push('addProducer', { producer }); | ||
} | ||
/** | ||
* Receive the given Consumer over this Transport. | ||
* | ||
* @param {Consumer} consumer | ||
* | ||
* @return {Promise} | ||
* | ||
* @example | ||
* transport.receive(aliceVideoConsumer) | ||
* .then(() => { | ||
* // Done | ||
* }); | ||
*/ | ||
receive(consumer) | ||
{ | ||
logger.debug('receive() [consumer:%o]', consumer); | ||
if (this._direction !== 'recv') | ||
return Promise.reject(new Error('cannot receive on a sending Transport')); | ||
else if (!consumer || consumer.klass !== 'Consumer') | ||
return Promise.reject(new TypeError('wrong Consumer')); | ||
// Enqueue command. | ||
return this._commandQueue.push('addConsumer', { consumer }); | ||
} | ||
_handleHandler() | ||
@@ -255,2 +187,4 @@ { | ||
logger.debug('Transport connection state changed to %s', state); | ||
this._connectionState = state; | ||
@@ -300,3 +234,3 @@ | ||
// Update Producer RTP parameters. | ||
producer.updateRtpParameters(rtpParameters); | ||
producer.setRtpParameters(rtpParameters); | ||
@@ -308,2 +242,150 @@ // Notify the server. | ||
/** | ||
* Send the given Producer over this Transport. | ||
* | ||
* @private | ||
* | ||
* @param {Producer} producer | ||
* | ||
* @return {Promise} | ||
*/ | ||
addProducer(producer) | ||
{ | ||
logger.debug('addProducer() [producer:%o]', producer); | ||
if (this._closed) | ||
return Promise.reject(new InvalidStateError('Transport closed')); | ||
if (this._direction !== 'send') | ||
return Promise.reject(new Error('not a sending Transport')); | ||
// Enqueue command. | ||
return this._commandQueue.push('addProducer', { producer }); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
removeProducer(producer, appData, { locallyClosed }) | ||
{ | ||
logger.debug('removeProducer() [producer:%o]', producer); | ||
// Enqueue command. | ||
this._commandQueue.push('removeProducer', { producer }) | ||
.catch(() => {}); | ||
if (locallyClosed) | ||
this.safeEmit('@notify', 'closeProducer', { id: producer.id, appData }); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
pauseProducer(producer, appData) | ||
{ | ||
logger.debug('pauseProducer() [producer:%o]', producer); | ||
const data = | ||
{ | ||
id : producer.id, | ||
appData : appData | ||
}; | ||
this.safeEmit('@notify', 'pauseProducer', data); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
resumeProducer(producer, appData) | ||
{ | ||
logger.debug('resumeProducer() [producer:%o]', producer); | ||
const data = | ||
{ | ||
id : producer.id, | ||
appData : appData | ||
}; | ||
this.safeEmit('@notify', 'resumeProducer', data); | ||
} | ||
/** | ||
* @private | ||
* | ||
* @return {Promise} | ||
*/ | ||
replaceProducerTrack(producer, track) | ||
{ | ||
logger.debug('replaceProducerTrack() [producer:%o]', producer); | ||
return this._commandQueue.push( | ||
'replaceProducerTrack', { producer, track }); | ||
} | ||
/** | ||
* Receive the given Consumer over this Transport. | ||
* | ||
* @private | ||
* | ||
* @param {Consumer} consumer | ||
* | ||
* @return {Promise} Resolves to a remote MediaStreamTrack. | ||
*/ | ||
addConsumer(consumer) | ||
{ | ||
logger.debug('addConsumer() [consumer:%o]', consumer); | ||
if (this._closed) | ||
return Promise.reject(new InvalidStateError('Transport closed')); | ||
if (this._direction !== 'recv') | ||
return Promise.reject(new Error('not a receiving Transport')); | ||
// Enqueue command. | ||
return this._commandQueue.push('addConsumer', { consumer }); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
removeConsumer(consumer) | ||
{ | ||
logger.debug('removeConsumer() [consumer:%o]', consumer); | ||
// Enqueue command. | ||
this._commandQueue.push('removeConsumer', { consumer }) | ||
.catch(() => {}); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
pauseConsumer(consumer, appData) | ||
{ | ||
logger.debug('pauseConsumer() [consumer:%o]', consumer); | ||
const data = | ||
{ | ||
id : consumer.id, | ||
appData : appData | ||
}; | ||
this.safeEmit('@notify', 'pauseConsumer', data); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
resumeConsumer(consumer, appData) | ||
{ | ||
logger.debug('resumeConsumer() [consumer:%o]', consumer); | ||
const data = | ||
{ | ||
id : consumer.id, | ||
appData : appData | ||
}; | ||
this.safeEmit('@notify', 'resumeConsumer', data); | ||
} | ||
_execCommand(command, promiseHolder) | ||
@@ -377,11 +459,4 @@ { | ||
if (producer.closed) | ||
return Promise.reject(new Error('Producer closed')); | ||
else if (producer.handled) | ||
return Promise.reject(new Error('Producer already handled by a Transport')); | ||
let producerRtpParameters; | ||
producer.setHandled('tmp'); | ||
// Call the handler. | ||
@@ -411,11 +486,3 @@ return Promise.resolve() | ||
{ | ||
producer.setHandled(true, producerRtpParameters); | ||
this._producers.set(producer.id, producer); | ||
this._handleProducer(producer); | ||
}) | ||
.catch((error) => | ||
{ | ||
producer.setHandled(false); | ||
throw error; | ||
producer.setRtpParameters(producerRtpParameters); | ||
}); | ||
@@ -444,18 +511,4 @@ } | ||
if (consumer.closed) | ||
return Promise.reject(new Error('Consumer closed')); | ||
else if (consumer.handled) | ||
return Promise.reject(new Error('Consumer already handled by a Transport')); | ||
// Check whether we can receive this Consumer. | ||
if (!consumer.supported) | ||
{ | ||
return Promise.reject( | ||
new Error('cannot receive this Consumer, unsupported codecs')); | ||
} | ||
let consumerTrack; | ||
consumer.setHandled('tmp'); | ||
// Call the handler. | ||
@@ -473,4 +526,5 @@ return Promise.resolve() | ||
{ | ||
id : consumer.id, | ||
paused : consumer.locallyPaused | ||
id : consumer.id, | ||
transportId : this.id, | ||
paused : consumer.locallyPaused | ||
}; | ||
@@ -482,13 +536,3 @@ | ||
{ | ||
consumer.setHandled(true, consumerTrack); | ||
this._consumers.set(consumer.id, consumer); | ||
this._handleConsumer(consumer); | ||
return consumerTrack; | ||
}) | ||
.catch((error) => | ||
{ | ||
consumer.setHandled(false); | ||
throw error; | ||
consumer.setTrack(consumerTrack); | ||
}); | ||
@@ -504,84 +548,2 @@ } | ||
} | ||
_handleProducer(producer) | ||
{ | ||
producer.on('@close', (originator, appData) => | ||
{ | ||
this._producers.delete(producer.id); | ||
// Enqueue command. | ||
this._commandQueue.push('removeProducer', { producer }) | ||
.catch(() => {}); | ||
if (originator === 'local') | ||
{ | ||
this.safeEmit( | ||
'@notify', 'closeProducer', { id: producer.id, appData }); | ||
} | ||
}); | ||
producer.on('@pause', (appData) => | ||
{ | ||
const data = | ||
{ | ||
id : producer.id, | ||
appData : appData | ||
}; | ||
this.safeEmit('@notify', 'pauseProducer', data); | ||
}); | ||
producer.on('@resume', (appData) => | ||
{ | ||
const data = | ||
{ | ||
id : producer.id, | ||
appData : appData | ||
}; | ||
this.safeEmit('@notify', 'resumeProducer', data); | ||
}); | ||
producer.on('@replacetrack', (track, callback, errback) => | ||
{ | ||
// Enqueue command. | ||
return this._commandQueue.push('replaceProducerTrack', { producer, track }) | ||
.then(callback) | ||
.catch(errback); | ||
}); | ||
} | ||
_handleConsumer(consumer) | ||
{ | ||
consumer.on('@close', () => | ||
{ | ||
this._consumers.delete(consumer.id); | ||
// Enqueue command. | ||
this._commandQueue.push('removeConsumer', { consumer }) | ||
.catch(() => {}); | ||
}); | ||
consumer.on('@pause', (appData) => | ||
{ | ||
const data = | ||
{ | ||
id : consumer.id, | ||
appData : appData | ||
}; | ||
this.safeEmit('@notify', 'pauseConsumer', data); | ||
}); | ||
consumer.on('@resume', (appData) => | ||
{ | ||
const data = | ||
{ | ||
id : consumer.id, | ||
appData : appData | ||
}; | ||
this.safeEmit('@notify', 'resumeConsumer', data); | ||
}); | ||
} | ||
} |
@@ -172,7 +172,8 @@ import randomNumberLib from 'random-number'; | ||
{ | ||
name : 'rtx', | ||
mimeType : `${capCodec.kind}/rtx`, | ||
clockRate : capCodec.clockRate, | ||
payloadType : capCodec.recvRtxPayloadType, | ||
parameters : | ||
name : 'rtx', | ||
mimeType : `${capCodec.kind}/rtx`, | ||
kind : capCodec.kind, | ||
clockRate : capCodec.clockRate, | ||
preferredPayloadType : capCodec.recvRtxPayloadType, | ||
parameters : | ||
{ | ||
@@ -179,0 +180,0 @@ apt : capCodec.recvPayloadType |
@@ -250,2 +250,3 @@ # mediasoup protocol | ||
id: 3333, | ||
transportId: 9999, | ||
paused: false | ||
@@ -332,5 +333,5 @@ } | ||
### newPeer | ||
### producerClosed | ||
A new `Peer` has joined the server-side `Room`. | ||
A server-side `Producer` has been closed in the server. | ||
@@ -341,15 +342,5 @@ Notification: | ||
{ | ||
method: 'newPeer', | ||
method: 'producerClosed', | ||
notification: true, | ||
name: 'alice', | ||
consumers: | ||
[ | ||
{ | ||
id: 5555, | ||
kind: 'audio', | ||
rtpParameters: {}, | ||
paused: false, | ||
appData: Any | ||
} | ||
], | ||
id: 2222, | ||
appData: Any | ||
@@ -360,5 +351,5 @@ } | ||
### peerClosed | ||
### producerPaused | ||
A server-side `Peer` has been closed (it may have left the room or his server-side `Peer` has been closed in the server). | ||
A server-side `Producer` has been paused in the server. | ||
@@ -369,5 +360,5 @@ Notification: | ||
{ | ||
method: 'peerClosed', | ||
method: 'producerPaused', | ||
notification: true, | ||
name: 'alice', | ||
id: 2222, | ||
appData: Any | ||
@@ -378,5 +369,5 @@ } | ||
### producerClosed | ||
### producerResumed | ||
A server-side `Producer` has been closed in the server. | ||
A server-side `Producer` has been resumed in the server. | ||
@@ -387,3 +378,3 @@ Notification: | ||
{ | ||
method: 'producerClosed', | ||
method: 'producerResumed', | ||
notification: true, | ||
@@ -396,5 +387,5 @@ id: 2222, | ||
### producerPaused | ||
### newPeer | ||
A server-side `Producer` has been paused in the server. | ||
A new `Peer` has joined the server-side `Room`. | ||
@@ -405,5 +396,15 @@ Notification: | ||
{ | ||
method: 'producerPaused', | ||
method: 'newPeer', | ||
notification: true, | ||
id: 2222, | ||
name: 'alice', | ||
consumers: | ||
[ | ||
{ | ||
id: 5555, | ||
kind: 'audio', | ||
rtpParameters: {}, | ||
paused: false, | ||
appData: Any | ||
} | ||
], | ||
appData: Any | ||
@@ -414,5 +415,5 @@ } | ||
### producerResumed | ||
### peerClosed | ||
A server-side `Producer` has been resumed in the server. | ||
A server-side `Peer` has been closed (it may have left the room or his server-side `Peer` has been closed in the server). | ||
@@ -423,5 +424,5 @@ Notification: | ||
{ | ||
method: 'producerResumed', | ||
method: 'peerClosed', | ||
notification: true, | ||
id: 2222, | ||
name: 'alice', | ||
appData: Any | ||
@@ -428,0 +429,0 @@ } |
{ | ||
"name": "mediasoup-client", | ||
"version": "0.3.1", | ||
"version": "0.4.0", | ||
"description": "mediasoup client SDK for mediasoup >= 2.0.0", | ||
@@ -11,3 +11,3 @@ "author": "Iñaki Baz Castillo <ibc@aliax.net> (https://inakibaz.me)", | ||
"bowser": "^1.7.2", | ||
"debug": "git://github.com/ibc/debug.git#disable-colors-in-edge-and-ie", | ||
"debug": "^3.0.1", | ||
"random-number": "0.0.9", | ||
@@ -14,0 +14,0 @@ "sdp-transform": "^2.3.0" |
@@ -66,7 +66,7 @@ # mediasoup-client | ||
// Send our audio. | ||
sendTransport.send(audioProducer) | ||
audioProducer.send(sendTransport) | ||
.then(() => console.log('sending our mic')); | ||
// Send our video. | ||
sendTransport.send(videoProducer) | ||
videoProducer.send(sendTransport) | ||
.then(() => console.log('sending our webcam')); | ||
@@ -98,4 +98,4 @@ }); | ||
{ | ||
// Error response, so pass the error message to the local Room. | ||
errback(String(error)); | ||
// Error response, so pass the error to the local Room. | ||
errback(error); | ||
}); | ||
@@ -157,3 +157,3 @@ }); | ||
// Receive the media over our receiving Transport. | ||
recvTransport.receive(consumer) | ||
consumer.receive(recvTransport) | ||
.then((track) => | ||
@@ -160,0 +160,0 @@ { |
18
TODO.md
@@ -22,18 +22,6 @@ # TODO | ||
- This is for both sending and receiving PeerConnections. | ||
Currently, in Firefox50.removeProducer/Consumer, we don't do SDP O/A if this is the latest Producer/Consumer. However, it does not work. When clater adding a new Producer Firefox sends nothing. I think that it's changing the transport ICE and DTLS stuff... | ||
Hummm, no no, it was a problem with H264 (not yet properly negotiated). When using opus and VP8, if I close the audio Producer, Firefox stops sending VP8. Fucking SDP and BUNDLE. | ||
https://github.com/versatica/mediasoup-client/issues/2 | ||
* Implement `getStats()` in browsers? or better report uniformely from mediasoup? | ||
# TODO in mediasoup 2.0.0 (server) | ||
* Rename `RtpReceiver` to `Producer` and `RtpSender` to `Consumer` | ||
* | ||
* mediasoup should accept `peer.createTransport()` by passing a `transportId` number. The same for `RtpReceiver`. | ||
* mediasoup should provide `const transport = peer.Transport()` rather than `createTransport()` (that returns a Promise). | ||
* May have to react on DTLS ALERT CLOSE in the server and make it "really" close the Transport and notify the client. Bufff... I don't like this... | ||
* mediasoup must be ready for the case in which the client closes a `Transport` on which a `Producer` was running. | ||
* Upon a `updateProducer` notification, map the new SSRCs of the `Producer` and send a PLI back to the browser (otherwise, consumers will get frozen video). |
Git dependency
Supply chain riskContains a dependency which resolves to a remote git URL. Dependencies fetched from git URLs are not immutable and can be used to inject untrusted code or reduce the likelihood of a reproducible install.
Found 1 instance in 1 package
173628
6109
0
+ Addeddebug@3.2.7(transitive)
+ Addedms@2.1.3(transitive)
Updateddebug@^3.0.1