diff --git a/src/audio/VoiceRecording.ts b/src/audio/VoiceRecording.ts index e429bd2035f..29217d0b0c8 100644 --- a/src/audio/VoiceRecording.ts +++ b/src/audio/VoiceRecording.ts @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as Recorder from 'opus-recorder'; +// @ts-ignore +import Recorder from 'opus-recorder/dist/recorder.min.js'; import encoderPath from 'opus-recorder/dist/encoderWorker.min.js'; import { SimpleObservable } from "matrix-widget-api"; import EventEmitter from "events"; diff --git a/test/audio/VoiceRecording-test.ts b/test/audio/VoiceRecording-test.ts index ac4f52eabe2..dabb30f33ad 100644 --- a/test/audio/VoiceRecording-test.ts +++ b/test/audio/VoiceRecording-test.ts @@ -14,7 +14,24 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { VoiceRecording } from "../../src/audio/VoiceRecording"; +import { mocked } from 'jest-mock'; +// @ts-ignore +import Recorder from 'opus-recorder/dist/recorder.min.js'; + +import { VoiceRecording, voiceRecorderOptions, higQualityRecorderOptions } from "../../src/audio/VoiceRecording"; +import { createAudioContext } from '../..//src/audio/compat'; +import MediaDeviceHandler from "../../src/MediaDeviceHandler"; + +jest.mock('opus-recorder/dist/recorder.min.js'); +const RecorderMock = mocked(Recorder); + +jest.mock('../../src/audio/compat', () => ({ + createAudioContext: jest.fn(), +})); +const createAudioContextMock = mocked(createAudioContext); + +jest.mock("../../src/MediaDeviceHandler"); +const MediaDeviceHandlerMock = mocked(MediaDeviceHandler); /** * The tests here are heavily using access to private props. @@ -43,6 +60,7 @@ describe("VoiceRecording", () => { // @ts-ignore recording.observable = { update: jest.fn(), + close: jest.fn(), }; jest.spyOn(recording, "stop").mockImplementation(); recorderSecondsSpy = jest.spyOn(recording, "recorderSeconds", "get"); @@ -52,6 +70,56 @@ describe("VoiceRecording", () => { jest.resetAllMocks(); }); + describe("when starting a recording", () => { + beforeEach(() => { + const mockAudioContext = { + createMediaStreamSource: jest.fn().mockReturnValue({ + connect: jest.fn(), + disconnect: jest.fn(), + }), + createScriptProcessor: jest.fn().mockReturnValue({ + connect: jest.fn(), + disconnect: jest.fn(), + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + }), + destination: {}, + close: jest.fn(), + }; + createAudioContextMock.mockReturnValue(mockAudioContext as unknown as AudioContext); + }); + + afterEach(async () => { + await recording.stop(); + }); + + it("should record high-quality audio if voice processing is disabled", async () => { + MediaDeviceHandlerMock.getAudioNoiseSuppression.mockReturnValue(false); + await recording.start(); + + expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith(expect.objectContaining({ + audio: expect.objectContaining({ noiseSuppression: { ideal: false } }), + })); + expect(RecorderMock).toHaveBeenCalledWith(expect.objectContaining({ + encoderBitRate: higQualityRecorderOptions.bitrate, + encoderApplication: higQualityRecorderOptions.encoderApplication, + })); + }); + + it("should record normal-quality voice if voice processing is enabled", async () => { + MediaDeviceHandlerMock.getAudioNoiseSuppression.mockReturnValue(true); + await recording.start(); + + expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith(expect.objectContaining({ + audio: expect.objectContaining({ noiseSuppression: { ideal: true } }), + })); + expect(RecorderMock).toHaveBeenCalledWith(expect.objectContaining({ + encoderBitRate: voiceRecorderOptions.bitrate, + encoderApplication: voiceRecorderOptions.encoderApplication, + })); + }); + }); + describe("when recording", () => { beforeEach(() => { // @ts-ignore