Handle broadcast chunk errors (#9970)
* Use strings for broadcast playback states * Handle broadcast decode errors
This commit is contained in:
parent
60edb85a1a
commit
533b250bb6
10 changed files with 483 additions and 275 deletions
|
@ -659,6 +659,7 @@
|
|||
"%(senderName)s ended a <a>voice broadcast</a>": "%(senderName)s ended a <a>voice broadcast</a>",
|
||||
"You ended a voice broadcast": "You ended a voice broadcast",
|
||||
"%(senderName)s ended a voice broadcast": "%(senderName)s ended a voice broadcast",
|
||||
"Unable to play this voice broadcast": "Unable to play this voice broadcast",
|
||||
"Stop live broadcasting?": "Stop live broadcasting?",
|
||||
"Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.": "Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.",
|
||||
"Yes, stop broadcast": "Yes, stop broadcast",
|
||||
|
|
32
src/voice-broadcast/components/atoms/VoiceBroadcastError.tsx
Normal file
32
src/voice-broadcast/components/atoms/VoiceBroadcastError.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
Copyright 2023 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 React from "react";
|
||||
|
||||
import { Icon as WarningIcon } from "../../../../res/img/element-icons/warning.svg";
|
||||
|
||||
interface Props {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export const VoiceBroadcastError: React.FC<Props> = ({ message }) => {
|
||||
return (
|
||||
<div className="mx_VoiceBroadcastRecordingConnectionError">
|
||||
<WarningIcon className="mx_Icon mx_Icon_16" />
|
||||
{message}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -18,6 +18,7 @@ import React, { ReactElement } from "react";
|
|||
import classNames from "classnames";
|
||||
|
||||
import {
|
||||
VoiceBroadcastError,
|
||||
VoiceBroadcastHeader,
|
||||
VoiceBroadcastPlayback,
|
||||
VoiceBroadcastPlaybackControl,
|
||||
|
@ -67,6 +68,24 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
|
|||
["mx_VoiceBroadcastBody--pip"]: pip,
|
||||
});
|
||||
|
||||
const content =
|
||||
playbackState === VoiceBroadcastPlaybackState.Error ? (
|
||||
<VoiceBroadcastError message={playback.errorMessage} />
|
||||
) : (
|
||||
<>
|
||||
<div className="mx_VoiceBroadcastBody_controls">
|
||||
{seekBackwardButton}
|
||||
<VoiceBroadcastPlaybackControl state={playbackState} onClick={toggle} />
|
||||
{seekForwardButton}
|
||||
</div>
|
||||
<SeekBar playback={playback} />
|
||||
<div className="mx_VoiceBroadcastBody_timerow">
|
||||
<Clock seconds={times.position} />
|
||||
<Clock seconds={-times.timeLeft} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<VoiceBroadcastHeader
|
||||
|
@ -77,16 +96,7 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
|
|||
showBroadcast={playbackState !== VoiceBroadcastPlaybackState.Buffering}
|
||||
showBuffering={playbackState === VoiceBroadcastPlaybackState.Buffering}
|
||||
/>
|
||||
<div className="mx_VoiceBroadcastBody_controls">
|
||||
{seekBackwardButton}
|
||||
<VoiceBroadcastPlaybackControl state={playbackState} onClick={toggle} />
|
||||
{seekForwardButton}
|
||||
</div>
|
||||
<SeekBar playback={playback} />
|
||||
<div className="mx_VoiceBroadcastBody_timerow">
|
||||
<Clock seconds={times.position} />
|
||||
<Clock seconds={-times.timeLeft} />
|
||||
</div>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -27,6 +27,7 @@ export * from "./audio/VoiceBroadcastRecorder";
|
|||
export * from "./components/VoiceBroadcastBody";
|
||||
export * from "./components/atoms/LiveBadge";
|
||||
export * from "./components/atoms/VoiceBroadcastControl";
|
||||
export * from "./components/atoms/VoiceBroadcastError";
|
||||
export * from "./components/atoms/VoiceBroadcastHeader";
|
||||
export * from "./components/atoms/VoiceBroadcastPlaybackControl";
|
||||
export * from "./components/atoms/VoiceBroadcastRecordingConnectionError";
|
||||
|
|
|
@ -43,12 +43,14 @@ import {
|
|||
import { RelationsHelper, RelationsHelperEvent } from "../../events/RelationsHelper";
|
||||
import { VoiceBroadcastChunkEvents } from "../utils/VoiceBroadcastChunkEvents";
|
||||
import { determineVoiceBroadcastLiveness } from "../utils/determineVoiceBroadcastLiveness";
|
||||
import { _t } from "../../languageHandler";
|
||||
|
||||
export enum VoiceBroadcastPlaybackState {
|
||||
Paused,
|
||||
Playing,
|
||||
Stopped,
|
||||
Buffering,
|
||||
Paused = "pause",
|
||||
Playing = "playing",
|
||||
Stopped = "stopped",
|
||||
Buffering = "buffering",
|
||||
Error = "error",
|
||||
}
|
||||
|
||||
export enum VoiceBroadcastPlaybackEvent {
|
||||
|
@ -205,12 +207,24 @@ export class VoiceBroadcastPlayback
|
|||
}
|
||||
};
|
||||
|
||||
private async tryLoadPlayback(chunkEvent: MatrixEvent): Promise<void> {
|
||||
try {
|
||||
return await this.loadPlayback(chunkEvent);
|
||||
} catch (err) {
|
||||
logger.warn("Unable to load broadcast playback", {
|
||||
message: err.message,
|
||||
broadcastId: this.infoEvent.getId(),
|
||||
chunkId: chunkEvent.getId(),
|
||||
});
|
||||
this.setError();
|
||||
}
|
||||
}
|
||||
|
||||
private async loadPlayback(chunkEvent: MatrixEvent): Promise<void> {
|
||||
const eventId = chunkEvent.getId();
|
||||
|
||||
if (!eventId) {
|
||||
logger.warn("got voice broadcast chunk event without ID", this.infoEvent, chunkEvent);
|
||||
return;
|
||||
throw new Error("Broadcast chunk event without Id occurred");
|
||||
}
|
||||
|
||||
const helper = new MediaEventHelper(chunkEvent);
|
||||
|
@ -311,16 +325,28 @@ export class VoiceBroadcastPlayback
|
|||
private async playEvent(event: MatrixEvent): Promise<void> {
|
||||
this.setState(VoiceBroadcastPlaybackState.Playing);
|
||||
this.currentlyPlaying = event;
|
||||
const playback = await this.getOrLoadPlaybackForEvent(event);
|
||||
const playback = await this.tryGetOrLoadPlaybackForEvent(event);
|
||||
playback?.play();
|
||||
}
|
||||
|
||||
private async tryGetOrLoadPlaybackForEvent(event: MatrixEvent): Promise<Playback | undefined> {
|
||||
try {
|
||||
return await this.getOrLoadPlaybackForEvent(event);
|
||||
} catch (err) {
|
||||
logger.warn("Unable to load broadcast playback", {
|
||||
message: err.message,
|
||||
broadcastId: this.infoEvent.getId(),
|
||||
chunkId: event.getId(),
|
||||
});
|
||||
this.setError();
|
||||
}
|
||||
}
|
||||
|
||||
private async getOrLoadPlaybackForEvent(event: MatrixEvent): Promise<Playback | undefined> {
|
||||
const eventId = event.getId();
|
||||
|
||||
if (!eventId) {
|
||||
logger.warn("event without id occurred");
|
||||
return;
|
||||
throw new Error("Broadcast chunk event without Id occurred");
|
||||
}
|
||||
|
||||
if (!this.playbacks.has(eventId)) {
|
||||
|
@ -330,13 +356,12 @@ export class VoiceBroadcastPlayback
|
|||
const playback = this.playbacks.get(eventId);
|
||||
|
||||
if (!playback) {
|
||||
// logging error, because this should not happen
|
||||
logger.warn("unable to find playback for event", event);
|
||||
throw new Error(`Unable to find playback for event ${event.getId()}`);
|
||||
}
|
||||
|
||||
// try to load the playback for the next event for a smooth(er) playback
|
||||
const nextEvent = this.chunkEvents.getNext(event);
|
||||
if (nextEvent) this.loadPlayback(nextEvent);
|
||||
if (nextEvent) this.tryLoadPlayback(nextEvent);
|
||||
|
||||
return playback;
|
||||
}
|
||||
|
@ -405,8 +430,8 @@ export class VoiceBroadcastPlayback
|
|||
}
|
||||
|
||||
const currentPlayback = this.getCurrentPlayback();
|
||||
const skipToPlayback = await this.tryGetOrLoadPlaybackForEvent(event);
|
||||
const currentPlaybackEvent = this.currentlyPlaying;
|
||||
const skipToPlayback = await this.getOrLoadPlaybackForEvent(event);
|
||||
|
||||
if (!skipToPlayback) {
|
||||
logger.warn("voice broadcast chunk to skip to not found", event);
|
||||
|
@ -464,6 +489,9 @@ export class VoiceBroadcastPlayback
|
|||
}
|
||||
|
||||
public stop(): void {
|
||||
// error is a final state
|
||||
if (this.getState() === VoiceBroadcastPlaybackState.Error) return;
|
||||
|
||||
this.setState(VoiceBroadcastPlaybackState.Stopped);
|
||||
this.getCurrentPlayback()?.stop();
|
||||
this.currentlyPlaying = null;
|
||||
|
@ -471,6 +499,9 @@ export class VoiceBroadcastPlayback
|
|||
}
|
||||
|
||||
public pause(): void {
|
||||
// error is a final state
|
||||
if (this.getState() === VoiceBroadcastPlaybackState.Error) return;
|
||||
|
||||
// stopped voice broadcasts cannot be paused
|
||||
if (this.getState() === VoiceBroadcastPlaybackState.Stopped) return;
|
||||
|
||||
|
@ -479,6 +510,9 @@ export class VoiceBroadcastPlayback
|
|||
}
|
||||
|
||||
public resume(): void {
|
||||
// error is a final state
|
||||
if (this.getState() === VoiceBroadcastPlaybackState.Error) return;
|
||||
|
||||
if (!this.currentlyPlaying) {
|
||||
// no playback to resume, start from the beginning
|
||||
this.start();
|
||||
|
@ -496,6 +530,9 @@ export class VoiceBroadcastPlayback
|
|||
* paused → playing
|
||||
*/
|
||||
public async toggle(): Promise<void> {
|
||||
// error is a final state
|
||||
if (this.getState() === VoiceBroadcastPlaybackState.Error) return;
|
||||
|
||||
if (this.state === VoiceBroadcastPlaybackState.Stopped) {
|
||||
await this.start();
|
||||
return;
|
||||
|
@ -514,6 +551,9 @@ export class VoiceBroadcastPlayback
|
|||
}
|
||||
|
||||
private setState(state: VoiceBroadcastPlaybackState): void {
|
||||
// error is a final state
|
||||
if (this.getState() === VoiceBroadcastPlaybackState.Error) return;
|
||||
|
||||
if (this.state === state) {
|
||||
return;
|
||||
}
|
||||
|
@ -522,6 +562,16 @@ export class VoiceBroadcastPlayback
|
|||
this.emit(VoiceBroadcastPlaybackEvent.StateChanged, state, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set error state. Stop current playback, if any.
|
||||
*/
|
||||
private setError(): void {
|
||||
this.setState(VoiceBroadcastPlaybackState.Error);
|
||||
this.getCurrentPlayback()?.stop();
|
||||
this.currentlyPlaying = null;
|
||||
this.setPosition(0);
|
||||
}
|
||||
|
||||
public getInfoState(): VoiceBroadcastInfoState {
|
||||
return this.infoState;
|
||||
}
|
||||
|
@ -536,6 +586,10 @@ export class VoiceBroadcastPlayback
|
|||
this.setLiveness(determineVoiceBroadcastLiveness(this.infoState));
|
||||
}
|
||||
|
||||
public get errorMessage(): string {
|
||||
return this.getState() === VoiceBroadcastPlaybackState.Error ? _t("Unable to play this voice broadcast") : "";
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.chunkRelationHelper.destroy();
|
||||
this.infoRelationHelper.destroy();
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<VoiceBroadcastPlaybackControl /> should render state 0 as expected 1`] = `
|
||||
exports[`<VoiceBroadcastPlaybackControl /> should render state buffering as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<VoiceBroadcastPlaybackControl /> should render state pause as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="resume voice broadcast"
|
||||
|
@ -15,7 +30,7 @@ exports[`<VoiceBroadcastPlaybackControl /> should render state 0 as expected 1`]
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`<VoiceBroadcastPlaybackControl /> should render state 1 as expected 1`] = `
|
||||
exports[`<VoiceBroadcastPlaybackControl /> should render state playing as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
|
@ -30,7 +45,7 @@ exports[`<VoiceBroadcastPlaybackControl /> should render state 1 as expected 1`]
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`<VoiceBroadcastPlaybackControl /> should render state 2 as expected 1`] = `
|
||||
exports[`<VoiceBroadcastPlaybackControl /> should render state stopped as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="play voice broadcast"
|
||||
|
@ -44,18 +59,3 @@ exports[`<VoiceBroadcastPlaybackControl /> should render state 2 as expected 1`]
|
|||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<VoiceBroadcastPlaybackControl /> should render state 3 as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
VoiceBroadcastPlaybackEvent,
|
||||
VoiceBroadcastPlaybackState,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { filterConsole, stubClient } from "../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../utils/test-utils";
|
||||
import dis from "../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
|
@ -53,6 +53,11 @@ describe("VoiceBroadcastPlaybackBody", () => {
|
|||
let playback: VoiceBroadcastPlayback;
|
||||
let renderResult: RenderResult;
|
||||
|
||||
filterConsole(
|
||||
// expected for some tests
|
||||
"voice broadcast chunk event to skip to not found",
|
||||
);
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
mocked(client.relations).mockClear();
|
||||
|
@ -214,6 +219,17 @@ describe("VoiceBroadcastPlaybackBody", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("when rendering an error broadcast", () => {
|
||||
beforeEach(() => {
|
||||
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Error);
|
||||
renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([
|
||||
[VoiceBroadcastPlaybackState.Paused, "not-live"],
|
||||
[VoiceBroadcastPlaybackState.Playing, "live"],
|
||||
|
|
|
@ -1,235 +1,5 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a 0/not-live broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="30s backward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="resume voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl mx_VoiceBroadcastControl-play"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="30s forward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="mx_SeekBar"
|
||||
max="1"
|
||||
min="0"
|
||||
step="0.001"
|
||||
style="--fillTo: 0;"
|
||||
tabindex="0"
|
||||
type="range"
|
||||
value="0"
|
||||
/>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_timerow"
|
||||
>
|
||||
<span
|
||||
class="mx_Clock"
|
||||
>
|
||||
00:00
|
||||
</span>
|
||||
<span
|
||||
class="mx_Clock"
|
||||
>
|
||||
-23:42
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a 1/live broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="30s backward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="30s forward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="mx_SeekBar"
|
||||
max="1"
|
||||
min="0"
|
||||
step="0.001"
|
||||
style="--fillTo: 0;"
|
||||
tabindex="0"
|
||||
type="range"
|
||||
value="0"
|
||||
/>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_timerow"
|
||||
>
|
||||
<span
|
||||
class="mx_Clock"
|
||||
>
|
||||
00:00
|
||||
</span>
|
||||
<span
|
||||
class="mx_Clock"
|
||||
>
|
||||
-23:42
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
|
@ -357,6 +127,117 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a pause/not-live broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="30s backward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="resume voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl mx_VoiceBroadcastControl-play"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="30s forward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="mx_SeekBar"
|
||||
max="1"
|
||||
min="0"
|
||||
step="0.001"
|
||||
style="--fillTo: 0;"
|
||||
tabindex="0"
|
||||
type="range"
|
||||
value="0"
|
||||
/>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_timerow"
|
||||
>
|
||||
<span
|
||||
class="mx_Clock"
|
||||
>
|
||||
00:00
|
||||
</span>
|
||||
<span
|
||||
class="mx_Clock"
|
||||
>
|
||||
-23:42
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a playing broadcast in pip mode should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
|
@ -591,6 +472,125 @@ exports[`VoiceBroadcastPlaybackBody when rendering a playing broadcast should re
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a playing/live broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="30s backward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="30s forward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="mx_SeekBar"
|
||||
max="1"
|
||||
min="0"
|
||||
step="0.001"
|
||||
style="--fillTo: 0;"
|
||||
tabindex="0"
|
||||
type="range"
|
||||
value="0"
|
||||
/>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_timerow"
|
||||
>
|
||||
<span
|
||||
class="mx_Clock"
|
||||
>
|
||||
00:00
|
||||
</span>
|
||||
<span
|
||||
class="mx_Clock"
|
||||
>
|
||||
-23:42
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a stopped broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
|
@ -681,3 +681,64 @@ exports[`VoiceBroadcastPlaybackBody when rendering a stopped broadcast should re
|
|||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering an error broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastRecordingConnectionError"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Unable to play this voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<VoiceBroadcastSmallPlaybackBody /> when rendering a { state: 0, liveness: 'not-live' }/%s broadcast should render as expected 1`] = `
|
||||
exports[`<VoiceBroadcastSmallPlaybackBody /> when rendering a { state: 'pause', liveness: 'not-live' }/%s broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip mx_VoiceBroadcastBody--small"
|
||||
|
@ -76,7 +76,7 @@ exports[`<VoiceBroadcastSmallPlaybackBody /> when rendering a { state: 0, livene
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`<VoiceBroadcastSmallPlaybackBody /> when rendering a { state: 1, liveness: 'live' }/%s broadcast should render as expected 1`] = `
|
||||
exports[`<VoiceBroadcastSmallPlaybackBody /> when rendering a { state: 'playing', liveness: 'live' }/%s broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip mx_VoiceBroadcastBody--small"
|
||||
|
|
|
@ -186,7 +186,11 @@ describe("VoiceBroadcastPlayback", () => {
|
|||
});
|
||||
};
|
||||
|
||||
filterConsole("Starting load of AsyncWrapper for modal");
|
||||
filterConsole(
|
||||
"Starting load of AsyncWrapper for modal",
|
||||
// expected for some tests
|
||||
"Unable to load broadcast playback",
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
|
@ -283,6 +287,35 @@ describe("VoiceBroadcastPlayback", () => {
|
|||
expect(playback.durationSeconds).toEqual(6.5);
|
||||
});
|
||||
|
||||
describe("and starting a playback with a broken chunk", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(chunk2Playback.prepare).mockRejectedValue("Error decoding chunk");
|
||||
await playback.start();
|
||||
});
|
||||
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Error);
|
||||
|
||||
it("start() should keep it in the error state)", async () => {
|
||||
await playback.start();
|
||||
expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Error);
|
||||
});
|
||||
|
||||
it("stop() should keep it in the error state)", () => {
|
||||
playback.stop();
|
||||
expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Error);
|
||||
});
|
||||
|
||||
it("toggle() should keep it in the error state)", async () => {
|
||||
await playback.toggle();
|
||||
expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Error);
|
||||
});
|
||||
|
||||
it("pause() should keep it in the error state)", () => {
|
||||
playback.pause();
|
||||
expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and an event with the same transaction Id occurs", () => {
|
||||
beforeEach(() => {
|
||||
room.addLiveEvents([chunk2BEvent]);
|
||||
|
|
Loading…
Reference in a new issue