Display voice broadcast total length (#9517)
This commit is contained in:
parent
9b644844da
commit
66c20a0798
10 changed files with 443 additions and 94 deletions
|
@ -17,7 +17,9 @@ limitations under the License.
|
||||||
.mx_VoiceBroadcastBody {
|
.mx_VoiceBroadcastBody {
|
||||||
background-color: $quinary-content;
|
background-color: $quinary-content;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
color: $secondary-content;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
font-size: $font-12px;
|
||||||
padding: $spacing-12;
|
padding: $spacing-12;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,3 +39,8 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_VoiceBroadcastBody_timerow {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import { useVoiceBroadcastPlayback } from "../../hooks/useVoiceBroadcastPlayback
|
||||||
import { Icon as PlayIcon } from "../../../../res/img/element-icons/play.svg";
|
import { Icon as PlayIcon } from "../../../../res/img/element-icons/play.svg";
|
||||||
import { Icon as PauseIcon } from "../../../../res/img/element-icons/pause.svg";
|
import { Icon as PauseIcon } from "../../../../res/img/element-icons/pause.svg";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
|
import Clock from "../../../components/views/audio_messages/Clock";
|
||||||
|
|
||||||
interface VoiceBroadcastPlaybackBodyProps {
|
interface VoiceBroadcastPlaybackBodyProps {
|
||||||
playback: VoiceBroadcastPlayback;
|
playback: VoiceBroadcastPlayback;
|
||||||
|
@ -36,6 +37,7 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
|
||||||
playback,
|
playback,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
|
length,
|
||||||
live,
|
live,
|
||||||
room,
|
room,
|
||||||
sender,
|
sender,
|
||||||
|
@ -73,6 +75,8 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lengthSeconds = Math.round(length / 1000);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_VoiceBroadcastBody">
|
<div className="mx_VoiceBroadcastBody">
|
||||||
<VoiceBroadcastHeader
|
<VoiceBroadcastHeader
|
||||||
|
@ -84,6 +88,9 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
|
||||||
<div className="mx_VoiceBroadcastBody_controls">
|
<div className="mx_VoiceBroadcastBody_controls">
|
||||||
{ control }
|
{ control }
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mx_VoiceBroadcastBody_timerow">
|
||||||
|
<Clock seconds={lengthSeconds} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -50,7 +50,15 @@ export const useVoiceBroadcastPlayback = (playback: VoiceBroadcastPlayback) => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [length, setLength] = useState(playback.getLength());
|
||||||
|
useTypedEventEmitter(
|
||||||
|
playback,
|
||||||
|
VoiceBroadcastPlaybackEvent.LengthChanged,
|
||||||
|
length => setLength(length),
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
length,
|
||||||
live: playbackInfoState !== VoiceBroadcastInfoState.Stopped,
|
live: playbackInfoState !== VoiceBroadcastInfoState.Stopped,
|
||||||
room: room,
|
room: room,
|
||||||
sender: playback.infoEvent.sender,
|
sender: playback.infoEvent.sender,
|
||||||
|
|
|
@ -31,6 +31,7 @@ import { IDestroyable } from "../../utils/IDestroyable";
|
||||||
import { VoiceBroadcastChunkEventType, VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "..";
|
import { VoiceBroadcastChunkEventType, VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "..";
|
||||||
import { RelationsHelper, RelationsHelperEvent } from "../../events/RelationsHelper";
|
import { RelationsHelper, RelationsHelperEvent } from "../../events/RelationsHelper";
|
||||||
import { getReferenceRelationsForEvent } from "../../events";
|
import { getReferenceRelationsForEvent } from "../../events";
|
||||||
|
import { VoiceBroadcastChunkEvents } from "../utils/VoiceBroadcastChunkEvents";
|
||||||
|
|
||||||
export enum VoiceBroadcastPlaybackState {
|
export enum VoiceBroadcastPlaybackState {
|
||||||
Paused,
|
Paused,
|
||||||
|
@ -59,9 +60,9 @@ export class VoiceBroadcastPlayback
|
||||||
implements IDestroyable {
|
implements IDestroyable {
|
||||||
private state = VoiceBroadcastPlaybackState.Stopped;
|
private state = VoiceBroadcastPlaybackState.Stopped;
|
||||||
private infoState: VoiceBroadcastInfoState;
|
private infoState: VoiceBroadcastInfoState;
|
||||||
private chunkEvents = new Map<string, MatrixEvent>();
|
private chunkEvents = new VoiceBroadcastChunkEvents();
|
||||||
private queue: Playback[] = [];
|
private playbacks = new Map<string, Playback>();
|
||||||
private currentlyPlaying: Playback;
|
private currentlyPlaying: MatrixEvent;
|
||||||
private lastInfoEvent: MatrixEvent;
|
private lastInfoEvent: MatrixEvent;
|
||||||
private chunkRelationHelper: RelationsHelper;
|
private chunkRelationHelper: RelationsHelper;
|
||||||
private infoRelationHelper: RelationsHelper;
|
private infoRelationHelper: RelationsHelper;
|
||||||
|
@ -101,11 +102,12 @@ export class VoiceBroadcastPlayback
|
||||||
if (!eventId
|
if (!eventId
|
||||||
|| eventId.startsWith("~!") // don't add local events
|
|| eventId.startsWith("~!") // don't add local events
|
||||||
|| event.getContent()?.msgtype !== MsgType.Audio // don't add non-audio event
|
|| event.getContent()?.msgtype !== MsgType.Audio // don't add non-audio event
|
||||||
|| this.chunkEvents.has(eventId)) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.chunkEvents.set(eventId, event);
|
this.chunkEvents.addEvent(event);
|
||||||
|
this.emit(VoiceBroadcastPlaybackEvent.LengthChanged, this.chunkEvents.getLength());
|
||||||
|
|
||||||
if (this.getState() !== VoiceBroadcastPlaybackState.Stopped) {
|
if (this.getState() !== VoiceBroadcastPlaybackState.Stopped) {
|
||||||
await this.enqueueChunk(event);
|
await this.enqueueChunk(event);
|
||||||
|
@ -143,6 +145,8 @@ export class VoiceBroadcastPlayback
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.chunkEvents.addEvents(chunkEvents);
|
||||||
|
|
||||||
for (const chunkEvent of chunkEvents) {
|
for (const chunkEvent of chunkEvents) {
|
||||||
await this.enqueueChunk(chunkEvent);
|
await this.enqueueChunk(chunkEvent);
|
||||||
}
|
}
|
||||||
|
@ -158,7 +162,7 @@ export class VoiceBroadcastPlayback
|
||||||
const playback = PlaybackManager.instance.createPlaybackInstance(buffer);
|
const playback = PlaybackManager.instance.createPlaybackInstance(buffer);
|
||||||
await playback.prepare();
|
await playback.prepare();
|
||||||
playback.clockInfo.populatePlaceholdersFrom(chunkEvent);
|
playback.clockInfo.populatePlaceholdersFrom(chunkEvent);
|
||||||
this.queue[sequenceNumber - 1] = playback; // -1 because the sequence number starts at 1
|
this.playbacks.set(chunkEvent.getId(), playback);
|
||||||
playback.on(UPDATE_EVENT, (state) => this.onPlaybackStateChange(playback, state));
|
playback.on(UPDATE_EVENT, (state) => this.onPlaybackStateChange(playback, state));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,16 +171,18 @@ export class VoiceBroadcastPlayback
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.playNext(playback);
|
await this.playNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async playNext(current: Playback): Promise<void> {
|
private async playNext(): Promise<void> {
|
||||||
const next = this.queue[this.queue.indexOf(current) + 1];
|
if (!this.currentlyPlaying) return;
|
||||||
|
|
||||||
|
const next = this.chunkEvents.getNext(this.currentlyPlaying);
|
||||||
|
|
||||||
if (next) {
|
if (next) {
|
||||||
this.setState(VoiceBroadcastPlaybackState.Playing);
|
this.setState(VoiceBroadcastPlaybackState.Playing);
|
||||||
this.currentlyPlaying = next;
|
this.currentlyPlaying = next;
|
||||||
await next.play();
|
await this.playbacks.get(next.getId())?.play();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,19 +194,25 @@ export class VoiceBroadcastPlayback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getLength(): number {
|
||||||
|
return this.chunkEvents.getLength();
|
||||||
|
}
|
||||||
|
|
||||||
public async start(): Promise<void> {
|
public async start(): Promise<void> {
|
||||||
if (this.queue.length === 0) {
|
if (this.playbacks.size === 0) {
|
||||||
await this.loadChunks();
|
await this.loadChunks();
|
||||||
}
|
}
|
||||||
|
|
||||||
const toPlayIndex = this.getInfoState() === VoiceBroadcastInfoState.Stopped
|
const chunkEvents = this.chunkEvents.getEvents();
|
||||||
? 0 // start at the beginning for an ended voice broadcast
|
|
||||||
: this.queue.length - 1; // start at the current chunk for an ongoing voice broadcast
|
|
||||||
|
|
||||||
if (this.queue[toPlayIndex]) {
|
const toPlay = this.getInfoState() === VoiceBroadcastInfoState.Stopped
|
||||||
|
? chunkEvents[0] // start at the beginning for an ended voice broadcast
|
||||||
|
: chunkEvents[chunkEvents.length - 1]; // start at the current chunk for an ongoing voice broadcast
|
||||||
|
|
||||||
|
if (this.playbacks.has(toPlay?.getId())) {
|
||||||
this.setState(VoiceBroadcastPlaybackState.Playing);
|
this.setState(VoiceBroadcastPlaybackState.Playing);
|
||||||
this.currentlyPlaying = this.queue[toPlayIndex];
|
this.currentlyPlaying = toPlay;
|
||||||
await this.currentlyPlaying.play();
|
await this.playbacks.get(toPlay.getId()).play();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,14 +220,14 @@ export class VoiceBroadcastPlayback
|
||||||
}
|
}
|
||||||
|
|
||||||
public get length(): number {
|
public get length(): number {
|
||||||
return this.chunkEvents.size;
|
return this.chunkEvents.getLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
public stop(): void {
|
public stop(): void {
|
||||||
this.setState(VoiceBroadcastPlaybackState.Stopped);
|
this.setState(VoiceBroadcastPlaybackState.Stopped);
|
||||||
|
|
||||||
if (this.currentlyPlaying) {
|
if (this.currentlyPlaying) {
|
||||||
this.currentlyPlaying.stop();
|
this.playbacks.get(this.currentlyPlaying.getId()).stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +237,7 @@ export class VoiceBroadcastPlayback
|
||||||
|
|
||||||
this.setState(VoiceBroadcastPlaybackState.Paused);
|
this.setState(VoiceBroadcastPlaybackState.Paused);
|
||||||
if (!this.currentlyPlaying) return;
|
if (!this.currentlyPlaying) return;
|
||||||
this.currentlyPlaying.pause();
|
this.playbacks.get(this.currentlyPlaying.getId()).pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
public resume(): void {
|
public resume(): void {
|
||||||
|
@ -236,7 +248,7 @@ export class VoiceBroadcastPlayback
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState(VoiceBroadcastPlaybackState.Playing);
|
this.setState(VoiceBroadcastPlaybackState.Playing);
|
||||||
this.currentlyPlaying.play();
|
this.playbacks.get(this.currentlyPlaying.getId()).play();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -285,15 +297,13 @@ export class VoiceBroadcastPlayback
|
||||||
this.emit(VoiceBroadcastPlaybackEvent.InfoStateChanged, state);
|
this.emit(VoiceBroadcastPlaybackEvent.InfoStateChanged, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
private destroyQueue(): void {
|
|
||||||
this.queue.forEach(p => p.destroy());
|
|
||||||
this.queue = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
this.chunkRelationHelper.destroy();
|
this.chunkRelationHelper.destroy();
|
||||||
this.infoRelationHelper.destroy();
|
this.infoRelationHelper.destroy();
|
||||||
this.removeAllListeners();
|
this.removeAllListeners();
|
||||||
this.destroyQueue();
|
|
||||||
|
this.chunkEvents = new VoiceBroadcastChunkEvents();
|
||||||
|
this.playbacks.forEach(p => p.destroy());
|
||||||
|
this.playbacks = new Map<string, Playback>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
99
src/voice-broadcast/utils/VoiceBroadcastChunkEvents.ts
Normal file
99
src/voice-broadcast/utils/VoiceBroadcastChunkEvents.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 { VoiceBroadcastChunkEventType } from "..";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Voice broadcast chunk collection.
|
||||||
|
* Orders chunks by sequence (if available) or timestamp.
|
||||||
|
*/
|
||||||
|
export class VoiceBroadcastChunkEvents {
|
||||||
|
private events: MatrixEvent[] = [];
|
||||||
|
|
||||||
|
public getEvents(): MatrixEvent[] {
|
||||||
|
return [...this.events];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getNext(event: MatrixEvent): MatrixEvent | undefined {
|
||||||
|
return this.events[this.events.indexOf(event) + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public addEvent(event: MatrixEvent): void {
|
||||||
|
if (this.addOrReplaceEvent(event)) {
|
||||||
|
this.sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public addEvents(events: MatrixEvent[]): void {
|
||||||
|
const atLeastOneNew = events.reduce((newSoFar: boolean, event: MatrixEvent): boolean => {
|
||||||
|
return this.addOrReplaceEvent(event) || newSoFar;
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
if (atLeastOneNew) {
|
||||||
|
this.sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public includes(event: MatrixEvent): boolean {
|
||||||
|
return !!this.events.find(e => e.getId() === event.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLength(): number {
|
||||||
|
return this.events.reduce((length: number, event: MatrixEvent) => {
|
||||||
|
return length + this.calculateChunkLength(event);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateChunkLength(event: MatrixEvent): number {
|
||||||
|
return event.getContent()?.["org.matrix.msc1767.audio"]?.duration
|
||||||
|
|| event.getContent()?.info?.duration
|
||||||
|
|| 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addOrReplaceEvent = (event: MatrixEvent): boolean => {
|
||||||
|
this.events = this.events.filter(e => e.getId() !== event.getId());
|
||||||
|
this.events.push(event);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort by sequence, if available for all events.
|
||||||
|
* Else fall back to timestamp.
|
||||||
|
*/
|
||||||
|
private sort(): void {
|
||||||
|
const compareFn = this.allHaveSequence() ? this.compareBySequence : this.compareByTimestamp;
|
||||||
|
this.events.sort(compareFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
private compareBySequence = (a: MatrixEvent, b: MatrixEvent): number => {
|
||||||
|
const aSequence = a.getContent()?.[VoiceBroadcastChunkEventType]?.sequence || 0;
|
||||||
|
const bSequence = b.getContent()?.[VoiceBroadcastChunkEventType]?.sequence || 0;
|
||||||
|
return aSequence - bSequence;
|
||||||
|
};
|
||||||
|
|
||||||
|
private compareByTimestamp = (a: MatrixEvent, b: MatrixEvent): number => {
|
||||||
|
return a.getTs() - b.getTs();
|
||||||
|
};
|
||||||
|
|
||||||
|
private allHaveSequence(): boolean {
|
||||||
|
return !this.events.some((event: MatrixEvent) => {
|
||||||
|
const sequence = event.getContent()?.[VoiceBroadcastChunkEventType]?.sequence;
|
||||||
|
return parseInt(sequence, 10) !== sequence;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,17 +16,19 @@ limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
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 userEvent from "@testing-library/user-event";
|
||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
VoiceBroadcastInfoEventType,
|
VoiceBroadcastInfoState,
|
||||||
VoiceBroadcastPlayback,
|
VoiceBroadcastPlayback,
|
||||||
VoiceBroadcastPlaybackBody,
|
VoiceBroadcastPlaybackBody,
|
||||||
|
VoiceBroadcastPlaybackEvent,
|
||||||
VoiceBroadcastPlaybackState,
|
VoiceBroadcastPlaybackState,
|
||||||
} from "../../../../src/voice-broadcast";
|
} 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
|
// mock RoomAvatar, because it is doing too much fancy stuff
|
||||||
jest.mock("../../../../src/components/views/avatars/RoomAvatar", () => ({
|
jest.mock("../../../../src/components/views/avatars/RoomAvatar", () => ({
|
||||||
|
@ -46,19 +48,19 @@ describe("VoiceBroadcastPlaybackBody", () => {
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
client = stubClient();
|
client = stubClient();
|
||||||
infoEvent = mkEvent({
|
infoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||||
event: true,
|
roomId,
|
||||||
type: VoiceBroadcastInfoEventType,
|
VoiceBroadcastInfoState.Started,
|
||||||
content: {},
|
userId,
|
||||||
room: roomId,
|
client.getDeviceId(),
|
||||||
user: userId,
|
);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
playback = new VoiceBroadcastPlayback(infoEvent, client);
|
playback = new VoiceBroadcastPlayback(infoEvent, client);
|
||||||
jest.spyOn(playback, "toggle");
|
jest.spyOn(playback, "toggle").mockImplementation(() => Promise.resolve());
|
||||||
jest.spyOn(playback, "getState");
|
jest.spyOn(playback, "getState");
|
||||||
|
jest.spyOn(playback, "getLength").mockReturnValue((23 * 60 + 42) * 1000); // 23:42
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when rendering a buffering voice broadcast", () => {
|
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(() => {
|
beforeEach(() => {
|
||||||
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Stopped);
|
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Stopped);
|
||||||
renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />);
|
renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />);
|
||||||
|
@ -87,6 +89,18 @@ describe("VoiceBroadcastPlaybackBody", () => {
|
||||||
expect(playback.toggle).toHaveBeenCalled();
|
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([
|
describe.each([
|
||||||
|
|
|
@ -64,6 +64,15 @@ exports[`VoiceBroadcastPlaybackBody when rendering a 0 broadcast should render a
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_VoiceBroadcastBody_timerow"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_Clock"
|
||||||
|
>
|
||||||
|
23:42
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -132,6 +141,15 @@ exports[`VoiceBroadcastPlaybackBody when rendering a 1 broadcast should render a
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_VoiceBroadcastBody_timerow"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_Clock"
|
||||||
|
>
|
||||||
|
23:42
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -201,6 +219,92 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mocked } from "jest-mock";
|
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 { Relations } from "matrix-js-sdk/src/models/relations";
|
||||||
|
|
||||||
import { Playback, PlaybackState } from "../../../src/audio/Playback";
|
import { Playback, PlaybackState } from "../../../src/audio/Playback";
|
||||||
|
@ -24,7 +24,6 @@ import { getReferenceRelationsForEvent } from "../../../src/events";
|
||||||
import { RelationsHelperEvent } from "../../../src/events/RelationsHelper";
|
import { RelationsHelperEvent } from "../../../src/events/RelationsHelper";
|
||||||
import { MediaEventHelper } from "../../../src/utils/MediaEventHelper";
|
import { MediaEventHelper } from "../../../src/utils/MediaEventHelper";
|
||||||
import {
|
import {
|
||||||
VoiceBroadcastChunkEventType,
|
|
||||||
VoiceBroadcastInfoEventType,
|
VoiceBroadcastInfoEventType,
|
||||||
VoiceBroadcastInfoState,
|
VoiceBroadcastInfoState,
|
||||||
VoiceBroadcastPlayback,
|
VoiceBroadcastPlayback,
|
||||||
|
@ -33,6 +32,7 @@ import {
|
||||||
} from "../../../src/voice-broadcast";
|
} from "../../../src/voice-broadcast";
|
||||||
import { mkEvent, stubClient } from "../../test-utils";
|
import { mkEvent, stubClient } from "../../test-utils";
|
||||||
import { createTestPlayback } from "../../test-utils/audio";
|
import { createTestPlayback } from "../../test-utils/audio";
|
||||||
|
import { mkVoiceBroadcastChunkEvent } from "../utils/test-utils";
|
||||||
|
|
||||||
jest.mock("../../../src/events/getReferenceRelationsForEvent", () => ({
|
jest.mock("../../../src/events/getReferenceRelationsForEvent", () => ({
|
||||||
getReferenceRelationsForEvent: jest.fn(),
|
getReferenceRelationsForEvent: jest.fn(),
|
||||||
|
@ -49,19 +49,15 @@ describe("VoiceBroadcastPlayback", () => {
|
||||||
let infoEvent: MatrixEvent;
|
let infoEvent: MatrixEvent;
|
||||||
let playback: VoiceBroadcastPlayback;
|
let playback: VoiceBroadcastPlayback;
|
||||||
let onStateChanged: (state: VoiceBroadcastPlaybackState) => void;
|
let onStateChanged: (state: VoiceBroadcastPlaybackState) => void;
|
||||||
let chunk0Event: MatrixEvent;
|
|
||||||
let chunk1Event: MatrixEvent;
|
let chunk1Event: MatrixEvent;
|
||||||
let chunk2Event: MatrixEvent;
|
let chunk2Event: MatrixEvent;
|
||||||
let chunk3Event: MatrixEvent;
|
let chunk3Event: MatrixEvent;
|
||||||
const chunk0Data = new ArrayBuffer(1);
|
|
||||||
const chunk1Data = new ArrayBuffer(2);
|
const chunk1Data = new ArrayBuffer(2);
|
||||||
const chunk2Data = new ArrayBuffer(3);
|
const chunk2Data = new ArrayBuffer(3);
|
||||||
const chunk3Data = new ArrayBuffer(3);
|
const chunk3Data = new ArrayBuffer(3);
|
||||||
let chunk0Helper: MediaEventHelper;
|
|
||||||
let chunk1Helper: MediaEventHelper;
|
let chunk1Helper: MediaEventHelper;
|
||||||
let chunk2Helper: MediaEventHelper;
|
let chunk2Helper: MediaEventHelper;
|
||||||
let chunk3Helper: MediaEventHelper;
|
let chunk3Helper: MediaEventHelper;
|
||||||
let chunk0Playback: Playback;
|
|
||||||
let chunk1Playback: Playback;
|
let chunk1Playback: Playback;
|
||||||
let chunk2Playback: Playback;
|
let chunk2Playback: Playback;
|
||||||
let chunk3Playback: 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 => {
|
const mkChunkHelper = (data: ArrayBuffer): MediaEventHelper => {
|
||||||
return {
|
return {
|
||||||
sourceBlob: {
|
sourceBlob: {
|
||||||
|
@ -152,25 +133,20 @@ describe("VoiceBroadcastPlayback", () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
client = stubClient();
|
client = stubClient();
|
||||||
|
|
||||||
// crap event to test 0 as first sequence number
|
chunk1Event = mkVoiceBroadcastChunkEvent(userId, roomId, 23, 1);
|
||||||
chunk0Event = mkChunkEvent(0);
|
chunk2Event = mkVoiceBroadcastChunkEvent(userId, roomId, 23, 2);
|
||||||
chunk1Event = mkChunkEvent(1);
|
chunk3Event = mkVoiceBroadcastChunkEvent(userId, roomId, 23, 3);
|
||||||
chunk2Event = mkChunkEvent(2);
|
|
||||||
chunk3Event = mkChunkEvent(3);
|
|
||||||
|
|
||||||
chunk0Helper = mkChunkHelper(chunk0Data);
|
|
||||||
chunk1Helper = mkChunkHelper(chunk1Data);
|
chunk1Helper = mkChunkHelper(chunk1Data);
|
||||||
chunk2Helper = mkChunkHelper(chunk2Data);
|
chunk2Helper = mkChunkHelper(chunk2Data);
|
||||||
chunk3Helper = mkChunkHelper(chunk3Data);
|
chunk3Helper = mkChunkHelper(chunk3Data);
|
||||||
|
|
||||||
chunk0Playback = createTestPlayback();
|
|
||||||
chunk1Playback = createTestPlayback();
|
chunk1Playback = createTestPlayback();
|
||||||
chunk2Playback = createTestPlayback();
|
chunk2Playback = createTestPlayback();
|
||||||
chunk3Playback = createTestPlayback();
|
chunk3Playback = createTestPlayback();
|
||||||
|
|
||||||
jest.spyOn(PlaybackManager.instance, "createPlaybackInstance").mockImplementation(
|
jest.spyOn(PlaybackManager.instance, "createPlaybackInstance").mockImplementation(
|
||||||
(buffer: ArrayBuffer, _waveForm?: number[]) => {
|
(buffer: ArrayBuffer, _waveForm?: number[]) => {
|
||||||
if (buffer === chunk0Data) return chunk0Playback;
|
|
||||||
if (buffer === chunk1Data) return chunk1Playback;
|
if (buffer === chunk1Data) return chunk1Playback;
|
||||||
if (buffer === chunk2Data) return chunk2Playback;
|
if (buffer === chunk2Data) return chunk2Playback;
|
||||||
if (buffer === chunk3Data) return chunk3Playback;
|
if (buffer === chunk3Data) return chunk3Playback;
|
||||||
|
@ -178,7 +154,6 @@ describe("VoiceBroadcastPlayback", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
mocked(MediaEventHelper).mockImplementation((event: MatrixEvent) => {
|
mocked(MediaEventHelper).mockImplementation((event: MatrixEvent) => {
|
||||||
if (event === chunk0Event) return chunk0Helper;
|
|
||||||
if (event === chunk1Event) return chunk1Helper;
|
if (event === chunk1Event) return chunk1Helper;
|
||||||
if (event === chunk2Event) return chunk2Helper;
|
if (event === chunk2Event) return chunk2Helper;
|
||||||
if (event === chunk3Event) return chunk3Helper;
|
if (event === chunk3Event) return chunk3Helper;
|
||||||
|
@ -240,7 +215,7 @@ describe("VoiceBroadcastPlayback", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Resumed);
|
infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Resumed);
|
||||||
playback = mkPlayback();
|
playback = mkPlayback();
|
||||||
setUpChunkEvents([chunk2Event, chunk0Event, chunk1Event]);
|
setUpChunkEvents([chunk2Event, chunk1Event]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("and calling start", () => {
|
describe("and calling start", () => {
|
||||||
|
@ -282,20 +257,9 @@ describe("VoiceBroadcastPlayback", () => {
|
||||||
playback = mkPlayback();
|
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", () => {
|
describe("and there are some chunks", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setUpChunkEvents([chunk2Event, chunk0Event, chunk1Event]);
|
setUpChunkEvents([chunk2Event, chunk1Event]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should expose the info event", () => {
|
it("should expose the info event", () => {
|
||||||
|
@ -337,6 +301,21 @@ describe("VoiceBroadcastPlayback", () => {
|
||||||
stopPlayback();
|
stopPlayback();
|
||||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Stopped);
|
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", () => {
|
describe("and calling toggle for the first time", () => {
|
||||||
|
@ -378,16 +357,6 @@ describe("VoiceBroadcastPlayback", () => {
|
||||||
itShouldEmitAStateChangedEvent(VoiceBroadcastPlaybackState.Playing);
|
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.
|
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";
|
import { mkEvent } from "../../test-utils";
|
||||||
|
|
||||||
export const mkVoiceBroadcastInfoStateEvent = (
|
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…
Reference in a new issue