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.

AudioMixer.ts 2.9KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import { getLogger } from '@jitsi/logger';
  2. import { createAudioContext } from './WebAudioUtils';
  3. const logger = getLogger('modules/webaudio/AudioMixer');
  4. /**
  5. * The AudioMixer, as the name implies, mixes a number of MediaStreams containing audio tracks into a single
  6. * MediaStream.
  7. */
  8. export default class AudioMixer {
  9. private _started: boolean;
  10. private _streamsToMix: MediaStream[];
  11. private _streamMSSArray: MediaStreamAudioSourceNode[];
  12. private _audioContext?: AudioContext;
  13. private _mixedMSD?: MediaStreamAudioDestinationNode;
  14. /**
  15. * Create AudioMixer instance.
  16. */
  17. constructor() {
  18. this._started = false;
  19. this._streamsToMix = [];
  20. this._streamMSSArray = [];
  21. }
  22. /**
  23. * Add audio MediaStream to be mixed, if the stream doesn't contain any audio tracks it will be ignored.
  24. *
  25. * @param {MediaStream} stream - MediaStream to be mixed.
  26. */
  27. addMediaStream(stream: MediaStream): void {
  28. if (!stream.getAudioTracks() || stream.getAudioTracks().length === 0) {
  29. logger.warn('Added MediaStream doesn\'t contain audio tracks.');
  30. }
  31. this._streamsToMix.push(stream);
  32. }
  33. /**
  34. * At this point a WebAudio ChannelMergerNode is created and and the two associated MediaStreams are connected to
  35. * it; the resulting mixed MediaStream is returned.
  36. *
  37. * @returns {MediaStream | null} - MediaStream containing added streams mixed together, or null if no MediaStream
  38. * is added.
  39. */
  40. start(): MediaStream | null {
  41. // If the mixer was already started just return the existing mixed stream.
  42. if (this._started && this._mixedMSD) {
  43. return this._mixedMSD.stream;
  44. }
  45. this._audioContext = createAudioContext();
  46. if (!this._streamsToMix.length) {
  47. logger.warn('No MediaStream\'s added to AudioMixer, nothing will happen.');
  48. return null;
  49. }
  50. this._started = true;
  51. this._mixedMSD = this._audioContext!.createMediaStreamDestination();
  52. for (const stream of this._streamsToMix) {
  53. const streamMSS = this._audioContext!.createMediaStreamSource(stream);
  54. streamMSS.connect(this._mixedMSD);
  55. // Maintain a list of MediaStreamAudioSourceNode so we can disconnect them on reset.
  56. this._streamMSSArray.push(streamMSS);
  57. }
  58. return this._mixedMSD.stream;
  59. }
  60. /**
  61. * Disconnect MediaStreamAudioSourceNode and clear references.
  62. *
  63. * @returns {void}
  64. */
  65. reset(): void {
  66. this._started = false;
  67. this._streamsToMix = [];
  68. // Clean up created MediaStreamAudioSourceNode.
  69. for (const streamMSS of this._streamMSSArray) {
  70. streamMSS.disconnect();
  71. }
  72. this._streamMSSArray = [];
  73. if (this._audioContext) {
  74. this._audioContext = undefined;
  75. }
  76. this._mixedMSD = undefined;
  77. }
  78. }