Show time left for voice broadcast recordings (#9564)
This commit is contained in:
parent
962e8e0b23
commit
f6347d24ef
22 changed files with 469 additions and 145 deletions
|
@ -31,8 +31,9 @@ import { uploadFile } from "../../../src/ContentMessages";
|
|||
import { IEncryptedFile } from "../../../src/customisations/models/IMediaEventContent";
|
||||
import { createVoiceMessageContent } from "../../../src/utils/createVoiceMessageContent";
|
||||
import {
|
||||
ChunkRecordedPayload,
|
||||
createVoiceBroadcastRecorder,
|
||||
getChunkLength,
|
||||
getMaxBroadcastLength,
|
||||
VoiceBroadcastInfoEventContent,
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
|
@ -43,12 +44,29 @@ import {
|
|||
} from "../../../src/voice-broadcast";
|
||||
import { mkEvent, mkStubRoom, stubClient } from "../../test-utils";
|
||||
import dis from "../../../src/dispatcher/dispatcher";
|
||||
import { VoiceRecording } from "../../../src/audio/VoiceRecording";
|
||||
|
||||
jest.mock("../../../src/voice-broadcast/audio/VoiceBroadcastRecorder", () => ({
|
||||
...jest.requireActual("../../../src/voice-broadcast/audio/VoiceBroadcastRecorder") as object,
|
||||
createVoiceBroadcastRecorder: jest.fn(),
|
||||
}));
|
||||
|
||||
// mock VoiceRecording because it contains all the audio APIs
|
||||
jest.mock("../../../src/audio/VoiceRecording", () => ({
|
||||
VoiceRecording: jest.fn().mockReturnValue({
|
||||
disableMaxLength: jest.fn(),
|
||||
liveData: {
|
||||
onUpdate: jest.fn(),
|
||||
},
|
||||
off: jest.fn(),
|
||||
on: jest.fn(),
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
destroy: jest.fn(),
|
||||
contentType: "audio/ogg",
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock("../../../src/ContentMessages", () => ({
|
||||
uploadFile: jest.fn(),
|
||||
}));
|
||||
|
@ -61,13 +79,13 @@ describe("VoiceBroadcastRecording", () => {
|
|||
const roomId = "!room:example.com";
|
||||
const uploadedUrl = "mxc://example.com/vb";
|
||||
const uploadedFile = { file: true } as unknown as IEncryptedFile;
|
||||
const maxLength = getMaxBroadcastLength();
|
||||
let room: Room;
|
||||
let client: MatrixClient;
|
||||
let infoEvent: MatrixEvent;
|
||||
let voiceBroadcastRecording: VoiceBroadcastRecording;
|
||||
let onStateChanged: (state: VoiceBroadcastInfoState) => void;
|
||||
let voiceBroadcastRecorder: VoiceBroadcastRecorder;
|
||||
let onChunkRecorded: (chunk: ChunkRecordedPayload) => Promise<void>;
|
||||
|
||||
const mkVoiceBroadcastInfoEvent = (content: VoiceBroadcastInfoEventContent) => {
|
||||
return mkEvent({
|
||||
|
@ -83,6 +101,7 @@ describe("VoiceBroadcastRecording", () => {
|
|||
voiceBroadcastRecording = new VoiceBroadcastRecording(infoEvent, client);
|
||||
voiceBroadcastRecording.on(VoiceBroadcastRecordingEvent.StateChanged, onStateChanged);
|
||||
jest.spyOn(voiceBroadcastRecording, "destroy");
|
||||
jest.spyOn(voiceBroadcastRecording, "emit");
|
||||
jest.spyOn(voiceBroadcastRecording, "removeAllListeners");
|
||||
};
|
||||
|
||||
|
@ -111,6 +130,58 @@ describe("VoiceBroadcastRecording", () => {
|
|||
});
|
||||
};
|
||||
|
||||
const itShouldSendAVoiceMessage = (data: number[], size: number, duration: number, sequence: number) => {
|
||||
// events contain milliseconds
|
||||
duration *= 1000;
|
||||
|
||||
it("should send a voice message", () => {
|
||||
expect(uploadFile).toHaveBeenCalledWith(
|
||||
client,
|
||||
roomId,
|
||||
new Blob([new Uint8Array(data)], { type: voiceBroadcastRecorder.contentType }),
|
||||
);
|
||||
|
||||
expect(mocked(client.sendMessage)).toHaveBeenCalledWith(
|
||||
roomId,
|
||||
{
|
||||
body: "Voice message",
|
||||
file: {
|
||||
file: true,
|
||||
},
|
||||
info: {
|
||||
duration,
|
||||
mimetype: "audio/ogg",
|
||||
size,
|
||||
},
|
||||
["m.relates_to"]: {
|
||||
event_id: infoEvent.getId(),
|
||||
rel_type: "m.reference",
|
||||
},
|
||||
msgtype: "m.audio",
|
||||
["org.matrix.msc1767.audio"]: {
|
||||
duration,
|
||||
waveform: undefined,
|
||||
},
|
||||
["org.matrix.msc1767.file"]: {
|
||||
file: {
|
||||
file: true,
|
||||
},
|
||||
mimetype: "audio/ogg",
|
||||
name: "Voice message.ogg",
|
||||
size,
|
||||
url: "mxc://example.com/vb",
|
||||
},
|
||||
["org.matrix.msc1767.text"]: "Voice message",
|
||||
["org.matrix.msc3245.voice"]: {},
|
||||
url: "mxc://example.com/vb",
|
||||
["io.element.voice_broadcast_chunk"]: {
|
||||
sequence,
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
room = mkStubRoom(roomId, "Test Room", client);
|
||||
|
@ -120,23 +191,11 @@ describe("VoiceBroadcastRecording", () => {
|
|||
}
|
||||
});
|
||||
onStateChanged = jest.fn();
|
||||
voiceBroadcastRecorder = {
|
||||
contentType: "audio/ogg",
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
} as unknown as VoiceBroadcastRecorder;
|
||||
voiceBroadcastRecorder = new VoiceBroadcastRecorder(new VoiceRecording(), getChunkLength());
|
||||
jest.spyOn(voiceBroadcastRecorder, "start");
|
||||
jest.spyOn(voiceBroadcastRecorder, "stop");
|
||||
jest.spyOn(voiceBroadcastRecorder, "destroy");
|
||||
mocked(createVoiceBroadcastRecorder).mockReturnValue(voiceBroadcastRecorder);
|
||||
onChunkRecorded = jest.fn();
|
||||
|
||||
mocked(voiceBroadcastRecorder.on).mockImplementation((event: any, listener: any): VoiceBroadcastRecorder => {
|
||||
if (event === VoiceBroadcastRecorderEvent.ChunkRecorded) {
|
||||
onChunkRecorded = listener;
|
||||
}
|
||||
|
||||
return voiceBroadcastRecorder;
|
||||
});
|
||||
|
||||
mocked(uploadFile).mockResolvedValue({
|
||||
url: uploadedUrl,
|
||||
|
@ -240,68 +299,64 @@ describe("VoiceBroadcastRecording", () => {
|
|||
itShouldBeInState(VoiceBroadcastInfoState.Stopped);
|
||||
});
|
||||
|
||||
describe("and a chunk has been recorded", () => {
|
||||
beforeEach(async () => {
|
||||
await onChunkRecorded({
|
||||
buffer: new Uint8Array([1, 2, 3]),
|
||||
length: 23,
|
||||
});
|
||||
describe("and a chunk time update occurs", () => {
|
||||
beforeEach(() => {
|
||||
voiceBroadcastRecorder.emit(VoiceBroadcastRecorderEvent.CurrentChunkLengthUpdated, 10);
|
||||
});
|
||||
|
||||
it("should send a voice message", () => {
|
||||
expect(uploadFile).toHaveBeenCalledWith(
|
||||
client,
|
||||
roomId,
|
||||
new Blob([new Uint8Array([1, 2, 3])], { type: voiceBroadcastRecorder.contentType }),
|
||||
it("should update time left", () => {
|
||||
expect(voiceBroadcastRecording.getTimeLeft()).toBe(maxLength - 10);
|
||||
expect(voiceBroadcastRecording.emit).toHaveBeenCalledWith(
|
||||
VoiceBroadcastRecordingEvent.TimeLeftChanged,
|
||||
maxLength - 10,
|
||||
);
|
||||
});
|
||||
|
||||
expect(mocked(client.sendMessage)).toHaveBeenCalledWith(
|
||||
roomId,
|
||||
describe("and a chunk time update occurs, that would increase time left", () => {
|
||||
beforeEach(() => {
|
||||
mocked(voiceBroadcastRecording.emit).mockClear();
|
||||
voiceBroadcastRecorder.emit(VoiceBroadcastRecorderEvent.CurrentChunkLengthUpdated, 5);
|
||||
});
|
||||
|
||||
it("should not change time left", () => {
|
||||
expect(voiceBroadcastRecording.getTimeLeft()).toBe(maxLength - 10);
|
||||
expect(voiceBroadcastRecording.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and a chunk has been recorded", () => {
|
||||
beforeEach(async () => {
|
||||
voiceBroadcastRecorder.emit(
|
||||
VoiceBroadcastRecorderEvent.ChunkRecorded,
|
||||
{
|
||||
body: "Voice message",
|
||||
file: {
|
||||
file: true,
|
||||
},
|
||||
info: {
|
||||
duration: 23000,
|
||||
mimetype: "audio/ogg",
|
||||
size: 3,
|
||||
},
|
||||
["m.relates_to"]: {
|
||||
event_id: infoEvent.getId(),
|
||||
rel_type: "m.reference",
|
||||
},
|
||||
msgtype: "m.audio",
|
||||
["org.matrix.msc1767.audio"]: {
|
||||
duration: 23000,
|
||||
waveform: undefined,
|
||||
},
|
||||
["org.matrix.msc1767.file"]: {
|
||||
file: {
|
||||
file: true,
|
||||
},
|
||||
mimetype: "audio/ogg",
|
||||
name: "Voice message.ogg",
|
||||
size: 3,
|
||||
url: "mxc://example.com/vb",
|
||||
},
|
||||
["org.matrix.msc1767.text"]: "Voice message",
|
||||
["org.matrix.msc3245.voice"]: {},
|
||||
url: "mxc://example.com/vb",
|
||||
["io.element.voice_broadcast_chunk"]: {
|
||||
sequence: 1,
|
||||
},
|
||||
buffer: new Uint8Array([1, 2, 3]),
|
||||
length: 23,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
itShouldSendAVoiceMessage([1, 2, 3], 3, 23, 1);
|
||||
|
||||
describe("and another chunk has been recorded, that exceeds the max time", () => {
|
||||
beforeEach(() => {
|
||||
mocked(voiceBroadcastRecorder.stop).mockResolvedValue({
|
||||
buffer: new Uint8Array([23, 24, 25]),
|
||||
length: getMaxBroadcastLength(),
|
||||
});
|
||||
voiceBroadcastRecorder.emit(
|
||||
VoiceBroadcastRecorderEvent.CurrentChunkLengthUpdated,
|
||||
getMaxBroadcastLength(),
|
||||
);
|
||||
});
|
||||
|
||||
itShouldBeInState(VoiceBroadcastInfoState.Stopped);
|
||||
itShouldSendAVoiceMessage([23, 24, 25], 3, getMaxBroadcastLength(), 2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and calling stop", () => {
|
||||
beforeEach(async () => {
|
||||
await onChunkRecorded({
|
||||
buffer: new Uint8Array([1, 2, 3]),
|
||||
length: 23,
|
||||
});
|
||||
mocked(voiceBroadcastRecorder.stop).mockResolvedValue({
|
||||
buffer: new Uint8Array([4, 5, 6]),
|
||||
length: 42,
|
||||
|
@ -309,52 +364,7 @@ describe("VoiceBroadcastRecording", () => {
|
|||
await voiceBroadcastRecording.stop();
|
||||
});
|
||||
|
||||
it("should send the last chunk", () => {
|
||||
expect(uploadFile).toHaveBeenCalledWith(
|
||||
client,
|
||||
roomId,
|
||||
new Blob([new Uint8Array([4, 5, 6])], { type: voiceBroadcastRecorder.contentType }),
|
||||
);
|
||||
|
||||
expect(mocked(client.sendMessage)).toHaveBeenCalledWith(
|
||||
roomId,
|
||||
{
|
||||
body: "Voice message",
|
||||
file: {
|
||||
file: true,
|
||||
},
|
||||
info: {
|
||||
duration: 42000,
|
||||
mimetype: "audio/ogg",
|
||||
size: 3,
|
||||
},
|
||||
["m.relates_to"]: {
|
||||
event_id: infoEvent.getId(),
|
||||
rel_type: "m.reference",
|
||||
},
|
||||
msgtype: "m.audio",
|
||||
["org.matrix.msc1767.audio"]: {
|
||||
duration: 42000,
|
||||
waveform: undefined,
|
||||
},
|
||||
["org.matrix.msc1767.file"]: {
|
||||
file: {
|
||||
file: true,
|
||||
},
|
||||
mimetype: "audio/ogg",
|
||||
name: "Voice message.ogg",
|
||||
size: 3,
|
||||
url: "mxc://example.com/vb",
|
||||
},
|
||||
["org.matrix.msc1767.text"]: "Voice message",
|
||||
["org.matrix.msc3245.voice"]: {},
|
||||
url: "mxc://example.com/vb",
|
||||
["io.element.voice_broadcast_chunk"]: {
|
||||
sequence: 2,
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
itShouldSendAVoiceMessage([4, 5, 6], 3, 42, 1);
|
||||
});
|
||||
|
||||
describe.each([
|
||||
|
@ -384,10 +394,7 @@ describe("VoiceBroadcastRecording", () => {
|
|||
|
||||
it("should stop the recorder and remove all listeners", () => {
|
||||
expect(mocked(voiceBroadcastRecorder.stop)).toHaveBeenCalled();
|
||||
expect(mocked(voiceBroadcastRecorder.off)).toHaveBeenCalledWith(
|
||||
VoiceBroadcastRecorderEvent.ChunkRecorded,
|
||||
onChunkRecorded,
|
||||
);
|
||||
expect(mocked(voiceBroadcastRecorder.destroy)).toHaveBeenCalled();
|
||||
expect(mocked(voiceBroadcastRecording.removeAllListeners)).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue