mirror of
https://github.com/element-hq/element-web
synced 2024-11-26 19:26:04 +03:00
Show room broadcast when ending a recording (#9841)
This commit is contained in:
parent
3ce064731b
commit
91e078d96b
4 changed files with 86 additions and 12 deletions
|
@ -54,6 +54,8 @@ import { ThreadPayload } from "../dispatcher/payloads/ThreadPayload";
|
||||||
import {
|
import {
|
||||||
doClearCurrentVoiceBroadcastPlaybackIfStopped,
|
doClearCurrentVoiceBroadcastPlaybackIfStopped,
|
||||||
doMaybeSetCurrentVoiceBroadcastPlayback,
|
doMaybeSetCurrentVoiceBroadcastPlayback,
|
||||||
|
VoiceBroadcastRecording,
|
||||||
|
VoiceBroadcastRecordingsStoreEvent,
|
||||||
} from "../voice-broadcast";
|
} from "../voice-broadcast";
|
||||||
import { IRoomStateEventsActionPayload } from "../actions/MatrixActionCreators";
|
import { IRoomStateEventsActionPayload } from "../actions/MatrixActionCreators";
|
||||||
import { showCantStartACallDialog } from "../voice-broadcast/utils/showCantStartACallDialog";
|
import { showCantStartACallDialog } from "../voice-broadcast/utils/showCantStartACallDialog";
|
||||||
|
@ -153,6 +155,10 @@ export class RoomViewStore extends EventEmitter {
|
||||||
public constructor(dis: MatrixDispatcher, private readonly stores: SdkContextClass) {
|
public constructor(dis: MatrixDispatcher, private readonly stores: SdkContextClass) {
|
||||||
super();
|
super();
|
||||||
this.resetDispatcher(dis);
|
this.resetDispatcher(dis);
|
||||||
|
this.stores.voiceBroadcastRecordingsStore.addListener(
|
||||||
|
VoiceBroadcastRecordingsStoreEvent.CurrentChanged,
|
||||||
|
this.onCurrentBroadcastRecordingChanged,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addRoomListener(roomId: string, fn: Listener): void {
|
public addRoomListener(roomId: string, fn: Listener): void {
|
||||||
|
@ -167,13 +173,23 @@ export class RoomViewStore extends EventEmitter {
|
||||||
this.emit(roomId, isActive);
|
this.emit(roomId, isActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onCurrentBroadcastRecordingChanged = (recording: VoiceBroadcastRecording | null) => {
|
||||||
|
if (recording === null) {
|
||||||
|
const room = this.stores.client?.getRoom(this.state.roomId || undefined);
|
||||||
|
|
||||||
|
if (room) {
|
||||||
|
this.doMaybeSetCurrentVoiceBroadcastPlayback(room);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private setState(newState: Partial<State>): void {
|
private setState(newState: Partial<State>): void {
|
||||||
// If values haven't changed, there's nothing to do.
|
// If values haven't changed, there's nothing to do.
|
||||||
// This only tries a shallow comparison, so unchanged objects will slip
|
// This only tries a shallow comparison, so unchanged objects will slip
|
||||||
// through, but that's probably okay for now.
|
// through, but that's probably okay for now.
|
||||||
let stateChanged = false;
|
let stateChanged = false;
|
||||||
for (const key of Object.keys(newState)) {
|
for (const key of Object.keys(newState)) {
|
||||||
if (this.state[key] !== newState[key]) {
|
if (this.state[key as keyof State] !== newState[key as keyof State]) {
|
||||||
stateChanged = true;
|
stateChanged = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ export enum VoiceBroadcastRecordingsStoreEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EventMap {
|
interface EventMap {
|
||||||
[VoiceBroadcastRecordingsStoreEvent.CurrentChanged]: (recording: VoiceBroadcastRecording) => void;
|
[VoiceBroadcastRecordingsStoreEvent.CurrentChanged]: (recording: VoiceBroadcastRecording | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -41,17 +41,23 @@ export class VoiceBroadcastRecordingsStore extends TypedEventEmitter<VoiceBroadc
|
||||||
public setCurrent(current: VoiceBroadcastRecording): void {
|
public setCurrent(current: VoiceBroadcastRecording): void {
|
||||||
if (this.current === current) return;
|
if (this.current === current) return;
|
||||||
|
|
||||||
|
const infoEventId = current.infoEvent.getId();
|
||||||
|
|
||||||
|
if (!infoEventId) {
|
||||||
|
throw new Error("Got broadcast info event without Id");
|
||||||
|
}
|
||||||
|
|
||||||
if (this.current) {
|
if (this.current) {
|
||||||
this.current.off(VoiceBroadcastRecordingEvent.StateChanged, this.onCurrentStateChanged);
|
this.current.off(VoiceBroadcastRecordingEvent.StateChanged, this.onCurrentStateChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.current = current;
|
this.current = current;
|
||||||
this.current.on(VoiceBroadcastRecordingEvent.StateChanged, this.onCurrentStateChanged);
|
this.current.on(VoiceBroadcastRecordingEvent.StateChanged, this.onCurrentStateChanged);
|
||||||
this.recordings.set(current.infoEvent.getId(), current);
|
this.recordings.set(infoEventId, current);
|
||||||
this.emit(VoiceBroadcastRecordingsStoreEvent.CurrentChanged, current);
|
this.emit(VoiceBroadcastRecordingsStoreEvent.CurrentChanged, current);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCurrent(): VoiceBroadcastRecording {
|
public getCurrent(): VoiceBroadcastRecording | null {
|
||||||
return this.current;
|
return this.current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,11 +76,13 @@ export class VoiceBroadcastRecordingsStore extends TypedEventEmitter<VoiceBroadc
|
||||||
public getByInfoEvent(infoEvent: MatrixEvent, client: MatrixClient): VoiceBroadcastRecording {
|
public getByInfoEvent(infoEvent: MatrixEvent, client: MatrixClient): VoiceBroadcastRecording {
|
||||||
const infoEventId = infoEvent.getId();
|
const infoEventId = infoEvent.getId();
|
||||||
|
|
||||||
if (!this.recordings.has(infoEventId)) {
|
if (!infoEventId) {
|
||||||
this.recordings.set(infoEventId, new VoiceBroadcastRecording(infoEvent, client));
|
throw new Error("Got broadcast info event without Id");
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.recordings.get(infoEventId);
|
const recording = this.recordings.get(infoEventId) || new VoiceBroadcastRecording(infoEvent, client);
|
||||||
|
this.recordings.set(infoEventId, recording);
|
||||||
|
return recording;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onCurrentStateChanged = (state: VoiceBroadcastInfoState) => {
|
private onCurrentStateChanged = (state: VoiceBroadcastInfoState) => {
|
||||||
|
|
|
@ -79,6 +79,7 @@ jest.mock("../../src/utils/DMRoomMap", () => {
|
||||||
describe("RoomViewStore", function () {
|
describe("RoomViewStore", function () {
|
||||||
const userId = "@alice:server";
|
const userId = "@alice:server";
|
||||||
const roomId = "!randomcharacters:aser.ver";
|
const roomId = "!randomcharacters:aser.ver";
|
||||||
|
const roomId2 = "!room2:example.com";
|
||||||
// we need to change the alias to ensure cache misses as the cache exists
|
// we need to change the alias to ensure cache misses as the cache exists
|
||||||
// through all tests.
|
// through all tests.
|
||||||
let alias = "#somealias2:aser.ver";
|
let alias = "#somealias2:aser.ver";
|
||||||
|
@ -87,9 +88,14 @@ describe("RoomViewStore", function () {
|
||||||
getRoom: jest.fn(),
|
getRoom: jest.fn(),
|
||||||
getRoomIdForAlias: jest.fn(),
|
getRoomIdForAlias: jest.fn(),
|
||||||
isGuest: jest.fn(),
|
isGuest: jest.fn(),
|
||||||
getSafeUserId: jest.fn(),
|
getUserId: jest.fn().mockReturnValue(userId),
|
||||||
|
getSafeUserId: jest.fn().mockReturnValue(userId),
|
||||||
|
getDeviceId: jest.fn().mockReturnValue("ABC123"),
|
||||||
|
sendStateEvent: jest.fn().mockResolvedValue({}),
|
||||||
|
supportsExperimentalThreads: jest.fn(),
|
||||||
});
|
});
|
||||||
const room = new Room(roomId, mockClient, userId);
|
const room = new Room(roomId, mockClient, userId);
|
||||||
|
const room2 = new Room(roomId2, mockClient, userId);
|
||||||
|
|
||||||
const viewCall = async (): Promise<void> => {
|
const viewCall = async (): Promise<void> => {
|
||||||
dis.dispatch<ViewRoomPayload>({
|
dis.dispatch<ViewRoomPayload>({
|
||||||
|
@ -110,7 +116,11 @@ describe("RoomViewStore", function () {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
mockClient.credentials = { userId: userId };
|
mockClient.credentials = { userId: userId };
|
||||||
mockClient.joinRoom.mockResolvedValue(room);
|
mockClient.joinRoom.mockResolvedValue(room);
|
||||||
mockClient.getRoom.mockReturnValue(room);
|
mockClient.getRoom.mockImplementation((roomId: string): Room | null => {
|
||||||
|
if (roomId === room.roomId) return room;
|
||||||
|
if (roomId === room2.roomId) return room2;
|
||||||
|
return null;
|
||||||
|
});
|
||||||
mockClient.isGuest.mockReturnValue(false);
|
mockClient.isGuest.mockReturnValue(false);
|
||||||
mockClient.getSafeUserId.mockReturnValue(userId);
|
mockClient.getSafeUserId.mockReturnValue(userId);
|
||||||
|
|
||||||
|
@ -118,6 +128,7 @@ describe("RoomViewStore", function () {
|
||||||
dis = new MatrixDispatcher();
|
dis = new MatrixDispatcher();
|
||||||
slidingSyncManager = new MockSlidingSyncManager();
|
slidingSyncManager = new MockSlidingSyncManager();
|
||||||
stores = new TestSdkContext();
|
stores = new TestSdkContext();
|
||||||
|
stores.client = mockClient;
|
||||||
stores._SlidingSyncManager = slidingSyncManager;
|
stores._SlidingSyncManager = slidingSyncManager;
|
||||||
stores._PosthogAnalytics = new MockPosthogAnalytics();
|
stores._PosthogAnalytics = new MockPosthogAnalytics();
|
||||||
stores._SpaceStore = new MockSpaceStore();
|
stores._SpaceStore = new MockSpaceStore();
|
||||||
|
@ -142,7 +153,6 @@ describe("RoomViewStore", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("emits ActiveRoomChanged when the viewed room changes", async () => {
|
it("emits ActiveRoomChanged when the viewed room changes", async () => {
|
||||||
const roomId2 = "!roomid:2";
|
|
||||||
dis.dispatch({ action: Action.ViewRoom, room_id: roomId });
|
dis.dispatch({ action: Action.ViewRoom, room_id: roomId });
|
||||||
let payload = (await untilDispatch(Action.ActiveRoomChanged, dis)) as ActiveRoomChangedPayload;
|
let payload = (await untilDispatch(Action.ActiveRoomChanged, dis)) as ActiveRoomChangedPayload;
|
||||||
expect(payload.newRoomId).toEqual(roomId);
|
expect(payload.newRoomId).toEqual(roomId);
|
||||||
|
@ -155,7 +165,6 @@ describe("RoomViewStore", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("invokes room activity listeners when the viewed room changes", async () => {
|
it("invokes room activity listeners when the viewed room changes", async () => {
|
||||||
const roomId2 = "!roomid:2";
|
|
||||||
const callback = jest.fn();
|
const callback = jest.fn();
|
||||||
roomViewStore.addRoomListener(roomId, callback);
|
roomViewStore.addRoomListener(roomId, callback);
|
||||||
dis.dispatch({ action: Action.ViewRoom, room_id: roomId });
|
dis.dispatch({ action: Action.ViewRoom, room_id: roomId });
|
||||||
|
@ -225,7 +234,6 @@ describe("RoomViewStore", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("swaps to the replied event room if it is not the current room", async () => {
|
it("swaps to the replied event room if it is not the current room", async () => {
|
||||||
const roomId2 = "!room2:bar";
|
|
||||||
dis.dispatch({ action: Action.ViewRoom, room_id: roomId });
|
dis.dispatch({ action: Action.ViewRoom, room_id: roomId });
|
||||||
await untilDispatch(Action.ActiveRoomChanged, dis);
|
await untilDispatch(Action.ActiveRoomChanged, dis);
|
||||||
const replyToEvent = {
|
const replyToEvent = {
|
||||||
|
@ -295,6 +303,41 @@ describe("RoomViewStore", function () {
|
||||||
expect(Modal.createDialog).toMatchSnapshot();
|
expect(Modal.createDialog).toMatchSnapshot();
|
||||||
expect(roomViewStore.isViewingCall()).toBe(false);
|
expect(roomViewStore.isViewingCall()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("and viewing a room with a broadcast", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const broadcastEvent = mkVoiceBroadcastInfoStateEvent(
|
||||||
|
roomId2,
|
||||||
|
VoiceBroadcastInfoState.Started,
|
||||||
|
mockClient.getSafeUserId(),
|
||||||
|
"ABC123",
|
||||||
|
);
|
||||||
|
room2.addLiveEvents([broadcastEvent]);
|
||||||
|
|
||||||
|
stores.voiceBroadcastPlaybacksStore.getByInfoEvent(broadcastEvent, mockClient);
|
||||||
|
dis.dispatch({ action: Action.ViewRoom, room_id: roomId2 });
|
||||||
|
await untilDispatch(Action.ActiveRoomChanged, dis);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should continue recording", () => {
|
||||||
|
expect(stores.voiceBroadcastPlaybacksStore.getCurrent()).toBeNull();
|
||||||
|
expect(stores.voiceBroadcastRecordingsStore.getCurrent()?.getState()).toBe(
|
||||||
|
VoiceBroadcastInfoState.Started,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("and stopping the recording", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await stores.voiceBroadcastRecordingsStore.getCurrent()?.stop();
|
||||||
|
// check test precondition
|
||||||
|
expect(stores.voiceBroadcastRecordingsStore.getCurrent()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should view the broadcast", () => {
|
||||||
|
expect(stores.voiceBroadcastPlaybacksStore.getCurrent()?.infoEvent.getRoomId()).toBe(roomId2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Sliding Sync", function () {
|
describe("Sliding Sync", function () {
|
||||||
|
|
|
@ -69,6 +69,13 @@ describe("VoiceBroadcastRecordingsStore", () => {
|
||||||
recordings.off(VoiceBroadcastRecordingsStoreEvent.CurrentChanged, onCurrentChanged);
|
recordings.off(VoiceBroadcastRecordingsStoreEvent.CurrentChanged, onCurrentChanged);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("when setting a recording without info event Id, it should raise an error", () => {
|
||||||
|
infoEvent.event.event_id = undefined;
|
||||||
|
expect(() => {
|
||||||
|
recordings.setCurrent(recording);
|
||||||
|
}).toThrowError("Got broadcast info event without Id");
|
||||||
|
});
|
||||||
|
|
||||||
describe("when setting a current Voice Broadcast recording", () => {
|
describe("when setting a current Voice Broadcast recording", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
recordings.setCurrent(recording);
|
recordings.setCurrent(recording);
|
||||||
|
|
Loading…
Reference in a new issue