Display voice broadcast total length (#9517)
This commit is contained in:
parent
9b644844da
commit
66c20a0798
10 changed files with 443 additions and 94 deletions
|
@ -16,17 +16,19 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { render, RenderResult } from "@testing-library/react";
|
||||
import { act, render, RenderResult } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import {
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastPlayback,
|
||||
VoiceBroadcastPlaybackBody,
|
||||
VoiceBroadcastPlaybackEvent,
|
||||
VoiceBroadcastPlaybackState,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { mkEvent, stubClient } from "../../../test-utils";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../utils/test-utils";
|
||||
|
||||
// mock RoomAvatar, because it is doing too much fancy stuff
|
||||
jest.mock("../../../../src/components/views/avatars/RoomAvatar", () => ({
|
||||
|
@ -46,19 +48,19 @@ describe("VoiceBroadcastPlaybackBody", () => {
|
|||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
infoEvent = mkEvent({
|
||||
event: true,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
content: {},
|
||||
room: roomId,
|
||||
user: userId,
|
||||
});
|
||||
infoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
userId,
|
||||
client.getDeviceId(),
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
playback = new VoiceBroadcastPlayback(infoEvent, client);
|
||||
jest.spyOn(playback, "toggle");
|
||||
jest.spyOn(playback, "toggle").mockImplementation(() => Promise.resolve());
|
||||
jest.spyOn(playback, "getState");
|
||||
jest.spyOn(playback, "getLength").mockReturnValue((23 * 60 + 42) * 1000); // 23:42
|
||||
});
|
||||
|
||||
describe("when rendering a buffering voice broadcast", () => {
|
||||
|
@ -72,7 +74,7 @@ describe("VoiceBroadcastPlaybackBody", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe(`when rendering a ${VoiceBroadcastPlaybackState.Stopped} broadcast`, () => {
|
||||
describe(`when rendering a stopped broadcast`, () => {
|
||||
beforeEach(() => {
|
||||
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Stopped);
|
||||
renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />);
|
||||
|
@ -87,6 +89,18 @@ describe("VoiceBroadcastPlaybackBody", () => {
|
|||
expect(playback.toggle).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and the length updated", () => {
|
||||
beforeEach(() => {
|
||||
act(() => {
|
||||
playback.emit(VoiceBroadcastPlaybackEvent.LengthChanged, 42000); // 00:42
|
||||
});
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([
|
||||
|
|
|
@ -64,6 +64,15 @@ exports[`VoiceBroadcastPlaybackBody when rendering a 0 broadcast should render a
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_timerow"
|
||||
>
|
||||
<span
|
||||
class="mx_Clock"
|
||||
>
|
||||
23:42
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -132,6 +141,15 @@ exports[`VoiceBroadcastPlaybackBody when rendering a 1 broadcast should render a
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_timerow"
|
||||
>
|
||||
<span
|
||||
class="mx_Clock"
|
||||
>
|
||||
23:42
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -201,6 +219,92 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_timerow"
|
||||
>
|
||||
<span
|
||||
class="mx_Clock"
|
||||
>
|
||||
23:42
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a stopped broadcast and the length updated should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="play voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_timerow"
|
||||
>
|
||||
<span
|
||||
class="mx_Clock"
|
||||
>
|
||||
00:42
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { EventType, MatrixClient, MatrixEvent, MsgType, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
import { EventType, MatrixClient, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
import { Relations } from "matrix-js-sdk/src/models/relations";
|
||||
|
||||
import { Playback, PlaybackState } from "../../../src/audio/Playback";
|
||||
|
@ -24,7 +24,6 @@ import { getReferenceRelationsForEvent } from "../../../src/events";
|
|||
import { RelationsHelperEvent } from "../../../src/events/RelationsHelper";
|
||||
import { MediaEventHelper } from "../../../src/utils/MediaEventHelper";
|
||||
import {
|
||||
VoiceBroadcastChunkEventType,
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastPlayback,
|
||||
|
@ -33,6 +32,7 @@ import {
|
|||
} from "../../../src/voice-broadcast";
|
||||
import { mkEvent, stubClient } from "../../test-utils";
|
||||
import { createTestPlayback } from "../../test-utils/audio";
|
||||
import { mkVoiceBroadcastChunkEvent } from "../utils/test-utils";
|
||||
|
||||
jest.mock("../../../src/events/getReferenceRelationsForEvent", () => ({
|
||||
getReferenceRelationsForEvent: jest.fn(),
|
||||
|
@ -49,19 +49,15 @@ describe("VoiceBroadcastPlayback", () => {
|
|||
let infoEvent: MatrixEvent;
|
||||
let playback: VoiceBroadcastPlayback;
|
||||
let onStateChanged: (state: VoiceBroadcastPlaybackState) => void;
|
||||
let chunk0Event: MatrixEvent;
|
||||
let chunk1Event: MatrixEvent;
|
||||
let chunk2Event: MatrixEvent;
|
||||
let chunk3Event: MatrixEvent;
|
||||
const chunk0Data = new ArrayBuffer(1);
|
||||
const chunk1Data = new ArrayBuffer(2);
|
||||
const chunk2Data = new ArrayBuffer(3);
|
||||
const chunk3Data = new ArrayBuffer(3);
|
||||
let chunk0Helper: MediaEventHelper;
|
||||
let chunk1Helper: MediaEventHelper;
|
||||
let chunk2Helper: MediaEventHelper;
|
||||
let chunk3Helper: MediaEventHelper;
|
||||
let chunk0Playback: Playback;
|
||||
let chunk1Playback: Playback;
|
||||
let chunk2Playback: Playback;
|
||||
let chunk3Playback: Playback;
|
||||
|
@ -96,21 +92,6 @@ describe("VoiceBroadcastPlayback", () => {
|
|||
});
|
||||
};
|
||||
|
||||
const mkChunkEvent = (sequence: number) => {
|
||||
return mkEvent({
|
||||
event: true,
|
||||
user: client.getUserId(),
|
||||
room: roomId,
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
msgtype: MsgType.Audio,
|
||||
[VoiceBroadcastChunkEventType]: {
|
||||
sequence,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const mkChunkHelper = (data: ArrayBuffer): MediaEventHelper => {
|
||||
return {
|
||||
sourceBlob: {
|
||||
|
@ -152,25 +133,20 @@ describe("VoiceBroadcastPlayback", () => {
|
|||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
|
||||
// crap event to test 0 as first sequence number
|
||||
chunk0Event = mkChunkEvent(0);
|
||||
chunk1Event = mkChunkEvent(1);
|
||||
chunk2Event = mkChunkEvent(2);
|
||||
chunk3Event = mkChunkEvent(3);
|
||||
chunk1Event = mkVoiceBroadcastChunkEvent(userId, roomId, 23, 1);
|
||||
chunk2Event = mkVoiceBroadcastChunkEvent(userId, roomId, 23, 2);
|
||||
chunk3Event = mkVoiceBroadcastChunkEvent(userId, roomId, 23, 3);
|
||||
|
||||
chunk0Helper = mkChunkHelper(chunk0Data);
|
||||
chunk1Helper = mkChunkHelper(chunk1Data);
|
||||
chunk2Helper = mkChunkHelper(chunk2Data);
|
||||
chunk3Helper = mkChunkHelper(chunk3Data);
|
||||
|
||||
chunk0Playback = createTestPlayback();
|
||||
chunk1Playback = createTestPlayback();
|
||||
chunk2Playback = createTestPlayback();
|
||||
chunk3Playback = createTestPlayback();
|
||||
|
||||
jest.spyOn(PlaybackManager.instance, "createPlaybackInstance").mockImplementation(
|
||||
(buffer: ArrayBuffer, _waveForm?: number[]) => {
|
||||
if (buffer === chunk0Data) return chunk0Playback;
|
||||
if (buffer === chunk1Data) return chunk1Playback;
|
||||
if (buffer === chunk2Data) return chunk2Playback;
|
||||
if (buffer === chunk3Data) return chunk3Playback;
|
||||
|
@ -178,7 +154,6 @@ describe("VoiceBroadcastPlayback", () => {
|
|||
);
|
||||
|
||||
mocked(MediaEventHelper).mockImplementation((event: MatrixEvent) => {
|
||||
if (event === chunk0Event) return chunk0Helper;
|
||||
if (event === chunk1Event) return chunk1Helper;
|
||||
if (event === chunk2Event) return chunk2Helper;
|
||||
if (event === chunk3Event) return chunk3Helper;
|
||||
|
@ -240,7 +215,7 @@ describe("VoiceBroadcastPlayback", () => {
|
|||
beforeEach(() => {
|
||||
infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Resumed);
|
||||
playback = mkPlayback();
|
||||
setUpChunkEvents([chunk2Event, chunk0Event, chunk1Event]);
|
||||
setUpChunkEvents([chunk2Event, chunk1Event]);
|
||||
});
|
||||
|
||||
describe("and calling start", () => {
|
||||
|
@ -282,20 +257,9 @@ describe("VoiceBroadcastPlayback", () => {
|
|||
playback = mkPlayback();
|
||||
});
|
||||
|
||||
describe("and there is only a 0 sequence event", () => {
|
||||
beforeEach(() => {
|
||||
setUpChunkEvents([chunk0Event]);
|
||||
});
|
||||
|
||||
describe("and calling start", () => {
|
||||
startPlayback();
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Buffering);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and there are some chunks", () => {
|
||||
beforeEach(() => {
|
||||
setUpChunkEvents([chunk2Event, chunk0Event, chunk1Event]);
|
||||
setUpChunkEvents([chunk2Event, chunk1Event]);
|
||||
});
|
||||
|
||||
it("should expose the info event", () => {
|
||||
|
@ -337,6 +301,21 @@ describe("VoiceBroadcastPlayback", () => {
|
|||
stopPlayback();
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Stopped);
|
||||
});
|
||||
|
||||
describe("and calling destroy", () => {
|
||||
beforeEach(() => {
|
||||
playback.destroy();
|
||||
});
|
||||
|
||||
it("should call removeAllListeners", () => {
|
||||
expect(playback.removeAllListeners).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call destroy on the playbacks", () => {
|
||||
expect(chunk1Playback.destroy).toHaveBeenCalled();
|
||||
expect(chunk2Playback.destroy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and calling toggle for the first time", () => {
|
||||
|
@ -378,16 +357,6 @@ describe("VoiceBroadcastPlayback", () => {
|
|||
itShouldEmitAStateChangedEvent(VoiceBroadcastPlaybackState.Playing);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and calling destroy", () => {
|
||||
beforeEach(() => {
|
||||
playback.destroy();
|
||||
});
|
||||
|
||||
it("should call removeAllListeners", () => {
|
||||
expect(playback.removeAllListeners).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
99
test/voice-broadcast/utils/VoiceBroadcastChunkEvents-test.ts
Normal file
99
test/voice-broadcast/utils/VoiceBroadcastChunkEvents-test.ts
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { VoiceBroadcastChunkEvents } from "../../../src/voice-broadcast/utils/VoiceBroadcastChunkEvents";
|
||||
import { mkVoiceBroadcastChunkEvent } from "./test-utils";
|
||||
|
||||
describe("VoiceBroadcastChunkEvents", () => {
|
||||
const userId = "@user:example.com";
|
||||
const roomId = "!room:example.com";
|
||||
let eventSeq1Time1: MatrixEvent;
|
||||
let eventSeq2Time4: MatrixEvent;
|
||||
let eventSeq3Time2: MatrixEvent;
|
||||
let eventSeq4Time1: MatrixEvent;
|
||||
let eventSeqUTime3: MatrixEvent;
|
||||
let eventSeq2Time4Dup: MatrixEvent;
|
||||
let chunkEvents: VoiceBroadcastChunkEvents;
|
||||
|
||||
beforeEach(() => {
|
||||
eventSeq1Time1 = mkVoiceBroadcastChunkEvent(userId, roomId, 7, 1, 1);
|
||||
eventSeq2Time4 = mkVoiceBroadcastChunkEvent(userId, roomId, 23, 2, 4);
|
||||
eventSeq2Time4Dup = mkVoiceBroadcastChunkEvent(userId, roomId, 3141, 2, 4);
|
||||
jest.spyOn(eventSeq2Time4Dup, "getId").mockReturnValue(eventSeq2Time4.getId());
|
||||
eventSeq3Time2 = mkVoiceBroadcastChunkEvent(userId, roomId, 42, 3, 2);
|
||||
eventSeq4Time1 = mkVoiceBroadcastChunkEvent(userId, roomId, 69, 4, 1);
|
||||
eventSeqUTime3 = mkVoiceBroadcastChunkEvent(userId, roomId, 314, undefined, 3);
|
||||
chunkEvents = new VoiceBroadcastChunkEvents();
|
||||
});
|
||||
|
||||
describe("when adding events that all have a sequence", () => {
|
||||
beforeEach(() => {
|
||||
chunkEvents.addEvent(eventSeq2Time4);
|
||||
chunkEvents.addEvent(eventSeq1Time1);
|
||||
chunkEvents.addEvents([
|
||||
eventSeq4Time1,
|
||||
eventSeq2Time4Dup,
|
||||
eventSeq3Time2,
|
||||
]);
|
||||
});
|
||||
|
||||
it("should provide the events sort by sequence", () => {
|
||||
expect(chunkEvents.getEvents()).toEqual([
|
||||
eventSeq1Time1,
|
||||
eventSeq2Time4Dup,
|
||||
eventSeq3Time2,
|
||||
eventSeq4Time1,
|
||||
]);
|
||||
});
|
||||
|
||||
it("getLength should return the total length of all chunks", () => {
|
||||
expect(chunkEvents.getLength()).toBe(3259);
|
||||
});
|
||||
|
||||
it("should return the expected next chunk", () => {
|
||||
expect(chunkEvents.getNext(eventSeq2Time4Dup)).toBe(eventSeq3Time2);
|
||||
});
|
||||
|
||||
it("should return undefined for next last chunk", () => {
|
||||
expect(chunkEvents.getNext(eventSeq4Time1)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when adding events where at least one does not have a sequence", () => {
|
||||
beforeEach(() => {
|
||||
chunkEvents.addEvent(eventSeq2Time4);
|
||||
chunkEvents.addEvent(eventSeq1Time1);
|
||||
chunkEvents.addEvents([
|
||||
eventSeq4Time1,
|
||||
eventSeqUTime3,
|
||||
eventSeq2Time4Dup,
|
||||
eventSeq3Time2,
|
||||
]);
|
||||
});
|
||||
|
||||
it("should provide the events sort by timestamp without duplicates", () => {
|
||||
expect(chunkEvents.getEvents()).toEqual([
|
||||
eventSeq1Time1,
|
||||
eventSeq4Time1,
|
||||
eventSeq3Time2,
|
||||
eventSeqUTime3,
|
||||
eventSeq2Time4Dup,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -14,9 +14,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { EventType, MatrixEvent, MsgType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "../../../src/voice-broadcast";
|
||||
import {
|
||||
VoiceBroadcastChunkEventType,
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
} from "../../../src/voice-broadcast";
|
||||
import { mkEvent } from "../../test-utils";
|
||||
|
||||
export const mkVoiceBroadcastInfoStateEvent = (
|
||||
|
@ -48,3 +52,31 @@ export const mkVoiceBroadcastInfoStateEvent = (
|
|||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const mkVoiceBroadcastChunkEvent = (
|
||||
userId: string,
|
||||
roomId: string,
|
||||
duration: number,
|
||||
sequence?: number,
|
||||
timestamp?: number,
|
||||
): MatrixEvent => {
|
||||
return mkEvent({
|
||||
event: true,
|
||||
user: userId,
|
||||
room: roomId,
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
msgtype: MsgType.Audio,
|
||||
["org.matrix.msc1767.audio"]: {
|
||||
duration,
|
||||
},
|
||||
info: {
|
||||
duration,
|
||||
},
|
||||
[VoiceBroadcastChunkEventType]: {
|
||||
...(sequence ? { sequence } : {}),
|
||||
},
|
||||
},
|
||||
ts: timestamp,
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue