Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Receive audio only or video only #25

Merged
merged 9 commits into from
Nov 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/peer.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ class Peer extends EventEmitter {
* @param {number} [options.audioBandwidth] - A max audio bandwidth(kbps)
* @param {string} [options.videoCodec] - A video codec like 'H264'
* @param {string} [options.audioCodec] - A video codec like 'PCMU'
* @param {boolean} [options.videoReceiveEnabled] - A flag to set video recvonly
* @param {boolean} [options.audioReceiveEnabled] - A flag to set audio recvonly
* @return {MediaConnection} An instance of MediaConnection.
*/
call(peerId, stream, options = {}) {
Expand Down Expand Up @@ -156,6 +158,8 @@ class Peer extends EventEmitter {
* @param {number} [roomOptions.audioBandwidth] - A max audio bandwidth(kbps)
* @param {string} [roomOptions.videoCodec] - A video codec like 'H264'
* @param {string} [roomOptions.audioCodec] - A video codec like 'PCMU'
* @param {boolean} [options.videoReceiveEnabled] - A flag to set video recvonly
* @param {boolean} [options.audioReceiveEnabled] - A flag to set audio recvonly
* @return {SFURoom|MeshRoom} - An instance of SFURoom or MeshRoom.
*/
joinRoom(roomName, roomOptions = {}) {
Expand Down Expand Up @@ -373,6 +377,8 @@ class Peer extends EventEmitter {
* @param {number} [roomOptions.audioBandwidth] - A max audio bandwidth(kbps)
* @param {string} [roomOptions.videoCodec] - A video codec like 'H264'
* @param {string} [roomOptions.audioCodec] - A video codec like 'PCMU'
* @param {boolean} [roomOptions.videoReceiveEnabled] - A flag to set video recvonly
* @param {boolean} [roomOptions.audioReceiveEnabled] - A flag to set audio recvonly
* @return {SFURoom} - An instance of SFURoom.
*/
_initializeSfuRoom(roomName, roomOptions = {}) {
Expand Down Expand Up @@ -405,6 +411,8 @@ class Peer extends EventEmitter {
* @param {number} [roomOptions.audioBandwidth] - A max audio bandwidth(kbps)
* @param {string} [roomOptions.videoCodec] - A video codec like 'H264'
* @param {string} [roomOptions.audioCodec] - A video codec like 'PCMU'
* @param {boolean} [roomOptions.videoReceiveEnabled] - A flag to set video recvonly
* @param {boolean} [roomOptions.audioReceiveEnabled] - A flag to set audio recvonly
* @return {SFURoom} - An instance of MeshRoom.
*/
_initializeFullMeshRoom(roomName, roomOptions = {}) {
Expand Down
42 changes: 25 additions & 17 deletions src/peer/mediaConnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class MediaConnection extends Connection {
* @param {number} [options.audioBandwidth] - A max audio bandwidth(kbps)
* @param {string} [options.videoCodec] - A video codec like 'H264'
* @param {string} [options.audioCodec] - A video codec like 'PCMU'
* @param {boolean} [options.videoReceiveEnabled] - A flag to set video recvonly
* @param {boolean} [options.audioReceiveEnabled] - A flag to set audio recvonly
*/
constructor(remoteId, options) {
super(remoteId, options);
Expand All @@ -51,14 +53,16 @@ class MediaConnection extends Connection {
if (this._options.originator) {
this._negotiator.startConnection(
{
type: 'media',
stream: this.localStream,
originator: this._options.originator,
pcConfig: this._options.pcConfig,
videoBandwidth: this._options.videoBandwidth,
audioBandwidth: this._options.audioBandwidth,
videoCodec: this._options.videoCodec,
audioCodec: this._options.audioCodec,
type: 'media',
stream: this.localStream,
originator: this._options.originator,
pcConfig: this._options.pcConfig,
videoBandwidth: this._options.videoBandwidth,
audioBandwidth: this._options.audioBandwidth,
videoCodec: this._options.videoCodec,
audioCodec: this._options.audioCodec,
videoReceiveEnabled: this._options.videoReceiveEnabled,
audioReceiveEnabled: this._options.audioReceiveEnabled,
}
);
this._pcAvailable = true;
Expand All @@ -74,6 +78,8 @@ class MediaConnection extends Connection {
* @param {number} [options.audioBandwidth] - A max audio bandwidth(kbps)
* @param {string} [options.videoCodec] - A video codec like 'H264'
* @param {string} [options.audioCodec] - A video codec like 'PCMU'
* @param {boolean} [options.videoReceiveEnabled] - A flag to set video recvonly
* @param {boolean} [options.audioReceiveEnabled] - A flag to set audio recvonly
*/
answer(stream, options = {}) {
if (this.localStream) {
Expand All @@ -86,15 +92,17 @@ class MediaConnection extends Connection {
this.localStream = stream;
this._negotiator.startConnection(
{
type: 'media',
stream: this.localStream,
originator: false,
offer: this._options.payload.offer,
pcConfig: this._options.pcConfig,
audioBandwidth: options.audioBandwidth,
videoBandwidth: options.videoBandwidth,
videoCodec: options.videoCodec,
audioCodec: options.audioCodec,
type: 'media',
stream: this.localStream,
originator: false,
offer: this._options.payload.offer,
pcConfig: this._options.pcConfig,
audioBandwidth: options.audioBandwidth,
videoBandwidth: options.videoBandwidth,
videoCodec: options.videoCodec,
audioCodec: options.audioCodec,
videoReceiveEnabled: options.videoReceiveEnabled,
audioReceiveEnabled: options.audioReceiveEnabled,
}
);
this._pcAvailable = true;
Expand Down
28 changes: 17 additions & 11 deletions src/peer/meshRoom.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class MeshRoom extends Room {
* @param {number} [options.audioBandwidth] - A max audio bandwidth(kbps)
* @param {string} [options.videoCodec] - A video codec like 'H264'
* @param {string} [options.audioCodec] - A video codec like 'PCMU'
* @param {boolean} [options.videoReceiveEnabled] - A flag to set video recvonly
* @param {boolean} [options.audioReceiveEnabled] - A flag to set audio recvonly
*/
constructor(name, peerId, options) {
super(name, peerId, options);
Expand Down Expand Up @@ -78,13 +80,15 @@ class MeshRoom extends Room {
*/
makeMediaConnections(peerIds) {
const options = {
stream: this._localStream,
pcConfig: this._pcConfig,
originator: true,
videoBandwidth: this._options.videoBandwidth,
audioBandwidth: this._options.audioBandwidth,
videoCodec: this._options.videoCodec,
audioCodec: this._options.audioCodec,
stream: this._localStream,
pcConfig: this._pcConfig,
originator: true,
videoBandwidth: this._options.videoBandwidth,
audioBandwidth: this._options.audioBandwidth,
videoCodec: this._options.videoCodec,
audioCodec: this._options.audioCodec,
videoReceiveEnabled: this._options.videoReceiveEnabled,
audioReceiveEnabled: this._options.audioReceiveEnabled,
};

this._makeConnections(peerIds, 'media', options);
Expand Down Expand Up @@ -169,10 +173,12 @@ class MeshRoom extends Room {
this._setupMessageHandlers(connection);

connection.answer(this._localStream, {
videoBandwidth: this._options.videoBandwidth,
audioBandwidth: this._options.audioBandwidth,
videoCodec: this._options.videoCodec,
audioCodec: this._options.audioCodec,
videoBandwidth: this._options.videoBandwidth,
audioBandwidth: this._options.audioBandwidth,
videoCodec: this._options.videoCodec,
audioCodec: this._options.audioCodec,
videoReceiveEnabled: this._options.videoReceiveEnabled,
audioReceiveEnabled: this._options.audioReceiveEnabled,
});
} else {
logger.warn(`Received malformed connection type: ${offerMessage.connectionType}`);
Expand Down
71 changes: 58 additions & 13 deletions src/peer/negotiator.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class Negotiator extends EventEmitter {
* @param {number} [options.audioBandwidth] - A max audio bandwidth(kbps)
* @param {string} [options.videoCodec] - A video codec like 'H264'
* @param {string} [options.audioCodec] - A video codec like 'PCMU'
* @param {boolean} [options.videoReceiveEnabled] - A flag to set video recvonly
* @param {boolean} [options.audioReceiveEnabled] - A flag to set audio recvonly
*/
startConnection(options = {}) {
this._pc = this._createPeerConnection(options.pcConfig);
Expand All @@ -56,6 +58,7 @@ class Negotiator extends EventEmitter {
this._audioCodec = options.audioCodec;
this._videoCodec = options.videoCodec;
this._type = options.type;
this._recvonlyState = this._getReceiveOnlyState(options);

if (this._type === 'media') {
if (options.stream) {
Expand Down Expand Up @@ -290,24 +293,21 @@ class Negotiator extends EventEmitter {
_makeOfferSdp() {
let createOfferPromise;

// if this peer is in recvonly mode
const isRecvOnly = this._type === 'media' &&
((this._isRtpSenderAvailable && this._pc.getSenders().length === 0) ||
(this._isRtpLocalStreamsAvailable && this._pc.getLocalStreams().length === 0));

if (isRecvOnly) {
// DataConnection
if (this._type !== 'media') {
createOfferPromise = this._pc.createOffer();
// MediaConnection
} else {
if (this._isAddTransceiverAvailable) {
this._pc.addTransceiver('audio').setDirection('recvonly');
this._pc.addTransceiver('video').setDirection('recvonly');
this._recvonlyState.audio && this._pc.addTransceiver('audio').setDirection('recvonly');
this._recvonlyState.video && this._pc.addTransceiver('video').setDirection('recvonly');
createOfferPromise = this._pc.createOffer();
} else {
createOfferPromise = this._pc.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true,
offerToReceiveAudio: this._recvonlyState.audio,
offerToReceiveVideo: this._recvonlyState.video,
});
}
} else {
createOfferPromise = this._pc.createOffer();
}

return createOfferPromise
Expand Down Expand Up @@ -437,6 +437,51 @@ class Negotiator extends EventEmitter {
});
}

/**
* Get map object describes which kinds of tracks should be marked as recvonly
* @param {Object} options - Options of peer.call()
* @return {Object} Map object which streamTrack will be recvonly or not
*/
_getReceiveOnlyState(options = {}) {
const state = {
audio: false,
video: false,
};

const hasStream = options.stream instanceof MediaStream;
const hasAudioTrack = hasStream ? options.stream.getAudioTracks().length !== 0 : false;
const hasVideoTrack = hasStream ? options.stream.getVideoTracks().length !== 0 : false;

// force true if stream not passed(backward compatibility)
if (
hasStream === false
&& options.audioReceiveEnabled === undefined
&& options.videoReceiveEnabled === undefined
) {
state.audio = true;
state.video = true;
return state;
}

// Set recvonly to true if `stream does not have track` and `option is true` case only
if (options.audioReceiveEnabled && hasAudioTrack === false) {
state.audio = true;
}
if (options.videoReceiveEnabled && hasVideoTrack === false) {
state.video = true;
}

// If stream has track, ignore options, which results in setting sendrecv internally.
if (options.audioReceiveEnabled === false && hasAudioTrack) {
logger.warn('Option audioReceiveEnabled will be treated as true, because passed stream has MediaStreamTrack(kind = audio)');
}
if (options.videoReceiveEnabled === false && hasVideoTrack) {
logger.warn('Option videoReceiveEnabled will be treated as true, because passed stream has MediaStreamTrack(kind = video)');
}

return state;
}

/**
* Replace the stream being sent with a new one.
* Video and audio are replaced per track by using `xxxTrack` methods.
Expand Down Expand Up @@ -467,7 +512,7 @@ class Negotiator extends EventEmitter {
* Replace a track being sent with a new one.
* @param {RTCRtpSender} sender - The sender which type is video or audio.
* @param {MediaStreamTrack} track - The track of new stream.
* @param {MediaStream} stream - The stream which contains the track.
* @param {MediaStream} stream - The stream which contains the track.
* @private
*/
function _updateSenderWithTrack(sender, track, stream) {
Expand Down
2 changes: 2 additions & 0 deletions src/peer/room.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class Room extends EventEmitter {
* @param {number} [options.audioBandwidth] - A max audio bandwidth(kbps)
* @param {string} [options.videoCodec] - A video codec like 'H264'
* @param {string} [options.audioCodec] - A video codec like 'PCMU'
* @param {boolean} [options.videoReceiveEnabled] - A flag to set video recvonly
* @param {boolean} [options.audioReceiveEnabled] - A flag to set audio recvonly
*/
constructor(name, peerId, options = {}) {
super();
Expand Down
2 changes: 2 additions & 0 deletions src/peer/sfuRoom.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class SFURoom extends Room {
* @param {number} [options.audioBandwidth] - A max audio bandwidth(kbps)
* @param {string} [options.videoCodec] - A video codec like 'H264'
* @param {string} [options.audioCodec] - A video codec like 'PCMU'
* @param {boolean} [options.videoReceiveEnabled] - A flag to set video recvonly
* @param {boolean} [options.audioReceiveEnabled] - A flag to set audio recvonly
*/
constructor(name, peerId, options) {
super(name, peerId, options);
Expand Down
84 changes: 84 additions & 0 deletions tests/peer/negotiator.js
Original file line number Diff line number Diff line change
Expand Up @@ -1160,4 +1160,88 @@ describe('Negotiator', () => {
negotiator._setRemoteDescription(sdp);
});
});

describe('_getReceiveOnlyState', () => {
let negotiator;
const audioVideoStream = new MediaStream();
const audioOnlyStream = new MediaStream();
const videoOnlyStream = new MediaStream();

before(() => {
negotiator = new Negotiator();

sinon.stub(audioVideoStream, 'getVideoTracks').returns([{}]);
sinon.stub(audioVideoStream, 'getAudioTracks').returns([{}]);
sinon.stub(audioOnlyStream, 'getAudioTracks').returns([{}]);
sinon.stub(videoOnlyStream, 'getVideoTracks').returns([{}]);
});

it('should returns correct state with audio and video stream', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for comprehensive test cases! 👍 👍 👍

[
[{stream: audioVideoStream, audioReceiveEnabled: true, videoReceiveEnabled: true}, {audio: false, video: false}],
[{stream: audioVideoStream, audioReceiveEnabled: true, videoReceiveEnabled: false}, {audio: false, video: false}],
[{stream: audioVideoStream, audioReceiveEnabled: true}, {audio: false, video: false}],
[{stream: audioVideoStream, audioReceiveEnabled: false, videoReceiveEnabled: true}, {audio: false, video: false}],
[{stream: audioVideoStream, audioReceiveEnabled: false, videoReceiveEnabled: false}, {audio: false, video: false}],
[{stream: audioVideoStream, audioReceiveEnabled: false}, {audio: false, video: false}],
[{stream: audioVideoStream, videoReceiveEnabled: true}, {audio: false, video: false}],
[{stream: audioVideoStream, videoReceiveEnabled: false}, {audio: false, video: false}],
[{stream: audioVideoStream}, {audio: false, video: false}],
].forEach(([options, expect]) => {
const res = negotiator._getReceiveOnlyState(options);
assert.deepEqual(res, expect);
});
});

it('should returns correct state with audio only stream', () => {
[
[{stream: audioOnlyStream, audioReceiveEnabled: true, videoReceiveEnabled: true}, {audio: false, video: true}],
[{stream: audioOnlyStream, audioReceiveEnabled: true, videoReceiveEnabled: false}, {audio: false, video: false}],
[{stream: audioOnlyStream, audioReceiveEnabled: true}, {audio: false, video: false}],
[{stream: audioOnlyStream, audioReceiveEnabled: false, videoReceiveEnabled: true}, {audio: false, video: true}],
[{stream: audioOnlyStream, audioReceiveEnabled: false, videoReceiveEnabled: false}, {audio: false, video: false}],
[{stream: audioOnlyStream, audioReceiveEnabled: false}, {audio: false, video: false}],
[{stream: audioOnlyStream, videoReceiveEnabled: true}, {audio: false, video: true}],
[{stream: audioOnlyStream, videoReceiveEnabled: false}, {audio: false, video: false}],
[{stream: audioOnlyStream}, {audio: false, video: false}],
].forEach(([options, expect]) => {
const res = negotiator._getReceiveOnlyState(options);
assert.deepEqual(res, expect);
});
});

it('should returns correct state with video only stream', () => {
[
[{stream: videoOnlyStream, audioReceiveEnabled: true, videoReceiveEnabled: true}, {audio: true, video: false}],
[{stream: videoOnlyStream, audioReceiveEnabled: true, videoReceiveEnabled: false}, {audio: true, video: false}],
[{stream: videoOnlyStream, audioReceiveEnabled: true}, {audio: true, video: false}],
[{stream: videoOnlyStream, audioReceiveEnabled: false, videoReceiveEnabled: true}, {audio: false, video: false}],
[{stream: videoOnlyStream, audioReceiveEnabled: false, videoReceiveEnabled: false}, {audio: false, video: false}],
[{stream: videoOnlyStream, audioReceiveEnabled: false}, {audio: false, video: false}],
[{stream: videoOnlyStream, videoReceiveEnabled: true}, {audio: false, video: false}],
[{stream: videoOnlyStream, videoReceiveEnabled: false}, {audio: false, video: false}],
[{stream: videoOnlyStream}, {audio: false, video: false}],
].forEach(([options, expect]) => {
const res = negotiator._getReceiveOnlyState(options);
assert.deepEqual(res, expect);
});
});

it('should returns correct state without stream', () => {
[
[{stream: undefined, audioReceiveEnabled: true, videoReceiveEnabled: true}, {audio: true, video: true}],
[{stream: undefined, audioReceiveEnabled: true, videoReceiveEnabled: false}, {audio: true, video: false}],
[{stream: undefined, audioReceiveEnabled: true}, {audio: true, video: false}],
[{stream: undefined, audioReceiveEnabled: false, videoReceiveEnabled: true}, {audio: false, video: true}],
[{stream: undefined, audioReceiveEnabled: false, videoReceiveEnabled: false}, {audio: false, video: false}],
[{stream: undefined, audioReceiveEnabled: false}, {audio: false, video: false}],
[{stream: undefined, videoReceiveEnabled: true}, {audio: false, video: true}],
[{stream: undefined, videoReceiveEnabled: false}, {audio: false, video: false}],
[{stream: undefined}, {audio: true, video: true}], // special case for backward compatibility
].forEach(([options, expect]) => {
const res = negotiator._getReceiveOnlyState(options);
assert.deepEqual(res, expect);
});
});
});
});