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

Commit 788dd90

Browse files
weeman1337Kerry
andauthored
Implement voice broadcast playback buffering (#9435)
Co-authored-by: Kerry <[email protected]>
1 parent 877c95d commit 788dd90

File tree

5 files changed

+201
-22
lines changed

5 files changed

+201
-22
lines changed

src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import {
2020
PlaybackControlButton,
2121
VoiceBroadcastHeader,
2222
VoiceBroadcastPlayback,
23+
VoiceBroadcastPlaybackState,
2324
} from "../..";
25+
import Spinner from "../../../components/views/elements/Spinner";
2426
import { useVoiceBroadcastPlayback } from "../../hooks/useVoiceBroadcastPlayback";
2527

2628
interface VoiceBroadcastPlaybackBodyProps {
@@ -38,6 +40,10 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
3840
playbackState,
3941
} = useVoiceBroadcastPlayback(playback);
4042

43+
const control = playbackState === VoiceBroadcastPlaybackState.Buffering
44+
? <Spinner />
45+
: <PlaybackControlButton onClick={toggle} state={playbackState} />;
46+
4147
return (
4248
<div className="mx_VoiceBroadcastPlaybackBody">
4349
<VoiceBroadcastHeader
@@ -47,10 +53,7 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
4753
showBroadcast={true}
4854
/>
4955
<div className="mx_VoiceBroadcastPlaybackBody_controls">
50-
<PlaybackControlButton
51-
onClick={toggle}
52-
state={playbackState}
53-
/>
56+
{ control }
5457
</div>
5558
</div>
5659
);

src/voice-broadcast/models/VoiceBroadcastPlayback.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export enum VoiceBroadcastPlaybackState {
3636
Paused,
3737
Playing,
3838
Stopped,
39+
Buffering,
3940
}
4041

4142
export enum VoiceBroadcastPlaybackEvent {
@@ -91,7 +92,7 @@ export class VoiceBroadcastPlayback
9192
this.chunkRelationHelper.emitCurrent();
9293
}
9394

94-
private addChunkEvent(event: MatrixEvent): boolean {
95+
private addChunkEvent = async (event: MatrixEvent): Promise<boolean> => {
9596
const eventId = event.getId();
9697

9798
if (!eventId
@@ -102,8 +103,17 @@ export class VoiceBroadcastPlayback
102103
}
103104

104105
this.chunkEvents.set(eventId, event);
106+
107+
if (this.getState() !== VoiceBroadcastPlaybackState.Stopped) {
108+
await this.enqueueChunk(event);
109+
}
110+
111+
if (this.getState() === VoiceBroadcastPlaybackState.Buffering) {
112+
await this.start();
113+
}
114+
105115
return true;
106-
}
116+
};
107117

108118
private addInfoEvent = (event: MatrixEvent): void => {
109119
if (this.lastInfoEvent && this.lastInfoEvent.getTs() >= event.getTs()) {
@@ -149,20 +159,30 @@ export class VoiceBroadcastPlayback
149159
playback.on(UPDATE_EVENT, (state) => this.onPlaybackStateChange(playback, state));
150160
}
151161

152-
private onPlaybackStateChange(playback: Playback, newState: PlaybackState) {
162+
private async onPlaybackStateChange(playback: Playback, newState: PlaybackState) {
153163
if (newState !== PlaybackState.Stopped) {
154164
return;
155165
}
156166

157-
const next = this.queue[this.queue.indexOf(playback) + 1];
167+
await this.playNext(playback);
168+
}
169+
170+
private async playNext(current: Playback): Promise<void> {
171+
const next = this.queue[this.queue.indexOf(current) + 1];
158172

159173
if (next) {
174+
this.setState(VoiceBroadcastPlaybackState.Playing);
160175
this.currentlyPlaying = next;
161-
next.play();
176+
await next.play();
162177
return;
163178
}
164179

165-
this.setState(VoiceBroadcastPlaybackState.Stopped);
180+
if (this.getInfoState() === VoiceBroadcastInfoState.Stopped) {
181+
this.setState(VoiceBroadcastPlaybackState.Stopped);
182+
} else {
183+
// No more chunks available, although the broadcast is not finished → enter buffering state.
184+
this.setState(VoiceBroadcastPlaybackState.Buffering);
185+
}
166186
}
167187

168188
public async start(): Promise<void> {
@@ -174,14 +194,14 @@ export class VoiceBroadcastPlayback
174194
? 0 // start at the beginning for an ended voice broadcast
175195
: this.queue.length - 1; // start at the current chunk for an ongoing voice broadcast
176196

177-
if (this.queue.length === 0 || !this.queue[toPlayIndex]) {
178-
this.setState(VoiceBroadcastPlaybackState.Stopped);
197+
if (this.queue[toPlayIndex]) {
198+
this.setState(VoiceBroadcastPlaybackState.Playing);
199+
this.currentlyPlaying = this.queue[toPlayIndex];
200+
await this.currentlyPlaying.play();
179201
return;
180202
}
181203

182-
this.setState(VoiceBroadcastPlaybackState.Playing);
183-
this.currentlyPlaying = this.queue[toPlayIndex];
184-
await this.currentlyPlaying.play();
204+
this.setState(VoiceBroadcastPlaybackState.Buffering);
185205
}
186206

187207
public get length(): number {

test/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody-test.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ import React from "react";
1818
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
1919
import { render, RenderResult } from "@testing-library/react";
2020
import userEvent from "@testing-library/user-event";
21+
import { mocked } from "jest-mock";
2122

2223
import {
2324
VoiceBroadcastInfoEventType,
2425
VoiceBroadcastPlayback,
2526
VoiceBroadcastPlaybackBody,
27+
VoiceBroadcastPlaybackState,
2628
} from "../../../../src/voice-broadcast";
2729
import { mkEvent, stubClient } from "../../../test-utils";
2830

@@ -40,6 +42,7 @@ describe("VoiceBroadcastPlaybackBody", () => {
4042
let client: MatrixClient;
4143
let infoEvent: MatrixEvent;
4244
let playback: VoiceBroadcastPlayback;
45+
let renderResult: RenderResult;
4346

4447
beforeAll(() => {
4548
client = stubClient();
@@ -50,12 +53,18 @@ describe("VoiceBroadcastPlaybackBody", () => {
5053
room: roomId,
5154
user: userId,
5255
});
56+
});
57+
58+
beforeEach(() => {
5359
playback = new VoiceBroadcastPlayback(infoEvent, client);
5460
jest.spyOn(playback, "toggle");
61+
jest.spyOn(playback, "getState");
5562
});
5663

57-
describe("when rendering a broadcast", () => {
58-
let renderResult: RenderResult;
64+
describe("when rendering a buffering voice broadcast", () => {
65+
beforeEach(() => {
66+
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Buffering);
67+
});
5968

6069
beforeEach(() => {
6170
renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />);
@@ -64,6 +73,16 @@ describe("VoiceBroadcastPlaybackBody", () => {
6473
it("should render as expected", () => {
6574
expect(renderResult.container).toMatchSnapshot();
6675
});
76+
});
77+
78+
describe("when rendering a broadcast", () => {
79+
beforeEach(() => {
80+
renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />);
81+
});
82+
83+
it("should render as expected", () => {
84+
expect(renderResult.container).toMatchSnapshot();
85+
});
6786

6887
describe("and clicking the play button", () => {
6988
beforeEach(async () => {

test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,78 @@ exports[`VoiceBroadcastPlaybackBody when rendering a broadcast should render as
7777
</div>
7878
</div>
7979
`;
80+
81+
exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast should render as expected 1`] = `
82+
<div>
83+
<div
84+
class="mx_VoiceBroadcastPlaybackBody"
85+
>
86+
<div
87+
class="mx_VoiceBroadcastHeader"
88+
>
89+
<div
90+
data-testid="room-avatar"
91+
>
92+
room avatar:
93+
My room
94+
</div>
95+
<div
96+
class="mx_VoiceBroadcastHeader_content"
97+
>
98+
<div
99+
class="mx_VoiceBroadcastHeader_room"
100+
>
101+
My room
102+
</div>
103+
<div
104+
class="mx_VoiceBroadcastHeader_line"
105+
>
106+
<i
107+
aria-hidden="true"
108+
class="mx_Icon mx_Icon_16 mx_Icon_compound-secondary-content"
109+
role="presentation"
110+
style="mask-image: url(\\"image-file-stub\\");"
111+
/>
112+
@user:example.com
113+
</div>
114+
<div
115+
class="mx_VoiceBroadcastHeader_line"
116+
>
117+
<i
118+
aria-hidden="true"
119+
class="mx_Icon mx_Icon_16 mx_Icon_compound-secondary-content"
120+
role="presentation"
121+
style="mask-image: url(\\"image-file-stub\\");"
122+
/>
123+
Voice broadcast
124+
</div>
125+
</div>
126+
<div
127+
class="mx_LiveBadge"
128+
>
129+
<i
130+
aria-hidden="true"
131+
class="mx_Icon mx_Icon_16 mx_Icon_live-badge"
132+
role="presentation"
133+
style="mask-image: url(\\"image-file-stub\\");"
134+
/>
135+
Live
136+
</div>
137+
</div>
138+
<div
139+
class="mx_VoiceBroadcastPlaybackBody_controls"
140+
>
141+
<div
142+
class="mx_Spinner"
143+
>
144+
<div
145+
aria-label="Loading..."
146+
class="mx_Spinner_icon"
147+
role="progressbar"
148+
style="width: 32px; height: 32px;"
149+
/>
150+
</div>
151+
</div>
152+
</div>
153+
</div>
154+
`;

0 commit comments

Comments
 (0)