You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
464 lines
13 KiB
464 lines
13 KiB
/**
|
|
*
|
|
* @copyright Copyright (c) 2019, Daniel Calviño Sánchez (danxuliu@gmail.com)
|
|
*
|
|
* @license AGPL-3.0-or-later
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
import store from '../../../store/index.js'
|
|
|
|
/**
|
|
*
|
|
*/
|
|
export default function LocalMediaModel() {
|
|
|
|
this.attributes = {
|
|
localStreamRequestVideoError: null,
|
|
localStream: null,
|
|
audioAvailable: false,
|
|
audioEnabled: false,
|
|
speaking: false,
|
|
speakingWhileMuted: false,
|
|
currentVolume: -100,
|
|
volumeThreshold: -100,
|
|
videoAvailable: false,
|
|
videoEnabled: false,
|
|
localScreen: null,
|
|
token: '',
|
|
raisedHand: false,
|
|
}
|
|
|
|
this._handlers = []
|
|
|
|
this._handleLocalStreamRequestedBound = this._handleLocalStreamRequested.bind(this)
|
|
this._handleLocalStreamBound = this._handleLocalStream.bind(this)
|
|
this._handleLocalStreamRequestFailedRetryNoVideoBound = this._handleLocalStreamRequestFailedRetryNoVideo.bind(this)
|
|
this._handleLocalStreamRequestFailedBound = this._handleLocalStreamRequestFailed.bind(this)
|
|
this._handleLocalStreamChangedBound = this._handleLocalStreamChanged.bind(this)
|
|
this._handleLocalStreamStoppedBound = this._handleLocalStreamStopped.bind(this)
|
|
this._handleAudioOnBound = this._handleAudioOn.bind(this)
|
|
this._handleAudioOffBound = this._handleAudioOff.bind(this)
|
|
this._handleVolumeChangeBound = this._handleVolumeChange.bind(this)
|
|
this._handleSpeakingBound = this._handleSpeaking.bind(this)
|
|
this._handleStoppedSpeakingBound = this._handleStoppedSpeaking.bind(this)
|
|
this._handleSpeakingWhileMutedBound = this._handleSpeakingWhileMuted.bind(this)
|
|
this._handleStoppedSpeakingWhileMutedBound = this._handleStoppedSpeakingWhileMuted.bind(this)
|
|
this._handleVideoOnBound = this._handleVideoOn.bind(this)
|
|
this._handleVideoOffBound = this._handleVideoOff.bind(this)
|
|
this._handleLocalScreenBound = this._handleLocalScreen.bind(this)
|
|
this._handleLocalScreenStoppedBound = this._handleLocalScreenStopped.bind(this)
|
|
|
|
}
|
|
|
|
LocalMediaModel.prototype = {
|
|
|
|
get(key) {
|
|
return this.attributes[key]
|
|
},
|
|
|
|
set(key, value) {
|
|
this.attributes[key] = value
|
|
|
|
this._trigger('change:' + key, [value])
|
|
},
|
|
|
|
on(event, handler) {
|
|
if (!Object.prototype.hasOwnProperty.call(this._handlers, event)) {
|
|
this._handlers[event] = [handler]
|
|
} else {
|
|
this._handlers[event].push(handler)
|
|
}
|
|
},
|
|
|
|
off(event, handler) {
|
|
const handlers = this._handlers[event]
|
|
if (!handlers) {
|
|
return
|
|
}
|
|
|
|
const index = handlers.indexOf(handler)
|
|
if (index !== -1) {
|
|
handlers.splice(index, 1)
|
|
}
|
|
},
|
|
|
|
_trigger(event, args) {
|
|
let handlers = this._handlers[event]
|
|
if (!handlers) {
|
|
return
|
|
}
|
|
|
|
args.unshift(this)
|
|
|
|
handlers = handlers.slice(0)
|
|
for (let i = 0; i < handlers.length; i++) {
|
|
const handler = handlers[i]
|
|
handler.apply(handler, args)
|
|
}
|
|
},
|
|
|
|
getWebRtc() {
|
|
return this._webRtc
|
|
},
|
|
|
|
setWebRtc(webRtc) {
|
|
if (this._webRtc && this._webRtc.webrtc) {
|
|
this._webRtc.webrtc.off('localStreamRequested', this._handleLocalStreamRequestedBound)
|
|
this._webRtc.webrtc.off('localStream', this._handleLocalStreamBound)
|
|
this._webRtc.webrtc.off('localStreamRequestFailedRetryNoVideo', this._handleLocalStreamRequestFailedBound)
|
|
this._webRtc.webrtc.off('localStreamRequestFailed', this._handleLocalStreamRequestFailedBound)
|
|
this._webRtc.webrtc.off('localStreamChanged', this._handleLocalStreamChangedBound)
|
|
this._webRtc.webrtc.off('localStreamStopped', this._handleLocalStreamStoppedBound)
|
|
this._webRtc.webrtc.off('audioOn', this._handleAudioOnBound)
|
|
this._webRtc.webrtc.off('audioOff', this._handleAudioOffBound)
|
|
this._webRtc.webrtc.off('volumeChange', this._handleVolumeChangeBound)
|
|
this._webRtc.webrtc.off('speaking', this._handleSpeakingBound)
|
|
this._webRtc.webrtc.off('stoppedSpeaking', this._handleStoppedSpeakingBound)
|
|
this._webRtc.webrtc.off('speakingWhileMuted', this._handleSpeakingWhileMutedBound)
|
|
this._webRtc.webrtc.off('stoppedSpeakingWhileMuted', this._handleStoppedSpeakingWhileMutedBound)
|
|
this._webRtc.webrtc.off('videoOn', this._handleVideoOnBound)
|
|
this._webRtc.webrtc.off('videoOff', this._handleVideoOffBound)
|
|
this._webRtc.webrtc.off('localScreen', this._handleLocalScreenBound)
|
|
this._webRtc.webrtc.off('localScreenStopped', this._handleLocalScreenStoppedBound)
|
|
}
|
|
|
|
this._webRtc = webRtc
|
|
|
|
this.set('localStream', null)
|
|
this.set('audioAvailable', false)
|
|
this.set('audioEnabled', false)
|
|
this.set('speaking', false)
|
|
this.set('speakingWhileMuted', false)
|
|
this.set('currentVolume', -100)
|
|
this.set('volumeThreshold', -100)
|
|
this.set('videoAvailable', false)
|
|
this.set('videoEnabled', false)
|
|
this.set('localScreen', null)
|
|
|
|
this._webRtc.webrtc.on('localStreamRequested', this._handleLocalStreamRequestedBound)
|
|
this._webRtc.webrtc.on('localStream', this._handleLocalStreamBound)
|
|
this._webRtc.webrtc.on('localStreamRequestFailedRetryNoVideo', this._handleLocalStreamRequestFailedRetryNoVideoBound)
|
|
this._webRtc.webrtc.on('localStreamRequestFailed', this._handleLocalStreamRequestFailedBound)
|
|
this._webRtc.webrtc.on('localStreamChanged', this._handleLocalStreamChangedBound)
|
|
this._webRtc.webrtc.on('localStreamStopped', this._handleLocalStreamStoppedBound)
|
|
this._webRtc.webrtc.on('audioOn', this._handleAudioOnBound)
|
|
this._webRtc.webrtc.on('audioOff', this._handleAudioOffBound)
|
|
this._webRtc.webrtc.on('volumeChange', this._handleVolumeChangeBound)
|
|
this._webRtc.webrtc.on('speaking', this._handleSpeakingBound)
|
|
this._webRtc.webrtc.on('stoppedSpeaking', this._handleStoppedSpeakingBound)
|
|
this._webRtc.webrtc.on('speakingWhileMuted', this._handleSpeakingWhileMutedBound)
|
|
this._webRtc.webrtc.on('stoppedSpeakingWhileMuted', this._handleStoppedSpeakingWhileMutedBound)
|
|
this._webRtc.webrtc.on('videoOn', this._handleVideoOnBound)
|
|
this._webRtc.webrtc.on('videoOff', this._handleVideoOffBound)
|
|
this._webRtc.webrtc.on('localScreen', this._handleLocalScreenBound)
|
|
this._webRtc.webrtc.on('localScreenStopped', this._handleLocalScreenStoppedBound)
|
|
},
|
|
|
|
_handleLocalStreamRequested(constraints, context) {
|
|
if (context !== 'retry-no-video') {
|
|
this.set('localStreamRequestVideoError', null)
|
|
}
|
|
},
|
|
|
|
_handleLocalStream(configuration, localStream) {
|
|
// Although there could be several local streams active at the same
|
|
// time (if the local media is started again before stopping it
|
|
// first) the methods to control them ("mute", "unmute",
|
|
// "pauseVideo" and "resumeVideo") act on all the streams, it is not
|
|
// possible to control them individually. Also all local streams
|
|
// are transmitted when a Peer is created, but if another local
|
|
// stream is then added it will not be automatically added to the
|
|
// Peer. As it is not well supported and there is also no need to
|
|
// use several local streams for now it is assumed that only one
|
|
// local stream will be active at the same time.
|
|
this.set('localStream', localStream)
|
|
|
|
this.set('token', store.getters.getToken())
|
|
|
|
this._setInitialMediaState(configuration)
|
|
},
|
|
|
|
_handleLocalStreamRequestFailedRetryNoVideo(constraints, error) {
|
|
if (!error || error.name === 'NotFoundError') {
|
|
return
|
|
}
|
|
|
|
this.set('localStreamRequestVideoError', error)
|
|
},
|
|
|
|
_handleLocalStreamRequestFailed() {
|
|
this.set('localStream', null)
|
|
|
|
this._setInitialMediaState({ audio: false, video: false })
|
|
},
|
|
|
|
_setInitialMediaState(configuration) {
|
|
if (configuration.audio !== false) {
|
|
this.set('audioAvailable', true)
|
|
if (this.get('audioEnabled')) {
|
|
this.enableAudio()
|
|
} else {
|
|
this.disableAudio()
|
|
}
|
|
} else {
|
|
this.set('audioEnabled', false)
|
|
this.set('audioAvailable', false)
|
|
}
|
|
|
|
if (configuration.video !== false) {
|
|
this.set('videoAvailable', true)
|
|
if (this.get('videoEnabled')) {
|
|
this.enableVideo()
|
|
} else {
|
|
this.disableVideo()
|
|
}
|
|
} else {
|
|
this.set('videoEnabled', false)
|
|
this.set('videoAvailable', false)
|
|
}
|
|
|
|
this.set('raisedHand', { state: false, timestamp: Date.now() })
|
|
},
|
|
|
|
_handleLocalStreamChanged(localStream) {
|
|
// Only a single local stream is assumed to be active at the same time.
|
|
this.set('localStream', localStream)
|
|
|
|
this._updateMediaAvailability(localStream)
|
|
},
|
|
|
|
_updateMediaAvailability(localStream) {
|
|
if (localStream && localStream.getAudioTracks().length > 0) {
|
|
this.set('audioAvailable', true)
|
|
|
|
if (!this.get('audioEnabled')) {
|
|
// Explicitly disable the audio to ensure that it will also be
|
|
// disabled in the other end. Otherwise the WebRTC media could
|
|
// be enabled.
|
|
this.disableAudio()
|
|
}
|
|
} else {
|
|
this.disableAudio()
|
|
this.set('audioAvailable', false)
|
|
}
|
|
|
|
if (localStream && localStream.getVideoTracks().length > 0) {
|
|
this.set('videoAvailable', true)
|
|
|
|
if (!this.get('videoEnabled')) {
|
|
// Explicitly disable the video to ensure that it will also be
|
|
// disabled in the other end. Otherwise the WebRTC media could
|
|
// be enabled.
|
|
this.disableVideo()
|
|
}
|
|
} else {
|
|
this.disableVideo()
|
|
this.set('videoAvailable', false)
|
|
}
|
|
},
|
|
|
|
_handleLocalStreamStopped(localStream) {
|
|
if (this.get('localStream') !== localStream) {
|
|
return
|
|
}
|
|
|
|
this.set('localStream', null)
|
|
|
|
this.set('audioEnabled', false)
|
|
this.set('audioAvailable', false)
|
|
this.set('videoEnabled', false)
|
|
this.set('videoAvailable', false)
|
|
},
|
|
|
|
_handleAudioOn() {
|
|
if (!this.get('audioAvailable')) {
|
|
return
|
|
}
|
|
|
|
this.set('audioEnabled', true)
|
|
},
|
|
|
|
_handleAudioOff() {
|
|
if (!this.get('audioAvailable')) {
|
|
return
|
|
}
|
|
|
|
this.set('audioEnabled', false)
|
|
},
|
|
|
|
_handleVolumeChange(currentVolume, volumeThreshold) {
|
|
if (!this.get('audioAvailable')) {
|
|
return
|
|
}
|
|
|
|
this.set('currentVolume', currentVolume)
|
|
this.set('volumeThreshold', volumeThreshold)
|
|
},
|
|
|
|
_handleSpeaking() {
|
|
if (!this.get('audioAvailable')) {
|
|
return
|
|
}
|
|
|
|
this.set('speaking', true)
|
|
},
|
|
|
|
_handleStoppedSpeaking() {
|
|
if (!this.get('audioAvailable')) {
|
|
return
|
|
}
|
|
|
|
this.set('speaking', false)
|
|
},
|
|
|
|
_handleSpeakingWhileMuted() {
|
|
if (!this.get('audioAvailable')) {
|
|
return
|
|
}
|
|
|
|
this.set('speakingWhileMuted', true)
|
|
},
|
|
|
|
_handleStoppedSpeakingWhileMuted() {
|
|
if (!this.get('audioAvailable')) {
|
|
return
|
|
}
|
|
|
|
this.set('speakingWhileMuted', false)
|
|
},
|
|
|
|
_handleVideoOn() {
|
|
if (!this.get('videoAvailable')) {
|
|
return
|
|
}
|
|
|
|
this.set('videoEnabled', true)
|
|
},
|
|
|
|
_handleVideoOff() {
|
|
if (!this.get('videoAvailable')) {
|
|
return
|
|
}
|
|
|
|
this.set('videoEnabled', false)
|
|
},
|
|
|
|
_handleLocalScreen(screen) {
|
|
this.set('localScreen', screen)
|
|
},
|
|
|
|
_handleLocalScreenStopped() {
|
|
this.set('localScreen', null)
|
|
},
|
|
|
|
enableAudio() {
|
|
if (!this._webRtc) {
|
|
throw new Error('WebRtc not initialized yet')
|
|
}
|
|
|
|
localStorage.removeItem('audioDisabled_' + this.get('token'))
|
|
if (!this.get('audioAvailable')) {
|
|
return
|
|
}
|
|
|
|
this._webRtc.unmute()
|
|
},
|
|
|
|
disableAudio() {
|
|
if (!this._webRtc) {
|
|
throw new Error('WebRtc not initialized yet')
|
|
}
|
|
|
|
localStorage.setItem('audioDisabled_' + this.get('token'), 'true')
|
|
if (!this.get('audioAvailable')) {
|
|
// Ensure that the audio will be disabled once available.
|
|
this.set('audioEnabled', false)
|
|
|
|
return
|
|
}
|
|
|
|
this._webRtc.mute()
|
|
},
|
|
|
|
enableVideo() {
|
|
if (!this._webRtc) {
|
|
throw new Error('WebRtc not initialized yet')
|
|
}
|
|
|
|
localStorage.removeItem('videoDisabled_' + this.get('token'))
|
|
if (!this.get('videoAvailable')) {
|
|
return
|
|
}
|
|
|
|
this._webRtc.resumeVideo()
|
|
},
|
|
|
|
disableVideo() {
|
|
if (!this._webRtc) {
|
|
throw new Error('WebRtc not initialized yet')
|
|
}
|
|
|
|
localStorage.setItem('videoDisabled_' + this.get('token'), 'true')
|
|
if (!this.get('videoAvailable')) {
|
|
// Ensure that the video will be disabled once available.
|
|
this.set('videoEnabled', false)
|
|
|
|
return
|
|
}
|
|
|
|
this._webRtc.pauseVideo()
|
|
},
|
|
|
|
shareScreen(mode, callback) {
|
|
if (!this._webRtc) {
|
|
throw new Error('WebRtc not initialized yet')
|
|
}
|
|
|
|
this._webRtc.shareScreen(mode, callback)
|
|
},
|
|
|
|
stopSharingScreen() {
|
|
if (!this._webRtc) {
|
|
throw new Error('WebRtc not initialized yet')
|
|
}
|
|
|
|
this._webRtc.stopScreenShare()
|
|
},
|
|
|
|
/**
|
|
* Toggles hand raised mode for the local participant
|
|
*
|
|
* @param {bool} raised true for raised, false for lowered
|
|
*/
|
|
toggleHandRaised(raised) {
|
|
if (!this._webRtc) {
|
|
throw new Error('WebRtc not initialized yet')
|
|
}
|
|
|
|
const raisedHand = {
|
|
state: raised,
|
|
timestamp: Date.now(),
|
|
}
|
|
|
|
this._webRtc.sendToAll('raiseHand', raisedHand)
|
|
|
|
// Set state locally too, as even when sending to all the sender will not
|
|
// receive the message.
|
|
this.set('raisedHand', raisedHand)
|
|
},
|
|
|
|
}
|