Add voice broadcast playback pip (#9603)

pull/28788/head^2
Michael Weimann 2022-11-24 09:08:41 +01:00 committed by GitHub
parent 569a364933
commit a8e15ebe60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 372 additions and 41 deletions

View File

@ -39,11 +39,14 @@ import { CallStore } from "../../../stores/CallStore";
import {
useCurrentVoiceBroadcastPreRecording,
useCurrentVoiceBroadcastRecording,
VoiceBroadcastPlayback,
VoiceBroadcastPlaybackBody,
VoiceBroadcastPreRecording,
VoiceBroadcastPreRecordingPip,
VoiceBroadcastRecording,
VoiceBroadcastRecordingPip,
} from '../../../voice-broadcast';
import { useCurrentVoiceBroadcastPlayback } from '../../../voice-broadcast/hooks/useCurrentVoiceBroadcastPlayback';
const SHOW_CALL_IN_STATES = [
CallState.Connected,
@ -57,6 +60,7 @@ const SHOW_CALL_IN_STATES = [
interface IProps {
voiceBroadcastRecording?: Optional<VoiceBroadcastRecording>;
voiceBroadcastPreRecording?: Optional<VoiceBroadcastPreRecording>;
voiceBroadcastPlayback?: Optional<VoiceBroadcastPlayback>;
}
interface IState {
@ -330,6 +334,15 @@ class PipView extends React.Component<IProps, IState> {
this.setState({ showWidgetInPip, persistentWidgetId, persistentRoomId });
}
private createVoiceBroadcastPlaybackPipContent(voiceBroadcastPlayback: VoiceBroadcastPlayback): CreatePipChildren {
return ({ onStartMoving }) => <div onMouseDown={onStartMoving}>
<VoiceBroadcastPlaybackBody
playback={voiceBroadcastPlayback}
pip={true}
/>
</div>;
}
private createVoiceBroadcastPreRecordingPipContent(
voiceBroadcastPreRecording: VoiceBroadcastPreRecording,
): CreatePipChildren {
@ -358,6 +371,10 @@ class PipView extends React.Component<IProps, IState> {
pipContent = this.createVoiceBroadcastPreRecordingPipContent(this.props.voiceBroadcastPreRecording);
}
if (this.props.voiceBroadcastPlayback) {
pipContent = this.createVoiceBroadcastPlaybackPipContent(this.props.voiceBroadcastPlayback);
}
if (this.props.voiceBroadcastRecording) {
pipContent = this.createVoiceBroadcastRecordingPipContent(this.props.voiceBroadcastRecording);
}
@ -430,9 +447,13 @@ const PipViewHOC: React.FC<IProps> = (props) => {
const voiceBroadcastRecordingsStore = sdkContext.voiceBroadcastRecordingsStore;
const { currentVoiceBroadcastRecording } = useCurrentVoiceBroadcastRecording(voiceBroadcastRecordingsStore);
const voiceBroadcastPlaybacksStore = sdkContext.voiceBroadcastPlaybacksStore;
const { currentVoiceBroadcastPlayback } = useCurrentVoiceBroadcastPlayback(voiceBroadcastPlaybacksStore);
return <PipView
voiceBroadcastRecording={currentVoiceBroadcastRecording}
voiceBroadcastPlayback={currentVoiceBroadcastPlayback}
voiceBroadcastPreRecording={currentVoiceBroadcastPreRecording}
voiceBroadcastRecording={currentVoiceBroadcastRecording}
{...props}
/>;
};

View File

@ -30,7 +30,11 @@ import TypingStore from "../stores/TypingStore";
import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
import { WidgetPermissionStore } from "../stores/widgets/WidgetPermissionStore";
import WidgetStore from "../stores/WidgetStore";
import { VoiceBroadcastPreRecordingStore, VoiceBroadcastRecordingsStore } from "../voice-broadcast";
import {
VoiceBroadcastPlaybacksStore,
VoiceBroadcastPreRecordingStore,
VoiceBroadcastRecordingsStore,
} from "../voice-broadcast";
export const SDKContext = createContext<SdkContextClass>(undefined);
SDKContext.displayName = "SDKContext";
@ -68,6 +72,7 @@ export class SdkContextClass {
protected _TypingStore?: TypingStore;
protected _VoiceBroadcastRecordingsStore?: VoiceBroadcastRecordingsStore;
protected _VoiceBroadcastPreRecordingStore?: VoiceBroadcastPreRecordingStore;
protected _VoiceBroadcastPlaybacksStore?: VoiceBroadcastPlaybacksStore;
/**
* Automatically construct stores which need to be created eagerly so they can register with
@ -166,4 +171,11 @@ export class SdkContextClass {
}
return this._VoiceBroadcastPreRecordingStore;
}
public get voiceBroadcastPlaybacksStore(): VoiceBroadcastPlaybacksStore {
if (!this._VoiceBroadcastPlaybacksStore) {
this._VoiceBroadcastPlaybacksStore = VoiceBroadcastPlaybacksStore.instance();
}
return this._VoiceBroadcastPlaybacksStore;
}
}

View File

@ -51,6 +51,11 @@ import { UPDATE_EVENT } from "./AsyncStore";
import { SdkContextClass } from "../contexts/SDKContext";
import { CallStore } from "./CallStore";
import { ThreadPayload } from "../dispatcher/payloads/ThreadPayload";
import {
doClearCurrentVoiceBroadcastPlaybackIfStopped,
doMaybeSetCurrentVoiceBroadcastPlayback,
} from "../voice-broadcast";
import { IRoomStateEventsActionPayload } from "../actions/MatrixActionCreators";
const NUM_JOIN_RETRY = 5;
@ -195,6 +200,28 @@ export class RoomViewStore extends EventEmitter {
this.emit(UPDATE_EVENT);
}
private doMaybeSetCurrentVoiceBroadcastPlayback(room: Room): void {
doMaybeSetCurrentVoiceBroadcastPlayback(
room,
this.stores.client,
this.stores.voiceBroadcastPlaybacksStore,
this.stores.voiceBroadcastRecordingsStore,
);
}
private onRoomStateEvents(event: MatrixEvent): void {
const roomId = event.getRoomId?.();
// no room or not current room
if (!roomId || roomId !== this.state.roomId) return;
const room = this.stores.client?.getRoom(roomId);
if (room) {
this.doMaybeSetCurrentVoiceBroadcastPlayback(room);
}
}
private onDispatch(payload): void { // eslint-disable-line @typescript-eslint/naming-convention
switch (payload.action) {
// view_room:
@ -219,6 +246,10 @@ export class RoomViewStore extends EventEmitter {
wasContextSwitch: false,
viewingCall: false,
});
doClearCurrentVoiceBroadcastPlaybackIfStopped(this.stores.voiceBroadcastPlaybacksStore);
break;
case "MatrixActions.RoomState.events":
this.onRoomStateEvents((payload as IRoomStateEventsActionPayload).event);
break;
case Action.ViewRoomError:
this.viewRoomError(payload);
@ -395,6 +426,10 @@ export class RoomViewStore extends EventEmitter {
metricsTrigger: payload.metricsTrigger as JoinRoomPayload["metricsTrigger"],
});
}
if (room) {
this.doMaybeSetCurrentVoiceBroadcastPlayback(room);
}
} else if (payload.room_alias) {
// Try the room alias to room ID navigation cache first to avoid
// blocking room navigation on the homeserver.

View File

@ -15,6 +15,7 @@ limitations under the License.
*/
import React, { ReactElement } from "react";
import classNames from "classnames";
import {
VoiceBroadcastControl,
@ -36,10 +37,12 @@ import { SeekButton } from "../atoms/SeekButton";
const SEEK_TIME = 30;
interface VoiceBroadcastPlaybackBodyProps {
pip?: boolean;
playback: VoiceBroadcastPlayback;
}
export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProps> = ({
pip = false,
playback,
}) => {
const {
@ -107,8 +110,13 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
/>;
}
const classes = classNames({
mx_VoiceBroadcastBody: true,
["mx_VoiceBroadcastBody--pip"]: pip,
});
return (
<div className="mx_VoiceBroadcastBody">
<div className={classes}>
<VoiceBroadcastHeader
live={liveness}
microphoneLabel={sender?.name}

View File

@ -0,0 +1,44 @@
/*
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 { useState } from "react";
import { useTypedEventEmitter } from "../../hooks/useEventEmitter";
import { VoiceBroadcastPlayback } from "../models/VoiceBroadcastPlayback";
import {
VoiceBroadcastPlaybacksStore,
VoiceBroadcastPlaybacksStoreEvent,
} from "../stores/VoiceBroadcastPlaybacksStore";
export const useCurrentVoiceBroadcastPlayback = (
voiceBroadcastPlaybackStore: VoiceBroadcastPlaybacksStore,
) => {
const [currentVoiceBroadcastPlayback, setVoiceBroadcastPlayback] = useState(
voiceBroadcastPlaybackStore.getCurrent(),
);
useTypedEventEmitter(
voiceBroadcastPlaybackStore,
VoiceBroadcastPlaybacksStoreEvent.CurrentChanged,
(playback: VoiceBroadcastPlayback) => {
setVoiceBroadcastPlayback(playback);
},
);
return {
currentVoiceBroadcastPlayback,
};
};

View File

@ -40,6 +40,8 @@ export * from "./stores/VoiceBroadcastPlaybacksStore";
export * from "./stores/VoiceBroadcastPreRecordingStore";
export * from "./stores/VoiceBroadcastRecordingsStore";
export * from "./utils/checkVoiceBroadcastPreConditions";
export * from "./utils/doClearCurrentVoiceBroadcastPlaybackIfStopped";
export * from "./utils/doMaybeSetCurrentVoiceBroadcastPlayback";
export * from "./utils/getChunkLength";
export * from "./utils/getMaxBroadcastLength";
export * from "./utils/hasRoomLiveVoiceBroadcast";

View File

@ -25,7 +25,7 @@ export enum VoiceBroadcastPlaybacksStoreEvent {
}
interface EventMap {
[VoiceBroadcastPlaybacksStoreEvent.CurrentChanged]: (recording: VoiceBroadcastPlayback) => void;
[VoiceBroadcastPlaybacksStoreEvent.CurrentChanged]: (recording: VoiceBroadcastPlayback | null) => void;
}
/**
@ -53,7 +53,14 @@ export class VoiceBroadcastPlaybacksStore
this.emit(VoiceBroadcastPlaybacksStoreEvent.CurrentChanged, current);
}
public getCurrent(): VoiceBroadcastPlayback {
public clearCurrent(): void {
if (this.current === null) return;
this.current = null;
this.emit(VoiceBroadcastPlaybacksStoreEvent.CurrentChanged, null);
}
public getCurrent(): VoiceBroadcastPlayback | null {
return this.current;
}
@ -80,11 +87,15 @@ export class VoiceBroadcastPlaybacksStore
state: VoiceBroadcastPlaybackState,
playback: VoiceBroadcastPlayback,
): void => {
if ([
VoiceBroadcastPlaybackState.Buffering,
VoiceBroadcastPlaybackState.Playing,
].includes(state)) {
this.pauseExcept(playback);
switch (state) {
case VoiceBroadcastPlaybackState.Buffering:
case VoiceBroadcastPlaybackState.Playing:
this.pauseExcept(playback);
this.setCurrent(playback);
break;
case VoiceBroadcastPlaybackState.Stopped:
this.clearCurrent();
break;
}
};

View File

@ -0,0 +1,26 @@
/*
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 { VoiceBroadcastPlaybacksStore, VoiceBroadcastPlaybackState } from "..";
export const doClearCurrentVoiceBroadcastPlaybackIfStopped = (
voiceBroadcastPlaybacksStore: VoiceBroadcastPlaybacksStore,
) => {
if (voiceBroadcastPlaybacksStore.getCurrent()?.getState() === VoiceBroadcastPlaybackState.Stopped) {
// clear current if stopped
return;
}
};

View File

@ -0,0 +1,64 @@
/*
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 { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import {
hasRoomLiveVoiceBroadcast,
VoiceBroadcastPlaybacksStore,
VoiceBroadcastPlaybackState,
VoiceBroadcastRecordingsStore,
} from "..";
/**
* When a live voice broadcast is in the room and
* another voice broadcast is not currently being listened to or recorded
* the live broadcast in the room is set as the current broadcast to listen to.
* When there is no live broadcast in the room: clear current broadcast.
*
* @param {Room} room The room to check for a live voice broadcast
* @param {MatrixClient} client
* @param {VoiceBroadcastPlaybacksStore} voiceBroadcastPlaybacksStore
* @param {VoiceBroadcastRecordingsStore} voiceBroadcastRecordingsStore
*/
export const doMaybeSetCurrentVoiceBroadcastPlayback = (
room: Room,
client: MatrixClient,
voiceBroadcastPlaybacksStore: VoiceBroadcastPlaybacksStore,
voiceBroadcastRecordingsStore: VoiceBroadcastRecordingsStore,
): void => {
// do not disturb the current recording
if (voiceBroadcastRecordingsStore.hasCurrent()) return;
const currentPlayback = voiceBroadcastPlaybacksStore.getCurrent();
if (currentPlayback && currentPlayback.getState() !== VoiceBroadcastPlaybackState.Stopped) {
// do not disturb the current playback
return;
}
const { infoEvent } = hasRoomLiveVoiceBroadcast(room);
if (infoEvent) {
// live broadcast in the room + no recording + not listening yet: set the current broadcast
const voiceBroadcastPlayback = voiceBroadcastPlaybacksStore.getByInfoEvent(infoEvent, client);
voiceBroadcastPlaybacksStore.setCurrent(voiceBroadcastPlayback);
return;
}
// no broadcast; not listening: clear current
voiceBroadcastPlaybacksStore.clearCurrent();
};

View File

@ -19,36 +19,42 @@ import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "..";
interface Result {
// whether there is a live broadcast in the room
hasBroadcast: boolean;
// info event of any live broadcast in the room
infoEvent: MatrixEvent | null;
// whether the broadcast was started by the user
startedByUser: boolean;
}
/**
* Finds out whether there is a live broadcast in a room.
* Also returns if the user started the broadcast (if any).
*/
export const hasRoomLiveVoiceBroadcast = (room: Room, userId: string): Result => {
export const hasRoomLiveVoiceBroadcast = (room: Room, userId?: string): Result => {
let hasBroadcast = false;
let startedByUser = false;
let infoEvent: MatrixEvent | null = null;
const stateEvents = room.currentState.getStateEvents(VoiceBroadcastInfoEventType);
stateEvents.forEach((event: MatrixEvent) => {
stateEvents.every((event: MatrixEvent) => {
const state = event.getContent()?.state;
if (state && state !== VoiceBroadcastInfoState.Stopped) {
hasBroadcast = true;
infoEvent = event;
// state key = sender's MXID
if (event.getStateKey() === userId) {
infoEvent = event;
startedByUser = true;
// break here, because more than true / true is not possible
return false;
}
}
return true;
});
return {
hasBroadcast,
infoEvent,
startedByUser,
};
};

View File

@ -24,7 +24,11 @@ import { SpaceStoreClass } from "../src/stores/spaces/SpaceStore";
import { WidgetLayoutStore } from "../src/stores/widgets/WidgetLayoutStore";
import { WidgetPermissionStore } from "../src/stores/widgets/WidgetPermissionStore";
import WidgetStore from "../src/stores/WidgetStore";
import { VoiceBroadcastPreRecordingStore, VoiceBroadcastRecordingsStore } from "../src/voice-broadcast";
import {
VoiceBroadcastPlaybacksStore,
VoiceBroadcastPreRecordingStore,
VoiceBroadcastRecordingsStore,
} from "../src/voice-broadcast";
/**
* A class which provides the same API as SdkContextClass but adds additional unsafe setters which can
@ -42,6 +46,7 @@ export class TestSdkContext extends SdkContextClass {
public _SpaceStore?: SpaceStoreClass;
public _VoiceBroadcastRecordingsStore?: VoiceBroadcastRecordingsStore;
public _VoiceBroadcastPreRecordingStore?: VoiceBroadcastPreRecordingStore;
public _VoiceBroadcastPlaybacksStore?: VoiceBroadcastPlaybacksStore;
constructor() {
super();

View File

@ -21,6 +21,7 @@ import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
import { Widget, ClientWidgetApi } from "matrix-widget-api";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import type { RoomMember } from "matrix-js-sdk/src/models/room-member";
import {
@ -46,12 +47,15 @@ import { ViewRoomPayload } from "../../../../src/dispatcher/payloads/ViewRoomPay
import { TestSdkContext } from "../../../TestSdkContext";
import {
VoiceBroadcastInfoState,
VoiceBroadcastPlaybacksStore,
VoiceBroadcastPreRecording,
VoiceBroadcastPreRecordingStore,
VoiceBroadcastRecording,
VoiceBroadcastRecordingsStore,
} from "../../../../src/voice-broadcast";
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
import { RoomViewStore } from "../../../../src/stores/RoomViewStore";
import { IRoomStateEventsActionPayload } from "../../../../src/actions/MatrixActionCreators";
describe("PipView", () => {
useMockedCalls();
@ -60,9 +64,11 @@ describe("PipView", () => {
let sdkContext: TestSdkContext;
let client: Mocked<MatrixClient>;
let room: Room;
let room2: Room;
let alice: RoomMember;
let voiceBroadcastRecordingsStore: VoiceBroadcastRecordingsStore;
let voiceBroadcastPreRecordingStore: VoiceBroadcastPreRecordingStore;
let voiceBroadcastPlaybacksStore: VoiceBroadcastPlaybacksStore;
beforeEach(async () => {
stubClient();
@ -72,17 +78,27 @@ describe("PipView", () => {
room = new Room("!1:example.org", client, "@alice:example.org", {
pendingEventOrdering: PendingEventOrdering.Detached,
});
client.getRoom.mockImplementation(roomId => roomId === room.roomId ? room : null);
client.getRooms.mockReturnValue([room]);
alice = mkRoomMember(room.roomId, "@alice:example.org");
room2 = new Room("!2:example.com", client, "@alice:example.org", {
pendingEventOrdering: PendingEventOrdering.Detached,
});
client.getRoom.mockImplementation((roomId: string) => {
if (roomId === room.roomId) return room;
if (roomId === room2.roomId) return room2;
return null;
});
client.getRooms.mockReturnValue([room, room2]);
client.reEmitter.reEmit(room, [RoomStateEvent.Events]);
room.currentState.setStateEvents([
mkRoomCreateEvent(alice.userId, room.roomId),
]);
jest.spyOn(room, "getMember").mockImplementation(userId => userId === alice.userId ? alice : null);
client.getRoom.mockImplementation(roomId => roomId === room.roomId ? room : null);
client.getRooms.mockReturnValue([room]);
client.reEmitter.reEmit(room, [RoomStateEvent.Events]);
room2.currentState.setStateEvents([
mkRoomCreateEvent(alice.userId, room2.roomId),
]);
await Promise.all([CallStore.instance, WidgetMessagingStore.instance].map(
store => setupAsyncStoreWithClient(store, client),
@ -91,9 +107,11 @@ describe("PipView", () => {
sdkContext = new TestSdkContext();
voiceBroadcastRecordingsStore = new VoiceBroadcastRecordingsStore();
voiceBroadcastPreRecordingStore = new VoiceBroadcastPreRecordingStore();
voiceBroadcastPlaybacksStore = new VoiceBroadcastPlaybacksStore();
sdkContext.client = client;
sdkContext._VoiceBroadcastRecordingsStore = voiceBroadcastRecordingsStore;
sdkContext._VoiceBroadcastPreRecordingStore = voiceBroadcastPreRecordingStore;
sdkContext._VoiceBroadcastPlaybacksStore = voiceBroadcastPlaybacksStore;
});
afterEach(async () => {
@ -146,15 +164,18 @@ describe("PipView", () => {
ActiveWidgetStore.instance.destroyPersistentWidget("1", room.roomId);
};
const setUpVoiceBroadcastRecording = () => {
const voiceBroadcastInfoEvent = mkVoiceBroadcastInfoStateEvent(
const makeVoiceBroadcastInfoStateEvent = (): MatrixEvent => {
return mkVoiceBroadcastInfoStateEvent(
room.roomId,
VoiceBroadcastInfoState.Started,
alice.userId,
client.getDeviceId() || "",
);
};
const voiceBroadcastRecording = new VoiceBroadcastRecording(voiceBroadcastInfoEvent, client);
const setUpVoiceBroadcastRecording = () => {
const infoEvent = makeVoiceBroadcastInfoStateEvent();
const voiceBroadcastRecording = new VoiceBroadcastRecording(infoEvent, client);
voiceBroadcastRecordingsStore.setCurrent(voiceBroadcastRecording);
};
@ -168,6 +189,22 @@ describe("PipView", () => {
voiceBroadcastPreRecordingStore.setCurrent(voiceBroadcastPreRecording);
};
const setUpRoomViewStore = () => {
new RoomViewStore(defaultDispatcher, sdkContext);
};
const startVoiceBroadcastPlayback = (room: Room): MatrixEvent => {
const infoEvent = makeVoiceBroadcastInfoStateEvent();
room.currentState.setStateEvents([infoEvent]);
defaultDispatcher.dispatch<IRoomStateEventsActionPayload>({
action: "MatrixActions.RoomState.events",
event: infoEvent,
state: room.currentState,
lastStateEvent: null,
}, true);
return infoEvent;
};
it("hides if there's no content", () => {
renderPip();
expect(screen.queryByRole("complementary")).toBeNull();
@ -209,7 +246,7 @@ describe("PipView", () => {
});
it("shows a persistent widget with a return button when not viewing the room", () => {
viewRoom("!2:example.org");
viewRoom(room2.roomId);
renderPip();
withWidget(() => {
@ -230,7 +267,7 @@ describe("PipView", () => {
it("should render the voice broadcast recording PiP", () => {
// check for the „Live“ badge
screen.getByText("Live");
expect(screen.queryByText("Live")).toBeInTheDocument();
});
});
@ -242,7 +279,62 @@ describe("PipView", () => {
it("should render the voice broadcast pre-recording PiP", () => {
// check for the „Go live“ button
screen.getByText("Go live");
expect(screen.queryByText("Go live")).toBeInTheDocument();
});
});
describe("when viewing a room with a live voice broadcast", () => {
let startEvent: MatrixEvent | null = null;
beforeEach(() => {
setUpRoomViewStore();
viewRoom(room.roomId);
startEvent = startVoiceBroadcastPlayback(room);
renderPip();
});
it("should render the voice broadcast playback pip", () => {
// check for the „resume voice broadcast“ button
expect(screen.queryByLabelText("play voice broadcast")).toBeInTheDocument();
});
describe("and the broadcast stops", () => {
beforeEach(() => {
act(() => {
const stopEvent = mkVoiceBroadcastInfoStateEvent(
room.roomId,
VoiceBroadcastInfoState.Stopped,
alice.userId,
client.getDeviceId() || "",
startEvent,
);
room.currentState.setStateEvents([stopEvent]);
defaultDispatcher.dispatch<IRoomStateEventsActionPayload>({
action: "MatrixActions.RoomState.events",
event: stopEvent,
state: room.currentState,
lastStateEvent: stopEvent,
}, true);
});
});
it("should not render the voice broadcast playback pip", () => {
// check for the „resume voice broadcast“ button
expect(screen.queryByLabelText("play voice broadcast")).not.toBeInTheDocument();
});
});
describe("and leaving the room", () => {
beforeEach(() => {
act(() => {
viewRoom(room2.roomId);
});
});
it("should not render the voice broadcast playback pip", () => {
// check for the „resume voice broadcast“ button
expect(screen.queryByLabelText("play voice broadcast")).not.toBeInTheDocument();
});
});
});
});

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import {
hasRoomLiveVoiceBroadcast,
@ -29,25 +29,27 @@ describe("hasRoomLiveVoiceBroadcast", () => {
const roomId = "!room:example.com";
let client: MatrixClient;
let room: Room;
let expectedEvent: MatrixEvent | null = null;
const addVoiceBroadcastInfoEvent = (
state: VoiceBroadcastInfoState,
sender: string,
) => {
room.currentState.setStateEvents([
mkVoiceBroadcastInfoStateEvent(
room.roomId,
state,
sender,
"ASD123",
),
]);
): MatrixEvent => {
const infoEvent = mkVoiceBroadcastInfoStateEvent(
room.roomId,
state,
sender,
"ASD123",
);
room.currentState.setStateEvents([infoEvent]);
return infoEvent;
};
const itShouldReturnTrueTrue = () => {
it("should return true/true", () => {
expect(hasRoomLiveVoiceBroadcast(room, client.getUserId())).toEqual({
hasBroadcast: true,
infoEvent: expectedEvent,
startedByUser: true,
});
});
@ -57,6 +59,7 @@ describe("hasRoomLiveVoiceBroadcast", () => {
it("should return true/false", () => {
expect(hasRoomLiveVoiceBroadcast(room, client.getUserId())).toEqual({
hasBroadcast: true,
infoEvent: expectedEvent,
startedByUser: false,
});
});
@ -66,6 +69,7 @@ describe("hasRoomLiveVoiceBroadcast", () => {
it("should return false/false", () => {
expect(hasRoomLiveVoiceBroadcast(room, client.getUserId())).toEqual({
hasBroadcast: false,
infoEvent: null,
startedByUser: false,
});
});
@ -76,6 +80,7 @@ describe("hasRoomLiveVoiceBroadcast", () => {
});
beforeEach(() => {
expectedEvent = null;
room = new Room(roomId, client, client.getUserId());
});
@ -101,7 +106,7 @@ describe("hasRoomLiveVoiceBroadcast", () => {
describe("when there is a live broadcast from the current and another user", () => {
beforeEach(() => {
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Started, client.getUserId());
expectedEvent = addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Started, client.getUserId());
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Started, otherUserId);
});
@ -124,7 +129,7 @@ describe("hasRoomLiveVoiceBroadcast", () => {
VoiceBroadcastInfoState.Resumed,
])("when there is a live broadcast (%s) from the current user", (state: VoiceBroadcastInfoState) => {
beforeEach(() => {
addVoiceBroadcastInfoEvent(state, client.getUserId());
expectedEvent = addVoiceBroadcastInfoEvent(state, client.getUserId());
});
itShouldReturnTrueTrue();
@ -141,7 +146,7 @@ describe("hasRoomLiveVoiceBroadcast", () => {
describe("when there is a live broadcast from another user", () => {
beforeEach(() => {
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Resumed, otherUserId);
expectedEvent = addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Resumed, otherUserId);
});
itShouldReturnTrueFalse();