diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx index 41f0d8c104d7ebf2e7201add9518386366149c7c..63cd05048d733abbcfae9951feddd8af8ad7e626 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx @@ -8,6 +8,8 @@ import VideoService from './service'; import VideoList from './video-list/component'; import { fetchWebRTCMappedStunTurnServers } from '/imports/utils/fetchStunTurnServers'; +const VIDEO_CONSTRAINTS = Meteor.settings.public.kurento.cameraConstraints; + const intlClientErrors = defineMessages({ iceCandidateError: { id: 'app.video.iceCandidateError', @@ -350,7 +352,9 @@ class VideoProvider extends Component { // in this case, 'closed' state is not caused by an error; // we stop listening to prevent this from being treated as an error - this.webRtcPeers[id].peerConnection.oniceconnectionstatechange = null; + if (this.webRtcPeers[id]) { + this.webRtcPeers[id].peerConnection.oniceconnectionstatechange = null; + } if (shareWebcam) { this.unshareWebcam(); @@ -390,25 +394,10 @@ class VideoProvider extends Component { } catch (error) { this.logger('error', 'Video provider failed to fetch ice servers, using default'); } finally { - const videoConstraints = { - width: { - min: 320, - max: 640, - }, - height: { - min: 180, - max: 480, - }, - }; - - if (!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/)) { - videoConstraints.frameRate = { min: 5, ideal: 10 }; - } - const options = { mediaConstraints: { audio: false, - video: videoConstraints, + video: VIDEO_CONSTRAINTS, }, onicecandidate: this._getOnIceCandidateCallback(id, shareWebcam), }; @@ -803,7 +792,7 @@ class VideoProvider extends Component { const { cameraId } = message; this.logger('info', 'Handle play stop for camera', { cameraId }); - this.stopWebRTCPeer(id); + this.stopWebRTCPeer(cameraId); } handlePlayStart(message) { diff --git a/bigbluebutton-html5/private/config/settings-development.json b/bigbluebutton-html5/private/config/settings-development.json index 20f055a90f4fc9e59adf64e1bfb5143cc48bfdfe..5352e0e72acef339e2118634d595e4cfb9b377b3 100755 --- a/bigbluebutton-html5/private/config/settings-development.json +++ b/bigbluebutton-html5/private/config/settings-development.json @@ -79,6 +79,14 @@ "chromeExtensionLink": "LINK", "chromeScreenshareSources": ["window", "screen"], "firefoxScreenshareSource": "window", + "cameraConstraints": { + "width": { + "max": 640 + }, + "height": { + "max": 480 + } + }, "enableScreensharing": false, "enableVideo": false, "enableVideoStats": false, @@ -344,9 +352,9 @@ } }, "clientLog": [ - { - "target": "server", - "level": "info" + { + "target": "server", + "level": "info" } ] }, @@ -379,7 +387,6 @@ ] }, - "serverLog": { "level": "info" } diff --git a/bigbluebutton-html5/private/config/settings-production.json b/bigbluebutton-html5/private/config/settings-production.json index b493a3687f9122261fdb6c09f4aab641b077de7e..be718710186555c9f165ee6bb2b369a55da3756d 100755 --- a/bigbluebutton-html5/private/config/settings-production.json +++ b/bigbluebutton-html5/private/config/settings-production.json @@ -79,6 +79,14 @@ "chromeExtensionLink": "LINK", "chromeScreenshareSources": ["window", "screen"], "firefoxScreenshareSource": "window", + "cameraConstraints": { + "width": { + "max": 640 + }, + "height": { + "max": 480 + } + }, "enableScreensharing": false, "enableVideo": false, "enableVideoStats": false, @@ -344,9 +352,9 @@ } }, "clientLog": [ - { - "target": "server", - "level": "info" + { + "target": "server", + "level": "info" } ] }, diff --git a/labs/bbb-webrtc-sfu/config/default.example.yml b/labs/bbb-webrtc-sfu/config/default.example.yml index 565d12ea97d7aca479ddb84eeb91d9125cba45f6..47a91a68650199d22fdecf4f3c6ed040f03f741a 100644 --- a/labs/bbb-webrtc-sfu/config/default.example.yml +++ b/labs/bbb-webrtc-sfu/config/default.example.yml @@ -18,9 +18,13 @@ to-akka: "to-akka-apps-redis-channel" from-akka: "from-akka-apps-redis-channel" common-message-version: "2.x" webcam-force-h264: true +# Target bitrate (kbps) for webcams. Value 0 leaves it unconstrained. +webcam-target-bitrate: 300 screenshare-force-h264: true screenshare-preferred-h264-profile: "42e01f" screenshareKeyframeInterval: 2 +# Target bitrate (kbps) for screenshare. Value 0 leaves it unconstrained. +screenshare-target-bitrate: 0 recordScreenSharing: true recordWebcams: false diff --git a/labs/bbb-webrtc-sfu/lib/audio/audio.js b/labs/bbb-webrtc-sfu/lib/audio/audio.js index 8e6667e65adcd386940cdf5c6961b3e421b3f8be..385c7d8e304a0d3beee3695649d10bde8721e810 100644 --- a/labs/bbb-webrtc-sfu/lib/audio/audio.js +++ b/labs/bbb-webrtc-sfu/lib/audio/audio.js @@ -255,7 +255,7 @@ module.exports = class Audio extends BaseProvider { return Promise.resolve(); } catch (err) { - reject(this._handleError(LOG_PREFIX, err, "recv", this.userId)); + throw (this._handleError(LOG_PREFIX, err, "recv", this.userId)); } }; diff --git a/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/SdpSession.js b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/SdpSession.js index a1eac49832889e4b81062107c9d48264a8b88617..7b1345fdb7ccf5e13e95513d6a84f1c70db8b325 100644 --- a/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/SdpSession.js +++ b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/model/SdpSession.js @@ -60,13 +60,19 @@ module.exports = class SdpSession extends MediaSession { return reject(this._handleError(C.ERROR.MEDIA_NO_AVAILABLE_CODEC)); } + const { targetBitrate } = this._options; + + if (answer && targetBitrate && targetBitrate !== '0') { + this._answer.addBandwidth('video', targetBitrate); + } + if (this._type !== 'WebRtcEndpoint') { this._offer.replaceServerIpv4(kurentoIp); - return resolve(answer); + return resolve(this._answer? this._answer._plainSdp : null); } await this._MediaServer.gatherCandidates(this._mediaElement); - resolve(answer); + resolve(this._answer._plainSdp); } catch (err) { return reject(this._handleError(err)); diff --git a/labs/bbb-webrtc-sfu/lib/mcs-core/lib/utils/SdpWrapper.js b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/utils/SdpWrapper.js index 6f35e3b7c7d64b083b752d6a62c8652da012d0b6..a19912bdba9d2002543a2f01d5e57d050c1c6169 100644 --- a/labs/bbb-webrtc-sfu/lib/mcs-core/lib/utils/SdpWrapper.js +++ b/labs/bbb-webrtc-sfu/lib/mcs-core/lib/utils/SdpWrapper.js @@ -54,6 +54,20 @@ module.exports = class SdpWrapper { return this._mediaCapabilities.hasAvailableAudioCodec; } + addBandwidth (type, bw) { + // Bandwidth format + // { type: 'TIAS or AS', limit: 2048000 } + for(var ml of this._jsonSdp.media) { + if(ml.type === type ) { + ml['bandwidth'] = []; + ml.bandwidth.push({ type: 'TIAS', limit: (bw >>> 0) * 1000 }); + ml.bandwidth.push({ type: 'AS', limit: bw }); + } + } + + this._plainSdp = transform.write(this._jsonSdp); + } + /** * Given a SDP, test if there is an audio description in it * @return {boolean} true if there is more than one video description, else false diff --git a/labs/bbb-webrtc-sfu/lib/screenshare/screenshare.js b/labs/bbb-webrtc-sfu/lib/screenshare/screenshare.js index f2eaa8cc957533ac649516f195c1612846a281ce..aeab9fc3afc1e2860d09a8697cdbcc0f2b12952c 100644 --- a/labs/bbb-webrtc-sfu/lib/screenshare/screenshare.js +++ b/labs/bbb-webrtc-sfu/lib/screenshare/screenshare.js @@ -21,6 +21,7 @@ const kurentoIp = config.get('kurentoIp'); const localIpAddress = config.get('localIpAddress'); const FORCE_H264 = config.get('screenshare-force-h264'); const PREFERRED_H264_PROFILE = config.get('screenshare-preferred-h264-profile'); +const SCREENSHARE_TARGET_BITRATE = config.get('screenshare-target-bitrate'); const SHOULD_RECORD = config.get('recordScreenSharing'); const KEYFRAME_INTERVAL = config.get('screenshareKeyframeInterval'); const LOG_PREFIX = "[screenshare]"; @@ -274,7 +275,7 @@ module.exports = class Screenshare extends BaseProvider { _startPresenter (sdpOffer) { return new Promise(async (resolve, reject) => { try { - const retSource = await this.mcs.publish(this.mcsUserId, this._meetingId, 'WebRtcEndpoint', {descriptor: sdpOffer}); + const retSource = await this.mcs.publish(this.mcsUserId, this._meetingId, 'WebRtcEndpoint', {descriptor: sdpOffer, targetBitrate: SCREENSHARE_TARGET_BITRATE }); this._presenterEndpoint = retSource.sessionId; sharedScreens[this._voiceBridge] = this._presenterEndpoint; diff --git a/labs/bbb-webrtc-sfu/lib/video/video.js b/labs/bbb-webrtc-sfu/lib/video/video.js index 9250d86d90774f12d12590a6f0526ffe9b28171e..be068de8beda19dafd62708f32faac32df79c4c3 100644 --- a/labs/bbb-webrtc-sfu/lib/video/video.js +++ b/labs/bbb-webrtc-sfu/lib/video/video.js @@ -9,6 +9,7 @@ const Messaging = require('../bbb/messages/Messaging'); const h264_sdp = require('../h264-sdp'); const BaseProvider = require('../base/BaseProvider'); const FORCE_H264 = config.get('webcam-force-h264'); +const WEBCAM_TARGET_BITRATE = config.get('webcam-target-bitrate'); const SHOULD_RECORD = config.get('recordWebcams'); const LOG_PREFIX = "[video]"; @@ -259,7 +260,7 @@ module.exports = class Video extends BaseProvider { return new Promise(async (resolve, reject) => { try { if (this.shared) { - let { answer, sessionId } = await this.mcs.publish(this.userId, this.meetingId, type, { descriptor }); + let { answer, sessionId } = await this.mcs.publish(this.userId, this.meetingId, type, { descriptor, targetBitrate: WEBCAM_TARGET_BITRATE }); this.mediaId = sessionId; sharedWebcams[this.id] = this.mediaId; return resolve(answer);