From 67f58e235e3da5fa7a22c9e89040292a177a382e Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Mon, 17 Oct 2022 15:41:07 +0200 Subject: [PATCH] Implement voice broadcast playback buffering --- .../molecules/VoiceBroadcastPlaybackBody.tsx | 11 ++- .../models/VoiceBroadcastPlayback.ts | 42 ++++++++--- .../VoiceBroadcastPlaybackBody-test.tsx | 23 +++++- .../VoiceBroadcastPlaybackBody-test.tsx.snap | 75 +++++++++++++++++++ .../models/VoiceBroadcastPlayback-test.ts | 72 ++++++++++++++++-- 5 files changed, 201 insertions(+), 22 deletions(-) diff --git a/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx b/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx index 8edc5d0d9a8..035b3ce6e57 100644 --- a/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx +++ b/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx @@ -20,7 +20,9 @@ import { PlaybackControlButton, VoiceBroadcastHeader, VoiceBroadcastPlayback, + VoiceBroadcastPlaybackState, } from "../.."; +import Spinner from "../../../components/views/elements/Spinner"; import { useVoiceBroadcastPlayback } from "../../hooks/useVoiceBroadcastPlayback"; interface VoiceBroadcastPlaybackBodyProps { @@ -38,6 +40,10 @@ export const VoiceBroadcastPlaybackBody: React.FC + : ; + return (
- + { control }
); diff --git a/src/voice-broadcast/models/VoiceBroadcastPlayback.ts b/src/voice-broadcast/models/VoiceBroadcastPlayback.ts index 16ae9317e0e..641deb66ad1 100644 --- a/src/voice-broadcast/models/VoiceBroadcastPlayback.ts +++ b/src/voice-broadcast/models/VoiceBroadcastPlayback.ts @@ -36,6 +36,7 @@ export enum VoiceBroadcastPlaybackState { Paused, Playing, Stopped, + Buffering, } export enum VoiceBroadcastPlaybackEvent { @@ -91,7 +92,7 @@ export class VoiceBroadcastPlayback this.chunkRelationHelper.emitCurrent(); } - private addChunkEvent(event: MatrixEvent): boolean { + private addChunkEvent = async (event: MatrixEvent): Promise => { const eventId = event.getId(); if (!eventId @@ -102,8 +103,17 @@ export class VoiceBroadcastPlayback } this.chunkEvents.set(eventId, event); + + if (this.getState() !== VoiceBroadcastPlaybackState.Stopped) { + await this.enqueueChunk(event); + } + + if (this.getState() === VoiceBroadcastPlaybackState.Buffering) { + await this.start(); + } + return true; - } + }; private addInfoEvent = (event: MatrixEvent): void => { if (this.lastInfoEvent && this.lastInfoEvent.getTs() >= event.getTs()) { @@ -149,20 +159,30 @@ export class VoiceBroadcastPlayback playback.on(UPDATE_EVENT, (state) => this.onPlaybackStateChange(playback, state)); } - private onPlaybackStateChange(playback: Playback, newState: PlaybackState) { + private async onPlaybackStateChange(playback: Playback, newState: PlaybackState) { if (newState !== PlaybackState.Stopped) { return; } - const next = this.queue[this.queue.indexOf(playback) + 1]; + await this.playNext(playback); + } + + private async playNext(current: Playback): Promise { + const next = this.queue[this.queue.indexOf(current) + 1]; if (next) { + this.setState(VoiceBroadcastPlaybackState.Playing); this.currentlyPlaying = next; - next.play(); + await next.play(); return; } - this.setState(VoiceBroadcastPlaybackState.Stopped); + if (this.getInfoState() === VoiceBroadcastInfoState.Stopped) { + this.setState(VoiceBroadcastPlaybackState.Stopped); + } else { + // No more chunks available, although the broadcast is not finished → enter buffering state. + this.setState(VoiceBroadcastPlaybackState.Buffering); + } } public async start(): Promise { @@ -174,14 +194,14 @@ export class VoiceBroadcastPlayback ? 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.length === 0 || !this.queue[toPlayIndex]) { - this.setState(VoiceBroadcastPlaybackState.Stopped); + if (this.queue[toPlayIndex]) { + this.setState(VoiceBroadcastPlaybackState.Playing); + this.currentlyPlaying = this.queue[toPlayIndex]; + await this.currentlyPlaying.play(); return; } - this.setState(VoiceBroadcastPlaybackState.Playing); - this.currentlyPlaying = this.queue[toPlayIndex]; - await this.currentlyPlaying.play(); + this.setState(VoiceBroadcastPlaybackState.Buffering); } public get length(): number { diff --git a/test/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody-test.tsx b/test/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody-test.tsx index a4269533c3b..63c4b76fbd9 100644 --- a/test/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody-test.tsx +++ b/test/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody-test.tsx @@ -18,11 +18,13 @@ import React from "react"; import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { render, RenderResult } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import { mocked } from "jest-mock"; import { VoiceBroadcastInfoEventType, VoiceBroadcastPlayback, VoiceBroadcastPlaybackBody, + VoiceBroadcastPlaybackState, } from "../../../../src/voice-broadcast"; import { mkEvent, stubClient } from "../../../test-utils"; @@ -40,6 +42,7 @@ describe("VoiceBroadcastPlaybackBody", () => { let client: MatrixClient; let infoEvent: MatrixEvent; let playback: VoiceBroadcastPlayback; + let renderResult: RenderResult; beforeAll(() => { client = stubClient(); @@ -50,12 +53,18 @@ describe("VoiceBroadcastPlaybackBody", () => { room: roomId, user: userId, }); + }); + + beforeEach(() => { playback = new VoiceBroadcastPlayback(infoEvent, client); jest.spyOn(playback, "toggle"); + jest.spyOn(playback, "getState"); }); - describe("when rendering a broadcast", () => { - let renderResult: RenderResult; + describe("when rendering a buffering voice broadcast", () => { + beforeEach(() => { + mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Buffering); + }); beforeEach(() => { renderResult = render(); @@ -64,6 +73,16 @@ describe("VoiceBroadcastPlaybackBody", () => { it("should render as expected", () => { expect(renderResult.container).toMatchSnapshot(); }); + }); + + describe("when rendering a broadcast", () => { + beforeEach(() => { + renderResult = render(); + }); + + it("should render as expected", () => { + expect(renderResult.container).toMatchSnapshot(); + }); describe("and clicking the play button", () => { beforeEach(async () => { diff --git a/test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap b/test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap index b2515a78c22..beeb3f9ee33 100644 --- a/test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap +++ b/test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap @@ -77,3 +77,78 @@ exports[`VoiceBroadcastPlaybackBody when rendering a broadcast should render as `; + +exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast should render as expected 1`] = ` +
+
+
+
+ room avatar: + My room +
+
+
+ My room +
+
+