Created
January 7, 2020 23:56
-
-
Save kwindla/f273f9afce17cb1715288a208c5d1853 to your computer and use it in GitHub Desktop.
merge audio tracks partial sample
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
setupAudioContext () { | |
this.audioContext = new window.AudioContext(); | |
// create an output stream node. have to use the constructor form | |
// because we want to make the stream mono | |
this.audioOut = new MediaStreamAudioDestinationNode(this.audioContext, | |
{ channelCount: 1 }); | |
// connecting a single gain node to the audioOut node suffices for | |
// the pipeline to work. a MediaStreamDestination with nothing | |
// connected to it seems to hang the MediaRecorder | |
this.localGain = this.audioContext.createGain(); | |
this.localGain.connect(this.audioOut); | |
} | |
startRecording () { | |
this.setupAudioContext(); | |
this.localAudioInfo = { streamId: null, gainNode: null }; | |
this.remoteAudioInfo = {}; // [participantId]: {streamId, gainNode} | |
let audioTrack = this.audioOut.stream.getAudioTracks()[0]; | |
let mediaStream = new MediaStream([ | |
audioTrack | |
]); | |
// setup MediaRecorder, etc ... | |
} | |
// set up an audio gain node so we can control stream volume | |
createAudioGainNode (stream, muted=false, gainValue=1.0) { | |
try { | |
if (stream.getAudioTracks().length === 0) { | |
return null; | |
} | |
let source = this.audioContext.createMediaStreamSource(stream), | |
gainNode = this.audioContext.createGain(); | |
gainNode._dailyInitialGainValue = gainValue; | |
gainNode._dailySourceNode = source; | |
gainNode.gain.value = muted ? 0 : gainValue; | |
source.connect(gainNode); | |
gainNode.connect(this.audioOut); | |
return gainNode; | |
} catch (e) { | |
console.error('error setting up gain node', e); | |
} | |
} | |
removeAudioGainNode (gainNode) { | |
if (gainNode) { | |
gainNode.disconnect(this.audioOut); | |
// this cleanup may not be necessary, but given that we don't | |
// have much control over object lifecycle let's be extra careful | |
gainNode._dailySourceNode.disconnect(gainNode); | |
gainNode._dailySourceNode = null; | |
} | |
} | |
updateLocalAudioStream (localParticipant) { | |
if (!(localParticipant && localParticipant.stream && | |
localParticipant.stream.stream && | |
localParticipant.stream.id)) { | |
return; | |
} | |
if (this.localAudioInfo.streamId !== localParticipant.stream.id) { | |
console.log('recorder updating local audio stream'); | |
// create/update local audio track that's feeding into the | |
// recording stream. we need to clone the stream so we can | |
// control whether the local audio track is enabled (in | |
// audioconf mode the local audio track is disabled so that we | |
// don't send webrtc audio to Daily peers) | |
let stream = localParticipant.stream.stream.clone(), | |
audioTrack = stream.getAudioTracks()[0], | |
isMuted = isUserMuted(localParticipant.public.audioState); | |
if (audioTrack) { | |
audioTrack.enabled = true; | |
this.localAudioInfo.streamId = localParticipant.stream.id; | |
this.removeAudioGainNode(this.localAudioInfo.gainNode); | |
this.localAudioInfo.gainNode = this.createAudioGainNode(stream,isMuted); | |
} | |
} else { | |
// mute/unmute local audio stream if necessary | |
if (this.localAudioInfo.gainNode) { | |
let localMicMuted = isUserMuted(localParticipant.public.audioState), | |
gainNodeMuted = this.localAudioInfo.gainNode.gain.value === 0; | |
if (localMicMuted && !gainNodeMuted) { | |
this.localAudioInfo.gainNode.gain.value = 0; | |
} else if (!localMicMuted && gainNodeMuted) { | |
this.localAudioInfo.gainNode.gain.value = | |
this.localAudioInfo.gainNode._dailyInitialGainValue; | |
} | |
} | |
} | |
} | |
updateRemoteAudioStreams (camParticipants) { | |
// loop through our remote audio streams and remove any for which | |
// we don't have a camParticipants entry | |
Object.keys(this.remoteAudioInfo).forEach((participantId) => { | |
if (!find(camParticipants, (p) => p.public.id === participantId)) { | |
console.log('recording removing audio stream for', participantId); | |
this.removeAudioStream(participantId); | |
} | |
}); | |
// loop through camParticipants list. add/update track for any | |
// with an audio track. we let muting be handled on the remote end. | |
camParticipants.forEach((p) => { | |
if (p.public && p.audioTrack) { | |
this.updateAudioStream(p.public.id, p.audioTrack.pendingTrack); | |
} | |
}); | |
} | |
removeAudioStream (participantId) { | |
let streamInfo = this.remoteAudioInfo[participantId]; | |
if (streamInfo) { | |
if (streamInfo.gainNode) { | |
this.removeAudioGainNode(streamInfo.gainNode); | |
} | |
delete this.remoteAudioInfo[participantId]; | |
} | |
} | |
updateAudioStream (participantId, track, gainValue=1.0) { | |
if (!(track && participantId)) { | |
return; | |
} | |
let currentInfo = this.remoteAudioInfo[participantId]; | |
// make a new info record if we don't have one | |
if (!currentInfo) { | |
currentInfo = { trackId: null, gainNode: null }; | |
this.remoteAudioInfo[participantId] = currentInfo; | |
} | |
// if stream id doesn't match, remove current gain node and make a new one | |
if (track.id !== currentInfo.trackId) { | |
console.log("recording replacing audio stream for", participantId); | |
this.removeAudioGainNode(currentInfo.gainNode); | |
currentInfo.trackId = track.id; | |
currentInfo.gainNode = this.createAudioGainNode( | |
new MediaStream([track]), false, gainValue | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment