summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDaniel Calviño Sánchez <danxuliu@gmail.com>2024-06-12 09:03:08 +0200
committerDaniel Calviño Sánchez <danxuliu@gmail.com>2024-06-12 18:24:15 +0200
commit87853adab8688f49e9316f092c6744ade5b0e191 (patch)
treee5362a9b747c42492f29daebe20b1c8561d3e656 /src
parent1f2ba38538949ff5ada402ecc3cd106b525028a6 (diff)
fix: Mix audio of call participants in Safari
Safari does not allow autoplaying on audio elements in inactive tabs, so if the user switched to another tab during a call the participants that join while the tab is inactive will not be heard until the user switches back to the Talk tab. To work around that now an audio element is created when the local participant joins the call and then the audio of all participants is mixed and played on that audio element. Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/utils/webrtc/CallParticipantsAudioPlayer.js77
-rw-r--r--src/utils/webrtc/index.js4
2 files changed, 74 insertions, 7 deletions
diff --git a/src/utils/webrtc/CallParticipantsAudioPlayer.js b/src/utils/webrtc/CallParticipantsAudioPlayer.js
index 4625e5a52..4c366e900 100644
--- a/src/utils/webrtc/CallParticipantsAudioPlayer.js
+++ b/src/utils/webrtc/CallParticipantsAudioPlayer.js
@@ -14,18 +14,31 @@ import attachMediaStream from '../attachmediastream.js'
* "audioAvailable"; screen share audio, on the other hand, is always treated as
* unmuted.
*
- * The audio or screen audio of each participant is played on its own audio
- * element.
+ * By default, the audio or screen audio of each participant is played on its
+ * own audio element. Alternatively, the audio of all participants can be mixed
+ * and played by a single element created when the player is created by setting
+ * "mixAudio = true" in the constructor.
*
* Once the player is no longer needed "destroy()" must be called to stop
* tracking the participants and playing audio.
*
* @param {object} callParticipantCollection the CallParticipantCollection.
+ * @param {boolean} mixAudio true to mix and play all audio in a single audio
+ * element, false to play each audio on its own audio element.
*/
-export default function CallParticipantsAudioPlayer(callParticipantCollection) {
+export default function CallParticipantsAudioPlayer(callParticipantCollection, mixAudio = false) {
this._callParticipantCollection = callParticipantCollection
- this._audioElements = new Map()
+ this._mixAudio = mixAudio
+
+ if (this._mixAudio) {
+ this._audioContext = new (window.AudioContext || window.webkitAudioContext)()
+ this._audioDestination = this._audioContext.createMediaStreamDestination()
+ this._audioElement = attachMediaStream(this._audioDestination.stream, null, { audio: true })
+ this._audioNodes = new Map()
+ } else {
+ this._audioElements = new Map()
+ }
this._handleCallParticipantAddedBound = this._handleCallParticipantAdded.bind(this)
this._handleCallParticipantRemovedBound = this._handleCallParticipantRemoved.bind(this)
@@ -50,6 +63,11 @@ CallParticipantsAudioPlayer.prototype = {
this._callParticipantCollection.callParticipantModels.value.forEach(callParticipantModel => {
this._handleCallParticipantRemovedBound(this._callParticipantCollection, callParticipantModel)
})
+
+ if (this._mixAudio) {
+ this._audioElement.srcObject = null
+ this._audioContext.close()
+ }
},
_handleCallParticipantAdded(callParticipantCollection, callParticipantModel) {
@@ -73,12 +91,42 @@ CallParticipantsAudioPlayer.prototype = {
_handleStreamChanged(callParticipantModel, stream) {
const id = callParticipantModel.get('peerId') + '-stream'
const mute = !callParticipantModel.get('audioAvailable')
- this._setAudioElement(id, stream, mute)
+ if (this._mixAudio) {
+ this._setAudioNode(id, stream, mute)
+ } else {
+ this._setAudioElement(id, stream, mute)
+ }
},
_handleScreenChanged(callParticipantModel, screen) {
const id = callParticipantModel.get('peerId') + '-screen'
- this._setAudioElement(id, screen)
+ if (this._mixAudio) {
+ this._setAudioNode(id, screen)
+ } else {
+ this._setAudioElement(id, screen)
+ }
+ },
+
+ _setAudioNode(id, stream, mute = false) {
+ const audioNode = this._audioNodes.get(id)
+ if (audioNode) {
+ if (audioNode.connected) {
+ audioNode.audioSource.disconnect(this._audioDestination)
+ }
+
+ this._audioNodes.delete(id)
+ }
+
+ if (!stream) {
+ return
+ }
+
+ const audioSource = this._audioContext.createMediaStreamSource(stream)
+ if (!mute) {
+ audioSource.connect(this._audioDestination)
+ }
+
+ this._audioNodes.set(id, { audioSource, connected: !mute })
},
_setAudioElement(id, stream, mute = false) {
@@ -102,6 +150,23 @@ CallParticipantsAudioPlayer.prototype = {
},
_handleAudioAvailableChanged(callParticipantModel, audioAvailable) {
+ if (this._mixAudio) {
+ const audioNode = this._audioNodes.get(callParticipantModel.get('peerId') + '-stream')
+ if (!audioNode) {
+ return
+ }
+
+ if (audioAvailable && !audioNode.connected) {
+ audioNode.audioSource.connect(this._audioDestination)
+ audioNode.connected = true
+ } else if (!audioAvailable && audioNode.connected) {
+ audioNode.audioSource.disconnect(this._audioDestination)
+ audioNode.connected = false
+ }
+
+ return
+ }
+
const audioElement = this._audioElements.get(callParticipantModel.get('peerId') + '-stream')
if (!audioElement) {
return
diff --git a/src/utils/webrtc/index.js b/src/utils/webrtc/index.js
index dd3365b5f..579fea6db 100644
--- a/src/utils/webrtc/index.js
+++ b/src/utils/webrtc/index.js
@@ -21,6 +21,7 @@ import { PARTICIPANT, PRIVACY, VIRTUAL_BACKGROUND } from '../../constants.js'
import BrowserStorage from '../../services/BrowserStorage.js'
import { fetchSignalingSettings } from '../../services/signalingService.js'
import store from '../../store/index.js'
+import { isSafari } from '../browserCheck.js'
import CancelableRequest from '../cancelableRequest.js'
import Signaling from '../signaling.js'
import SignalingTypingHandler from '../SignalingTypingHandler.js'
@@ -215,7 +216,8 @@ async function signalingJoinCall(token, flags, silent, recordingConsent) {
callAnalyzer = new CallAnalyzer(localMediaModel, null, callParticipantCollection)
}
- callParticipantsAudioPlayer = new CallParticipantsAudioPlayer(callParticipantCollection)
+ const mixAudio = isSafari
+ callParticipantsAudioPlayer = new CallParticipantsAudioPlayer(callParticipantCollection, mixAudio)
const _signaling = signaling