Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 6d03cb3

Browse files
grimhiltjustjanne
andauthored
Fix voice messages with multiple composers (#9208)
* Allow having multiple voice messages in composers Co-authored-by: grimhilt <[email protected]> Co-authored-by: Janne Mareike Koschinski <[email protected]>
1 parent 85f9230 commit 6d03cb3

File tree

3 files changed

+39
-24
lines changed

3 files changed

+39
-24
lines changed

src/components/views/rooms/MessageComposer.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,8 @@ export default class MessageComposer extends React.Component<IProps, IState> {
308308
};
309309

310310
private updateRecordingState() {
311-
this.voiceRecording = VoiceRecordingStore.instance.getActiveRecording(this.props.room.roomId);
311+
const voiceRecordingId = VoiceRecordingStore.getVoiceRecordingId(this.props.room, this.props.relation);
312+
this.voiceRecording = VoiceRecordingStore.instance.getActiveRecording(voiceRecordingId);
312313
if (this.voiceRecording) {
313314
// If the recording has already started, it's probably a cached one.
314315
if (this.voiceRecording.hasRecording && !this.voiceRecording.isRecording) {
@@ -323,7 +324,8 @@ export default class MessageComposer extends React.Component<IProps, IState> {
323324

324325
private onRecordingStarted = () => {
325326
// update the recording instance, just in case
326-
this.voiceRecording = VoiceRecordingStore.instance.getActiveRecording(this.props.room.roomId);
327+
const voiceRecordingId = VoiceRecordingStore.getVoiceRecordingId(this.props.room, this.props.relation);
328+
this.voiceRecording = VoiceRecordingStore.instance.getActiveRecording(voiceRecordingId);
327329
this.setState({
328330
haveRecording: !!this.voiceRecording,
329331
});

src/components/views/rooms/VoiceRecordComposerTile.tsx

+9-7
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,20 @@ interface IState {
6464
export default class VoiceRecordComposerTile extends React.PureComponent<IProps, IState> {
6565
static contextType = RoomContext;
6666
public context!: React.ContextType<typeof RoomContext>;
67+
private voiceRecordingId: string;
6768

6869
public constructor(props: IProps) {
6970
super(props);
7071

7172
this.state = {
7273
recorder: null, // no recording started by default
7374
};
75+
76+
this.voiceRecordingId = VoiceRecordingStore.getVoiceRecordingId(this.props.room, this.props.relation);
7477
}
7578

7679
public componentDidMount() {
77-
const recorder = VoiceRecordingStore.instance.getActiveRecording(this.props.room.roomId);
80+
const recorder = VoiceRecordingStore.instance.getActiveRecording(this.voiceRecordingId);
7881
if (recorder) {
7982
if (recorder.isRecording || !recorder.hasRecording) {
8083
logger.warn("Cached recording hasn't ended yet and might cause issues");
@@ -87,7 +90,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
8790
public async componentWillUnmount() {
8891
// Stop recording, but keep the recording memory (don't dispose it). This is to let the user
8992
// come back and finish working with it.
90-
const recording = VoiceRecordingStore.instance.getActiveRecording(this.props.room.roomId);
93+
const recording = VoiceRecordingStore.instance.getActiveRecording(this.voiceRecordingId);
9194
await recording?.stop();
9295

9396
// Clean up our listeners by binding a falsy recorder
@@ -106,7 +109,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
106109

107110
let upload: IUpload;
108111
try {
109-
upload = await this.state.recorder.upload(this.props.room.roomId);
112+
upload = await this.state.recorder.upload(this.voiceRecordingId);
110113
} catch (e) {
111114
logger.error("Error uploading voice message:", e);
112115

@@ -179,7 +182,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
179182
}
180183

181184
private async disposeRecording() {
182-
await VoiceRecordingStore.instance.disposeRecording(this.props.room.roomId);
185+
await VoiceRecordingStore.instance.disposeRecording(this.voiceRecordingId);
183186

184187
// Reset back to no recording, which means no phase (ie: restart component entirely)
185188
this.setState({ recorder: null, recordingPhase: null, didUploadFail: false });
@@ -232,8 +235,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
232235
try {
233236
// stop any noises which might be happening
234237
PlaybackManager.instance.pauseAllExcept(null);
235-
236-
const recorder = VoiceRecordingStore.instance.startRecording(this.props.room.roomId);
238+
const recorder = VoiceRecordingStore.instance.startRecording(this.voiceRecordingId);
237239
await recorder.start();
238240

239241
this.bindNewRecorder(recorder);
@@ -244,7 +246,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
244246
accessError();
245247

246248
// noinspection ES6MissingAwait - if this goes wrong we don't want it to affect the call stack
247-
VoiceRecordingStore.instance.disposeRecording(this.props.room.roomId);
249+
VoiceRecordingStore.instance.disposeRecording(this.voiceRecordingId);
248250
}
249251
};
250252

src/stores/VoiceRecordingStore.ts

+26-15
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,19 @@ limitations under the License.
1515
*/
1616

1717
import { Optional } from "matrix-events-sdk";
18+
import { Room } from "matrix-js-sdk/src/models/room";
19+
import { RelationType } from "matrix-js-sdk/src/@types/event";
20+
import { IEventRelation } from "matrix-js-sdk/src/models/event";
1821

1922
import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
2023
import defaultDispatcher from "../dispatcher/dispatcher";
2124
import { ActionPayload } from "../dispatcher/payloads";
2225
import { VoiceRecording } from "../audio/VoiceRecording";
2326

27+
const SEPARATOR = "|";
28+
2429
interface IState {
25-
[roomId: string]: Optional<VoiceRecording>;
30+
[voiceRecordingId: string]: Optional<VoiceRecording>;
2631
}
2732

2833
export class VoiceRecordingStore extends AsyncStoreWithClient<IState> {
@@ -45,48 +50,54 @@ export class VoiceRecordingStore extends AsyncStoreWithClient<IState> {
4550
return;
4651
}
4752

53+
public static getVoiceRecordingId(room: Room, relation?: IEventRelation): string {
54+
if (relation?.rel_type === "io.element.thread" || relation?.rel_type === RelationType.Thread) {
55+
return room.roomId + SEPARATOR + relation.event_id;
56+
} else {
57+
return room.roomId;
58+
}
59+
}
60+
4861
/**
4962
* Gets the active recording instance, if any.
50-
* @param {string} roomId The room ID to get the recording in.
63+
* @param {string} voiceRecordingId The room ID (with optionally the thread ID if in one) to get the recording in.
5164
* @returns {Optional<VoiceRecording>} The recording, if any.
5265
*/
53-
public getActiveRecording(roomId: string): Optional<VoiceRecording> {
54-
return this.state[roomId];
66+
public getActiveRecording(voiceRecordingId: string): Optional<VoiceRecording> {
67+
return this.state[voiceRecordingId];
5568
}
5669

5770
/**
5871
* Starts a new recording if one isn't already in progress. Note that this simply
5972
* creates a recording instance - whether or not recording is actively in progress
6073
* can be seen via the VoiceRecording class.
61-
* @param {string} roomId The room ID to start recording in.
74+
* @param {string} voiceRecordingId The room ID (with optionally the thread ID if in one) to start recording in.
6275
* @returns {VoiceRecording} The recording.
6376
*/
64-
public startRecording(roomId: string): VoiceRecording {
77+
public startRecording(voiceRecordingId: string): VoiceRecording {
6578
if (!this.matrixClient) throw new Error("Cannot start a recording without a MatrixClient");
66-
if (!roomId) throw new Error("Recording must be associated with a room");
67-
if (this.state[roomId]) throw new Error("A recording is already in progress");
79+
if (!voiceRecordingId) throw new Error("Recording must be associated with a room");
80+
if (this.state[voiceRecordingId]) throw new Error("A recording is already in progress");
6881

6982
const recording = new VoiceRecording(this.matrixClient);
7083

7184
// noinspection JSIgnoredPromiseFromCall - we can safely run this async
72-
this.updateState({ ...this.state, [roomId]: recording });
85+
this.updateState({ ...this.state, [voiceRecordingId]: recording });
7386

7487
return recording;
7588
}
7689

7790
/**
7891
* Disposes of the current recording, no matter the state of it.
79-
* @param {string} roomId The room ID to dispose of the recording in.
92+
* @param {string} voiceRecordingId The room ID (with optionally the thread ID if in one) to dispose of the recording in.
8093
* @returns {Promise<void>} Resolves when complete.
8194
*/
82-
public disposeRecording(roomId: string): Promise<void> {
83-
if (this.state[roomId]) {
84-
this.state[roomId].destroy(); // stops internally
85-
}
95+
public disposeRecording(voiceRecordingId: string): Promise<void> {
96+
this.state[voiceRecordingId]?.destroy(); // stops internally
8697

8798
const {
8899
// eslint-disable-next-line @typescript-eslint/no-unused-vars
89-
[roomId]: _toDelete,
100+
[voiceRecordingId]: _toDelete,
90101
...newState
91102
} = this.state;
92103
// unexpectedly AsyncStore.updateState merges state

0 commit comments

Comments
 (0)