Tweak voice broadcast live icon (#9576)

This commit is contained in:
Michael Weimann 2022-11-16 16:13:59 +01:00 committed by GitHub
parent 973513cc75
commit cf3c899dd1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 262 additions and 73 deletions

View file

@ -25,3 +25,7 @@ limitations under the License.
gap: $spacing-4; gap: $spacing-4;
padding: 2px 4px; padding: 2px 4px;
} }
.mx_LiveBadge--grey {
background-color: $quaternary-content;
}

View file

@ -14,13 +14,27 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import classNames from "classnames";
import React from "react"; import React from "react";
import { Icon as LiveIcon } from "../../../../res/img/element-icons/live.svg"; import { Icon as LiveIcon } from "../../../../res/img/element-icons/live.svg";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
export const LiveBadge: React.FC = () => { interface Props {
return <div className="mx_LiveBadge"> grey?: boolean;
}
export const LiveBadge: React.FC<Props> = ({
grey = false,
}) => {
const liveBadgeClasses = classNames(
"mx_LiveBadge",
{
"mx_LiveBadge--grey": grey,
},
);
return <div className={liveBadgeClasses}>
<LiveIcon className="mx_Icon mx_Icon_16" /> <LiveIcon className="mx_Icon mx_Icon_16" />
{ _t("Live") } { _t("Live") }
</div>; </div>;

View file

@ -15,7 +15,7 @@ import React from "react";
import { Room } from "matrix-js-sdk/src/matrix"; import { Room } from "matrix-js-sdk/src/matrix";
import classNames from "classnames"; import classNames from "classnames";
import { LiveBadge } from "../.."; import { LiveBadge, VoiceBroadcastLiveness } from "../..";
import { Icon as LiveIcon } from "../../../../res/img/element-icons/live.svg"; import { Icon as LiveIcon } from "../../../../res/img/element-icons/live.svg";
import { Icon as MicrophoneIcon } from "../../../../res/img/voip/call-view/mic-on.svg"; import { Icon as MicrophoneIcon } from "../../../../res/img/voip/call-view/mic-on.svg";
import { Icon as TimerIcon } from "../../../../res/img/element-icons/Timer.svg"; import { Icon as TimerIcon } from "../../../../res/img/element-icons/Timer.svg";
@ -27,7 +27,7 @@ import Clock from "../../../components/views/audio_messages/Clock";
import { formatTimeLeft } from "../../../DateUtils"; import { formatTimeLeft } from "../../../DateUtils";
interface VoiceBroadcastHeaderProps { interface VoiceBroadcastHeaderProps {
live?: boolean; live?: VoiceBroadcastLiveness;
onCloseClick?: () => void; onCloseClick?: () => void;
onMicrophoneLineClick?: () => void; onMicrophoneLineClick?: () => void;
room: Room; room: Room;
@ -38,7 +38,7 @@ interface VoiceBroadcastHeaderProps {
} }
export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({ export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({
live = false, live = "not-live",
onCloseClick = () => {}, onCloseClick = () => {},
onMicrophoneLineClick, onMicrophoneLineClick,
room, room,
@ -54,7 +54,9 @@ export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({
</div> </div>
: null; : null;
const liveBadge = live ? <LiveBadge /> : null; const liveBadge = live === "not-live"
? null
: <LiveBadge grey={live === "grey"} />;
const closeButton = showClose const closeButton = showClose
? <AccessibleButton onClick={onCloseClick}> ? <AccessibleButton onClick={onCloseClick}>

View file

@ -39,7 +39,7 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
}) => { }) => {
const { const {
duration, duration,
live, liveness,
room, room,
sender, sender,
toggle, toggle,
@ -79,7 +79,7 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
return ( return (
<div className="mx_VoiceBroadcastBody"> <div className="mx_VoiceBroadcastBody">
<VoiceBroadcastHeader <VoiceBroadcastHeader
live={live} live={liveness}
microphoneLabel={sender?.name} microphoneLabel={sender?.name}
room={room} room={room}
showBroadcast={true} showBroadcast={true}

View file

@ -29,7 +29,7 @@ export const VoiceBroadcastRecordingBody: React.FC<VoiceBroadcastRecordingBodyPr
return ( return (
<div className="mx_VoiceBroadcastBody"> <div className="mx_VoiceBroadcastBody">
<VoiceBroadcastHeader <VoiceBroadcastHeader
live={live} live={live ? "live" : "grey"}
microphoneLabel={sender?.name} microphoneLabel={sender?.name}
room={room} room={room}
/> />

View file

@ -55,7 +55,7 @@ export const VoiceBroadcastRecordingPip: React.FC<VoiceBroadcastRecordingPipProp
className="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip" className="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip"
> >
<VoiceBroadcastHeader <VoiceBroadcastHeader
live={live} live={live ? "live" : "grey"}
room={room} room={room}
timeLeft={timeLeft} timeLeft={timeLeft}
/> />

View file

@ -19,7 +19,6 @@ import { useState } from "react";
import { useTypedEventEmitter } from "../../hooks/useEventEmitter"; import { useTypedEventEmitter } from "../../hooks/useEventEmitter";
import { MatrixClientPeg } from "../../MatrixClientPeg"; import { MatrixClientPeg } from "../../MatrixClientPeg";
import { import {
VoiceBroadcastInfoState,
VoiceBroadcastPlayback, VoiceBroadcastPlayback,
VoiceBroadcastPlaybackEvent, VoiceBroadcastPlaybackEvent,
VoiceBroadcastPlaybackState, VoiceBroadcastPlaybackState,
@ -41,13 +40,6 @@ export const useVoiceBroadcastPlayback = (playback: VoiceBroadcastPlayback) => {
}, },
); );
const [playbackInfoState, setPlaybackInfoState] = useState(playback.getInfoState());
useTypedEventEmitter(
playback,
VoiceBroadcastPlaybackEvent.InfoStateChanged,
setPlaybackInfoState,
);
const [duration, setDuration] = useState(playback.durationSeconds); const [duration, setDuration] = useState(playback.durationSeconds);
useTypedEventEmitter( useTypedEventEmitter(
playback, playback,
@ -55,9 +47,16 @@ export const useVoiceBroadcastPlayback = (playback: VoiceBroadcastPlayback) => {
d => setDuration(d / 1000), d => setDuration(d / 1000),
); );
const [liveness, setLiveness] = useState(playback.getLiveness());
useTypedEventEmitter(
playback,
VoiceBroadcastPlaybackEvent.LivenessChanged,
l => setLiveness(l),
);
return { return {
duration, duration,
live: playbackInfoState !== VoiceBroadcastInfoState.Stopped, liveness: liveness,
room: room, room: room,
sender: playback.infoEvent.sender, sender: playback.infoEvent.sender,
toggle: playbackToggle, toggle: playbackToggle,

View file

@ -74,7 +74,6 @@ export const useVoiceBroadcastRecording = (recording: VoiceBroadcastRecording) =
const live = [ const live = [
VoiceBroadcastInfoState.Started, VoiceBroadcastInfoState.Started,
VoiceBroadcastInfoState.Paused,
VoiceBroadcastInfoState.Resumed, VoiceBroadcastInfoState.Resumed,
].includes(recordingState); ].includes(recordingState);

View file

@ -52,6 +52,8 @@ export * from "./utils/VoiceBroadcastResumer";
export const VoiceBroadcastInfoEventType = "io.element.voice_broadcast_info"; export const VoiceBroadcastInfoEventType = "io.element.voice_broadcast_info";
export const VoiceBroadcastChunkEventType = "io.element.voice_broadcast_chunk"; export const VoiceBroadcastChunkEventType = "io.element.voice_broadcast_chunk";
export type VoiceBroadcastLiveness = "live" | "not-live" | "grey";
export enum VoiceBroadcastInfoState { export enum VoiceBroadcastInfoState {
Started = "started", Started = "started",
Paused = "paused", Paused = "paused",

View file

@ -30,7 +30,7 @@ import { PlaybackManager } from "../../audio/PlaybackManager";
import { UPDATE_EVENT } from "../../stores/AsyncStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore";
import { MediaEventHelper } from "../../utils/MediaEventHelper"; import { MediaEventHelper } from "../../utils/MediaEventHelper";
import { IDestroyable } from "../../utils/IDestroyable"; import { IDestroyable } from "../../utils/IDestroyable";
import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from ".."; import { VoiceBroadcastLiveness, VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "..";
import { RelationsHelper, RelationsHelperEvent } from "../../events/RelationsHelper"; import { RelationsHelper, RelationsHelperEvent } from "../../events/RelationsHelper";
import { VoiceBroadcastChunkEvents } from "../utils/VoiceBroadcastChunkEvents"; import { VoiceBroadcastChunkEvents } from "../utils/VoiceBroadcastChunkEvents";
@ -44,6 +44,7 @@ export enum VoiceBroadcastPlaybackState {
export enum VoiceBroadcastPlaybackEvent { export enum VoiceBroadcastPlaybackEvent {
PositionChanged = "position_changed", PositionChanged = "position_changed",
LengthChanged = "length_changed", LengthChanged = "length_changed",
LivenessChanged = "liveness_changed",
StateChanged = "state_changed", StateChanged = "state_changed",
InfoStateChanged = "info_state_changed", InfoStateChanged = "info_state_changed",
} }
@ -51,6 +52,7 @@ export enum VoiceBroadcastPlaybackEvent {
interface EventMap { interface EventMap {
[VoiceBroadcastPlaybackEvent.PositionChanged]: (position: number) => void; [VoiceBroadcastPlaybackEvent.PositionChanged]: (position: number) => void;
[VoiceBroadcastPlaybackEvent.LengthChanged]: (length: number) => void; [VoiceBroadcastPlaybackEvent.LengthChanged]: (length: number) => void;
[VoiceBroadcastPlaybackEvent.LivenessChanged]: (liveness: VoiceBroadcastLiveness) => void;
[VoiceBroadcastPlaybackEvent.StateChanged]: ( [VoiceBroadcastPlaybackEvent.StateChanged]: (
state: VoiceBroadcastPlaybackState, state: VoiceBroadcastPlaybackState,
playback: VoiceBroadcastPlayback playback: VoiceBroadcastPlayback
@ -70,6 +72,7 @@ export class VoiceBroadcastPlayback
/** @var current playback position in milliseconds */ /** @var current playback position in milliseconds */
private position = 0; private position = 0;
public readonly liveData = new SimpleObservable<number[]>(); public readonly liveData = new SimpleObservable<number[]>();
private liveness: VoiceBroadcastLiveness = "not-live";
// set vial addInfoEvent() in constructor // set vial addInfoEvent() in constructor
private infoState!: VoiceBroadcastInfoState; private infoState!: VoiceBroadcastInfoState;
@ -143,6 +146,7 @@ export class VoiceBroadcastPlayback
if (this.getState() === VoiceBroadcastPlaybackState.Buffering) { if (this.getState() === VoiceBroadcastPlaybackState.Buffering) {
await this.start(); await this.start();
this.updateLiveness();
} }
return true; return true;
@ -212,23 +216,19 @@ export class VoiceBroadcastPlayback
}; };
private setDuration(duration: number): void { private setDuration(duration: number): void {
const shouldEmit = this.duration !== duration; if (this.duration === duration) return;
this.duration = duration;
if (shouldEmit) { this.duration = duration;
this.emit(VoiceBroadcastPlaybackEvent.LengthChanged, this.duration); this.emit(VoiceBroadcastPlaybackEvent.LengthChanged, this.duration);
this.liveData.update([this.timeSeconds, this.durationSeconds]); this.liveData.update([this.timeSeconds, this.durationSeconds]);
}
} }
private setPosition(position: number): void { private setPosition(position: number): void {
const shouldEmit = this.position !== position; if (this.position === position) return;
this.position = position;
if (shouldEmit) { this.position = position;
this.emit(VoiceBroadcastPlaybackEvent.PositionChanged, this.position); this.emit(VoiceBroadcastPlaybackEvent.PositionChanged, this.position);
this.liveData.update([this.timeSeconds, this.durationSeconds]); this.liveData.update([this.timeSeconds, this.durationSeconds]);
}
} }
private onPlaybackStateChange = async (event: MatrixEvent, newState: PlaybackState): Promise<void> => { private onPlaybackStateChange = async (event: MatrixEvent, newState: PlaybackState): Promise<void> => {
@ -279,6 +279,42 @@ export class VoiceBroadcastPlayback
return playback; return playback;
} }
public getLiveness(): VoiceBroadcastLiveness {
return this.liveness;
}
private setLiveness(liveness: VoiceBroadcastLiveness): void {
if (this.liveness === liveness) return;
this.liveness = liveness;
this.emit(VoiceBroadcastPlaybackEvent.LivenessChanged, liveness);
}
private updateLiveness(): void {
if (this.infoState === VoiceBroadcastInfoState.Stopped) {
this.setLiveness("not-live");
return;
}
if (this.infoState === VoiceBroadcastInfoState.Paused) {
this.setLiveness("grey");
return;
}
if ([VoiceBroadcastPlaybackState.Stopped, VoiceBroadcastPlaybackState.Paused].includes(this.state)) {
this.setLiveness("grey");
return;
}
if (this.currentlyPlaying && this.chunkEvents.isLast(this.currentlyPlaying)) {
this.setLiveness("live");
return;
}
this.setLiveness("grey");
return;
}
public get currentState(): PlaybackState { public get currentState(): PlaybackState {
return PlaybackState.Playing; return PlaybackState.Playing;
} }
@ -295,7 +331,10 @@ export class VoiceBroadcastPlayback
const time = timeSeconds * 1000; const time = timeSeconds * 1000;
const event = this.chunkEvents.findByTime(time); const event = this.chunkEvents.findByTime(time);
if (!event) return; if (!event) {
logger.warn("voice broadcast chunk event to skip to not found");
return;
}
const currentPlayback = this.currentlyPlaying const currentPlayback = this.currentlyPlaying
? this.getPlaybackForEvent(this.currentlyPlaying) ? this.getPlaybackForEvent(this.currentlyPlaying)
@ -304,7 +343,7 @@ export class VoiceBroadcastPlayback
const skipToPlayback = this.getPlaybackForEvent(event); const skipToPlayback = this.getPlaybackForEvent(event);
if (!skipToPlayback) { if (!skipToPlayback) {
logger.error("voice broadcast chunk to skip to not found", event); logger.warn("voice broadcast chunk to skip to not found", event);
return; return;
} }
@ -324,6 +363,7 @@ export class VoiceBroadcastPlayback
} }
this.setPosition(time); this.setPosition(time);
this.updateLiveness();
} }
public async start(): Promise<void> { public async start(): Promise<void> {
@ -398,6 +438,7 @@ export class VoiceBroadcastPlayback
this.state = state; this.state = state;
this.emit(VoiceBroadcastPlaybackEvent.StateChanged, state, this); this.emit(VoiceBroadcastPlaybackEvent.StateChanged, state, this);
this.updateLiveness();
} }
public getInfoState(): VoiceBroadcastInfoState { public getInfoState(): VoiceBroadcastInfoState {
@ -411,6 +452,7 @@ export class VoiceBroadcastPlayback
this.infoState = state; this.infoState = state;
this.emit(VoiceBroadcastPlaybackEvent.InfoStateChanged, state); this.emit(VoiceBroadcastPlaybackEvent.InfoStateChanged, state);
this.updateLiveness();
} }
public destroy(): void { public destroy(): void {

View file

@ -93,6 +93,10 @@ export class VoiceBroadcastChunkEvents {
return null; return null;
} }
public isLast(event: MatrixEvent): boolean {
return this.events.indexOf(event) >= this.events.length - 1;
}
private calculateChunkLength(event: MatrixEvent): number { private calculateChunkLength(event: MatrixEvent): number {
return event.getContent()?.["org.matrix.msc1767.audio"]?.duration return event.getContent()?.["org.matrix.msc1767.audio"]?.duration
|| event.getContent()?.info?.duration || event.getContent()?.info?.duration

View file

@ -20,8 +20,13 @@ import { render } from "@testing-library/react";
import { LiveBadge } from "../../../../src/voice-broadcast"; import { LiveBadge } from "../../../../src/voice-broadcast";
describe("LiveBadge", () => { describe("LiveBadge", () => {
it("should render the expected HTML", () => { it("should render as expected with default props", () => {
const { container } = render(<LiveBadge />); const { container } = render(<LiveBadge />);
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });
it("should render in grey as expected", () => {
const { container } = render(<LiveBadge grey={true} />);
expect(container).toMatchSnapshot();
});
}); });

View file

@ -16,7 +16,7 @@ import { Container } from "react-dom";
import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix"; import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
import { render, RenderResult } from "@testing-library/react"; import { render, RenderResult } from "@testing-library/react";
import { VoiceBroadcastHeader } from "../../../../src/voice-broadcast"; import { VoiceBroadcastHeader, VoiceBroadcastLiveness } from "../../../../src/voice-broadcast";
import { mkRoom, stubClient } from "../../../test-utils"; import { mkRoom, stubClient } from "../../../test-utils";
// mock RoomAvatar, because it is doing too much fancy stuff // mock RoomAvatar, because it is doing too much fancy stuff
@ -35,7 +35,7 @@ describe("VoiceBroadcastHeader", () => {
const sender = new RoomMember(roomId, userId); const sender = new RoomMember(roomId, userId);
let container: Container; let container: Container;
const renderHeader = (live: boolean, showBroadcast: boolean = undefined): RenderResult => { const renderHeader = (live: VoiceBroadcastLiveness, showBroadcast: boolean = undefined): RenderResult => {
return render(<VoiceBroadcastHeader return render(<VoiceBroadcastHeader
live={live} live={live}
microphoneLabel={sender.name} microphoneLabel={sender.name}
@ -52,17 +52,27 @@ describe("VoiceBroadcastHeader", () => {
describe("when rendering a live broadcast header with broadcast info", () => { describe("when rendering a live broadcast header with broadcast info", () => {
beforeEach(() => { beforeEach(() => {
container = renderHeader(true, true).container; container = renderHeader("live", true).container;
}); });
it("should render the header with a live badge", () => { it("should render the header with a red live badge", () => {
expect(container).toMatchSnapshot();
});
});
describe("when rendering a live (grey) broadcast header with broadcast info", () => {
beforeEach(() => {
container = renderHeader("grey", true).container;
});
it("should render the header with a grey live badge", () => {
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });
}); });
describe("when rendering a non-live broadcast header", () => { describe("when rendering a non-live broadcast header", () => {
beforeEach(() => { beforeEach(() => {
container = renderHeader(false).container; container = renderHeader("not-live").container;
}); });
it("should render the header without a live badge", () => { it("should render the header without a live badge", () => {

View file

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LiveBadge should render the expected HTML 1`] = ` exports[`LiveBadge should render as expected with default props 1`] = `
<div> <div>
<div <div
class="mx_LiveBadge" class="mx_LiveBadge"
@ -12,3 +12,16 @@ exports[`LiveBadge should render the expected HTML 1`] = `
</div> </div>
</div> </div>
`; `;
exports[`LiveBadge should render in grey as expected 1`] = `
<div>
<div
class="mx_LiveBadge mx_LiveBadge--grey"
>
<div
class="mx_Icon mx_Icon_16"
/>
Live
</div>
</div>
`;

View file

@ -1,6 +1,56 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VoiceBroadcastHeader when rendering a live broadcast header with broadcast info should render the header with a live badge 1`] = ` exports[`VoiceBroadcastHeader when rendering a live (grey) broadcast header with broadcast info should render the header with a grey live badge 1`] = `
<div>
<div
class="mx_VoiceBroadcastHeader"
>
<div
data-testid="room-avatar"
>
room avatar:
!room:example.com
</div>
<div
class="mx_VoiceBroadcastHeader_content"
>
<div
class="mx_VoiceBroadcastHeader_room"
>
!room:example.com
</div>
<div
class="mx_VoiceBroadcastHeader_line"
>
<div
class="mx_Icon mx_Icon_16"
/>
<span>
test user
</span>
</div>
<div
class="mx_VoiceBroadcastHeader_line"
>
<div
class="mx_Icon mx_Icon_16"
/>
Voice broadcast
</div>
</div>
<div
class="mx_LiveBadge mx_LiveBadge--grey"
>
<div
class="mx_Icon mx_Icon_16"
/>
Live
</div>
</div>
</div>
`;
exports[`VoiceBroadcastHeader when rendering a live broadcast header with broadcast info should render the header with a red live badge 1`] = `
<div> <div>
<div <div
class="mx_VoiceBroadcastHeader" class="mx_VoiceBroadcastHeader"

View file

@ -22,6 +22,7 @@ import { mocked } from "jest-mock";
import { import {
VoiceBroadcastInfoState, VoiceBroadcastInfoState,
VoiceBroadcastLiveness,
VoiceBroadcastPlayback, VoiceBroadcastPlayback,
VoiceBroadcastPlaybackBody, VoiceBroadcastPlaybackBody,
VoiceBroadcastPlaybackEvent, VoiceBroadcastPlaybackEvent,
@ -62,6 +63,7 @@ describe("VoiceBroadcastPlaybackBody", () => {
beforeEach(() => { beforeEach(() => {
playback = new VoiceBroadcastPlayback(infoEvent, client); playback = new VoiceBroadcastPlayback(infoEvent, client);
jest.spyOn(playback, "toggle").mockImplementation(() => Promise.resolve()); jest.spyOn(playback, "toggle").mockImplementation(() => Promise.resolve());
jest.spyOn(playback, "getLiveness");
jest.spyOn(playback, "getState"); jest.spyOn(playback, "getState");
jest.spyOn(playback, "durationSeconds", "get").mockReturnValue(23 * 60 + 42); // 23:42 jest.spyOn(playback, "durationSeconds", "get").mockReturnValue(23 * 60 + 42); // 23:42
}); });
@ -69,6 +71,7 @@ describe("VoiceBroadcastPlaybackBody", () => {
describe("when rendering a buffering voice broadcast", () => { describe("when rendering a buffering voice broadcast", () => {
beforeEach(() => { beforeEach(() => {
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Buffering); mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Buffering);
mocked(playback.getLiveness).mockReturnValue("live");
renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />); renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />);
}); });
@ -80,6 +83,7 @@ describe("VoiceBroadcastPlaybackBody", () => {
describe(`when rendering a stopped broadcast`, () => { describe(`when rendering a stopped broadcast`, () => {
beforeEach(() => { beforeEach(() => {
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Stopped); mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Stopped);
mocked(playback.getLiveness).mockReturnValue("not-live");
renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />); renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />);
}); });
@ -107,11 +111,12 @@ describe("VoiceBroadcastPlaybackBody", () => {
}); });
describe.each([ describe.each([
VoiceBroadcastPlaybackState.Paused, [VoiceBroadcastPlaybackState.Paused, "not-live"],
VoiceBroadcastPlaybackState.Playing, [VoiceBroadcastPlaybackState.Playing, "live"],
])("when rendering a %s broadcast", (playbackState: VoiceBroadcastPlaybackState) => { ])("when rendering a %s/%s broadcast", (state: VoiceBroadcastPlaybackState, liveness: VoiceBroadcastLiveness) => {
beforeEach(() => { beforeEach(() => {
mocked(playback.getState).mockReturnValue(playbackState); mocked(playback.getState).mockReturnValue(state);
mocked(playback.getLiveness).mockReturnValue(liveness);
renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />); renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />);
}); });

View file

@ -60,21 +60,21 @@ describe("VoiceBroadcastRecordingBody", () => {
renderResult = render(<VoiceBroadcastRecordingBody recording={recording} />); renderResult = render(<VoiceBroadcastRecordingBody recording={recording} />);
}); });
it("should render the expected HTML", () => { it("should render with a red live badge", () => {
expect(renderResult.container).toMatchSnapshot(); expect(renderResult.container).toMatchSnapshot();
}); });
}); });
describe("when rendering a non-live broadcast", () => { describe("when rendering a paused broadcast", () => {
let renderResult: RenderResult; let renderResult: RenderResult;
beforeEach(() => { beforeEach(async () => {
recording.stop(); await recording.pause();
renderResult = render(<VoiceBroadcastRecordingBody recording={recording} />); renderResult = render(<VoiceBroadcastRecordingBody recording={recording} />);
}); });
it("should not render the live badge", () => { it("should render with a grey live badge", () => {
expect(renderResult.queryByText("Live")).toBeFalsy(); expect(renderResult.container).toMatchSnapshot();
}); });
}); });
}); });

View file

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VoiceBroadcastPlaybackBody when rendering a 0 broadcast should render as expected 1`] = ` exports[`VoiceBroadcastPlaybackBody when rendering a 0/not-live broadcast should render as expected 1`] = `
<div> <div>
<div <div
class="mx_VoiceBroadcastBody" class="mx_VoiceBroadcastBody"
@ -41,14 +41,6 @@ exports[`VoiceBroadcastPlaybackBody when rendering a 0 broadcast should render a
Voice broadcast Voice broadcast
</div> </div>
</div> </div>
<div
class="mx_LiveBadge"
>
<div
class="mx_Icon mx_Icon_16"
/>
Live
</div>
</div> </div>
<div <div
class="mx_VoiceBroadcastBody_controls" class="mx_VoiceBroadcastBody_controls"
@ -87,7 +79,7 @@ exports[`VoiceBroadcastPlaybackBody when rendering a 0 broadcast should render a
</div> </div>
`; `;
exports[`VoiceBroadcastPlaybackBody when rendering a 1 broadcast should render as expected 1`] = ` exports[`VoiceBroadcastPlaybackBody when rendering a 1/live broadcast should render as expected 1`] = `
<div> <div>
<div <div
class="mx_VoiceBroadcastBody" class="mx_VoiceBroadcastBody"
@ -303,14 +295,6 @@ exports[`VoiceBroadcastPlaybackBody when rendering a stopped broadcast and the l
Voice broadcast Voice broadcast
</div> </div>
</div> </div>
<div
class="mx_LiveBadge"
>
<div
class="mx_Icon mx_Icon_16"
/>
Live
</div>
</div> </div>
<div <div
class="mx_VoiceBroadcastBody_controls" class="mx_VoiceBroadcastBody_controls"

View file

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VoiceBroadcastRecordingBody when rendering a live broadcast should render the expected HTML 1`] = ` exports[`VoiceBroadcastRecordingBody when rendering a live broadcast should render with a red live badge 1`] = `
<div> <div>
<div <div
class="mx_VoiceBroadcastBody" class="mx_VoiceBroadcastBody"
@ -45,3 +45,49 @@ exports[`VoiceBroadcastRecordingBody when rendering a live broadcast should rend
</div> </div>
</div> </div>
`; `;
exports[`VoiceBroadcastRecordingBody when rendering a paused broadcast should render with a grey live badge 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"
>
My room
</div>
<div
class="mx_VoiceBroadcastHeader_line"
>
<div
class="mx_Icon mx_Icon_16"
/>
<span>
@user:example.com
</span>
</div>
</div>
<div
class="mx_LiveBadge mx_LiveBadge--grey"
>
<div
class="mx_Icon mx_Icon_16"
/>
Live
</div>
</div>
</div>
</div>
`;

View file

@ -36,7 +36,7 @@ exports[`VoiceBroadcastRecordingPip when rendering a paused recording should ren
</div> </div>
</div> </div>
<div <div
class="mx_LiveBadge" class="mx_LiveBadge mx_LiveBadge--grey"
> >
<div <div
class="mx_Icon mx_Icon_16" class="mx_Icon mx_Icon_16"

View file

@ -23,6 +23,7 @@ import { RelationsHelperEvent } from "../../../src/events/RelationsHelper";
import { MediaEventHelper } from "../../../src/utils/MediaEventHelper"; import { MediaEventHelper } from "../../../src/utils/MediaEventHelper";
import { import {
VoiceBroadcastInfoState, VoiceBroadcastInfoState,
VoiceBroadcastLiveness,
VoiceBroadcastPlayback, VoiceBroadcastPlayback,
VoiceBroadcastPlaybackEvent, VoiceBroadcastPlaybackEvent,
VoiceBroadcastPlaybackState, VoiceBroadcastPlaybackState,
@ -76,6 +77,12 @@ describe("VoiceBroadcastPlayback", () => {
}); });
}; };
const itShouldHaveLiveness = (liveness: VoiceBroadcastLiveness): void => {
it(`should have liveness ${liveness}`, () => {
expect(playback.getLiveness()).toBe(liveness);
});
};
const startPlayback = () => { const startPlayback = () => {
beforeEach(async () => { beforeEach(async () => {
await playback.start(); await playback.start();
@ -187,6 +194,8 @@ describe("VoiceBroadcastPlayback", () => {
describe("and calling start", () => { describe("and calling start", () => {
startPlayback(); startPlayback();
itShouldHaveLiveness("grey");
it("should be in buffering state", () => { it("should be in buffering state", () => {
expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Buffering); expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Buffering);
}); });
@ -223,6 +232,7 @@ describe("VoiceBroadcastPlayback", () => {
}); });
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing); itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing);
itShouldHaveLiveness("live");
it("should update the duration", () => { it("should update the duration", () => {
expect(playback.durationSeconds).toBe(2.3); expect(playback.durationSeconds).toBe(2.3);