[Backport staging] Update Voice Broadcast buffering style (#9660)
Co-authored-by: Michael Weimann <michaelw@matrix.org>
This commit is contained in:
parent
f4b6719a28
commit
fcc49d0959
8 changed files with 163 additions and 60 deletions
|
@ -40,8 +40,9 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: $spacing-4;
|
gap: $spacing-4;
|
||||||
|
|
||||||
i {
|
.mx_Spinner {
|
||||||
flex-shrink: 0;
|
flex: 0 0 14px;
|
||||||
|
padding: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
|
|
|
@ -657,6 +657,7 @@
|
||||||
"Change input device": "Change input device",
|
"Change input device": "Change input device",
|
||||||
"Live": "Live",
|
"Live": "Live",
|
||||||
"Voice broadcast": "Voice broadcast",
|
"Voice broadcast": "Voice broadcast",
|
||||||
|
"Buffering…": "Buffering…",
|
||||||
"Cannot reach homeserver": "Cannot reach homeserver",
|
"Cannot reach homeserver": "Cannot reach homeserver",
|
||||||
"Ensure you have a stable internet connection, or get in touch with the server admin": "Ensure you have a stable internet connection, or get in touch with the server admin",
|
"Ensure you have a stable internet connection, or get in touch with the server admin": "Ensure you have a stable internet connection, or get in touch with the server admin",
|
||||||
"Your %(brand)s is misconfigured": "Your %(brand)s is misconfigured",
|
"Your %(brand)s is misconfigured": "Your %(brand)s is misconfigured",
|
||||||
|
|
|
@ -25,6 +25,7 @@ import AccessibleButton from "../../../components/views/elements/AccessibleButto
|
||||||
import { Icon as XIcon } from "../../../../res/img/element-icons/cancel-rounded.svg";
|
import { Icon as XIcon } from "../../../../res/img/element-icons/cancel-rounded.svg";
|
||||||
import Clock from "../../../components/views/audio_messages/Clock";
|
import Clock from "../../../components/views/audio_messages/Clock";
|
||||||
import { formatTimeLeft } from "../../../DateUtils";
|
import { formatTimeLeft } from "../../../DateUtils";
|
||||||
|
import Spinner from "../../../components/views/elements/Spinner";
|
||||||
|
|
||||||
interface VoiceBroadcastHeaderProps {
|
interface VoiceBroadcastHeaderProps {
|
||||||
live?: VoiceBroadcastLiveness;
|
live?: VoiceBroadcastLiveness;
|
||||||
|
@ -33,6 +34,7 @@ interface VoiceBroadcastHeaderProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
microphoneLabel?: string;
|
microphoneLabel?: string;
|
||||||
showBroadcast?: boolean;
|
showBroadcast?: boolean;
|
||||||
|
showBuffering?: boolean;
|
||||||
timeLeft?: number;
|
timeLeft?: number;
|
||||||
showClose?: boolean;
|
showClose?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -44,47 +46,55 @@ export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({
|
||||||
room,
|
room,
|
||||||
microphoneLabel,
|
microphoneLabel,
|
||||||
showBroadcast = false,
|
showBroadcast = false,
|
||||||
|
showBuffering = false,
|
||||||
showClose = false,
|
showClose = false,
|
||||||
timeLeft,
|
timeLeft,
|
||||||
}) => {
|
}) => {
|
||||||
const broadcast = showBroadcast
|
const broadcast = showBroadcast && (
|
||||||
? <div className="mx_VoiceBroadcastHeader_line">
|
<div className="mx_VoiceBroadcastHeader_line">
|
||||||
<LiveIcon className="mx_Icon mx_Icon_16" />
|
<LiveIcon className="mx_Icon mx_Icon_16" />
|
||||||
{ _t("Voice broadcast") }
|
{ _t("Voice broadcast") }
|
||||||
</div>
|
</div>
|
||||||
: null;
|
);
|
||||||
|
|
||||||
const liveBadge = live === "not-live"
|
const liveBadge = live !== "not-live" && (
|
||||||
? null
|
<LiveBadge grey={live === "grey"} />
|
||||||
: <LiveBadge grey={live === "grey"} />;
|
);
|
||||||
|
|
||||||
const closeButton = showClose
|
const closeButton = showClose && (
|
||||||
? <AccessibleButton onClick={onCloseClick}>
|
<AccessibleButton onClick={onCloseClick}>
|
||||||
<XIcon className="mx_Icon mx_Icon_16" />
|
<XIcon className="mx_Icon mx_Icon_16" />
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
: null;
|
);
|
||||||
|
|
||||||
const timeLeftLine = timeLeft
|
const timeLeftLine = timeLeft && (
|
||||||
? <div className="mx_VoiceBroadcastHeader_line">
|
<div className="mx_VoiceBroadcastHeader_line">
|
||||||
<TimerIcon className="mx_Icon mx_Icon_16" />
|
<TimerIcon className="mx_Icon mx_Icon_16" />
|
||||||
<Clock formatFn={formatTimeLeft} seconds={timeLeft} />
|
<Clock formatFn={formatTimeLeft} seconds={timeLeft} />
|
||||||
</div>
|
</div>
|
||||||
: null;
|
);
|
||||||
|
|
||||||
|
const buffering = showBuffering && (
|
||||||
|
<div className="mx_VoiceBroadcastHeader_line">
|
||||||
|
<Spinner w={14} h={14} />
|
||||||
|
{ _t("Buffering…") }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const microphoneLineClasses = classNames({
|
const microphoneLineClasses = classNames({
|
||||||
mx_VoiceBroadcastHeader_line: true,
|
mx_VoiceBroadcastHeader_line: true,
|
||||||
["mx_VoiceBroadcastHeader_mic--clickable"]: onMicrophoneLineClick,
|
["mx_VoiceBroadcastHeader_mic--clickable"]: onMicrophoneLineClick,
|
||||||
});
|
});
|
||||||
|
|
||||||
const microphoneLine = microphoneLabel
|
const microphoneLine = microphoneLabel && (
|
||||||
? <div
|
<div
|
||||||
className={microphoneLineClasses}
|
className={microphoneLineClasses}
|
||||||
onClick={onMicrophoneLineClick}
|
onClick={onMicrophoneLineClick}
|
||||||
>
|
>
|
||||||
<MicrophoneIcon className="mx_Icon mx_Icon_16" />
|
<MicrophoneIcon className="mx_Icon mx_Icon_16" />
|
||||||
<span>{ microphoneLabel }</span>
|
<span>{ microphoneLabel }</span>
|
||||||
</div>
|
</div>
|
||||||
: null;
|
);
|
||||||
|
|
||||||
return <div className="mx_VoiceBroadcastHeader">
|
return <div className="mx_VoiceBroadcastHeader">
|
||||||
<RoomAvatar room={room} width={32} height={32} />
|
<RoomAvatar room={room} width={32} height={32} />
|
||||||
|
@ -95,6 +105,7 @@ export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({
|
||||||
{ microphoneLine }
|
{ microphoneLine }
|
||||||
{ timeLeftLine }
|
{ timeLeftLine }
|
||||||
{ broadcast }
|
{ broadcast }
|
||||||
|
{ buffering }
|
||||||
</div>
|
</div>
|
||||||
{ liveBadge }
|
{ liveBadge }
|
||||||
{ closeButton }
|
{ closeButton }
|
||||||
|
|
|
@ -23,7 +23,6 @@ import {
|
||||||
VoiceBroadcastPlayback,
|
VoiceBroadcastPlayback,
|
||||||
VoiceBroadcastPlaybackState,
|
VoiceBroadcastPlaybackState,
|
||||||
} from "../..";
|
} from "../..";
|
||||||
import Spinner from "../../../components/views/elements/Spinner";
|
|
||||||
import { useVoiceBroadcastPlayback } from "../../hooks/useVoiceBroadcastPlayback";
|
import { useVoiceBroadcastPlayback } from "../../hooks/useVoiceBroadcastPlayback";
|
||||||
import { Icon as PlayIcon } from "../../../../res/img/element-icons/play.svg";
|
import { Icon as PlayIcon } from "../../../../res/img/element-icons/play.svg";
|
||||||
import { Icon as PauseIcon } from "../../../../res/img/element-icons/pause.svg";
|
import { Icon as PauseIcon } from "../../../../res/img/element-icons/pause.svg";
|
||||||
|
@ -55,40 +54,35 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
|
||||||
toggle,
|
toggle,
|
||||||
} = useVoiceBroadcastPlayback(playback);
|
} = useVoiceBroadcastPlayback(playback);
|
||||||
|
|
||||||
let control: React.ReactNode;
|
let controlIcon: React.FC<React.SVGProps<SVGSVGElement>>;
|
||||||
|
let controlLabel: string;
|
||||||
|
let className = "";
|
||||||
|
|
||||||
if (playbackState === VoiceBroadcastPlaybackState.Buffering) {
|
switch (playbackState) {
|
||||||
control = <Spinner />;
|
case VoiceBroadcastPlaybackState.Stopped:
|
||||||
} else {
|
controlIcon = PlayIcon;
|
||||||
let controlIcon: React.FC<React.SVGProps<SVGSVGElement>>;
|
className = "mx_VoiceBroadcastControl-play";
|
||||||
let controlLabel: string;
|
controlLabel = _t("play voice broadcast");
|
||||||
let className = "";
|
break;
|
||||||
|
case VoiceBroadcastPlaybackState.Paused:
|
||||||
switch (playbackState) {
|
controlIcon = PlayIcon;
|
||||||
case VoiceBroadcastPlaybackState.Stopped:
|
className = "mx_VoiceBroadcastControl-play";
|
||||||
controlIcon = PlayIcon;
|
controlLabel = _t("resume voice broadcast");
|
||||||
className = "mx_VoiceBroadcastControl-play";
|
break;
|
||||||
controlLabel = _t("play voice broadcast");
|
case VoiceBroadcastPlaybackState.Buffering:
|
||||||
break;
|
case VoiceBroadcastPlaybackState.Playing:
|
||||||
case VoiceBroadcastPlaybackState.Paused:
|
controlIcon = PauseIcon;
|
||||||
controlIcon = PlayIcon;
|
controlLabel = _t("pause voice broadcast");
|
||||||
className = "mx_VoiceBroadcastControl-play";
|
break;
|
||||||
controlLabel = _t("resume voice broadcast");
|
|
||||||
break;
|
|
||||||
case VoiceBroadcastPlaybackState.Playing:
|
|
||||||
controlIcon = PauseIcon;
|
|
||||||
controlLabel = _t("pause voice broadcast");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
control = <VoiceBroadcastControl
|
|
||||||
className={className}
|
|
||||||
label={controlLabel}
|
|
||||||
icon={controlIcon}
|
|
||||||
onClick={toggle}
|
|
||||||
/>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const control = <VoiceBroadcastControl
|
||||||
|
className={className}
|
||||||
|
label={controlLabel}
|
||||||
|
icon={controlIcon}
|
||||||
|
onClick={toggle}
|
||||||
|
/>;
|
||||||
|
|
||||||
let seekBackwardButton: ReactElement | null = null;
|
let seekBackwardButton: ReactElement | null = null;
|
||||||
let seekForwardButton: ReactElement | null = null;
|
let seekForwardButton: ReactElement | null = null;
|
||||||
|
|
||||||
|
@ -125,7 +119,8 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
|
||||||
live={liveness}
|
live={liveness}
|
||||||
microphoneLabel={sender?.name}
|
microphoneLabel={sender?.name}
|
||||||
room={room}
|
room={room}
|
||||||
showBroadcast={true}
|
showBroadcast={playbackState !== VoiceBroadcastPlaybackState.Buffering}
|
||||||
|
showBuffering={playbackState === VoiceBroadcastPlaybackState.Buffering}
|
||||||
/>
|
/>
|
||||||
<div className="mx_VoiceBroadcastBody_controls">
|
<div className="mx_VoiceBroadcastBody_controls">
|
||||||
{ seekBackwardButton }
|
{ seekBackwardButton }
|
||||||
|
|
|
@ -27,6 +27,13 @@ import {
|
||||||
export const useVoiceBroadcastPlayback = (playback: VoiceBroadcastPlayback) => {
|
export const useVoiceBroadcastPlayback = (playback: VoiceBroadcastPlayback) => {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const room = client.getRoom(playback.infoEvent.getRoomId());
|
const room = client.getRoom(playback.infoEvent.getRoomId());
|
||||||
|
|
||||||
|
if (!room) {
|
||||||
|
throw new Error(
|
||||||
|
`Voice Broadcast room not found (event ${playback.infoEvent.getId()})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const playbackToggle = () => {
|
const playbackToggle = () => {
|
||||||
playback.toggle();
|
playback.toggle();
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,12 +35,17 @@ describe("VoiceBroadcastHeader", () => {
|
||||||
const sender = new RoomMember(roomId, userId);
|
const sender = new RoomMember(roomId, userId);
|
||||||
let container: Container;
|
let container: Container;
|
||||||
|
|
||||||
const renderHeader = (live: VoiceBroadcastLiveness, showBroadcast: boolean = undefined): RenderResult => {
|
const renderHeader = (
|
||||||
|
live: VoiceBroadcastLiveness,
|
||||||
|
showBroadcast?: boolean,
|
||||||
|
buffering?: boolean,
|
||||||
|
): RenderResult => {
|
||||||
return render(<VoiceBroadcastHeader
|
return render(<VoiceBroadcastHeader
|
||||||
live={live}
|
live={live}
|
||||||
microphoneLabel={sender.name}
|
microphoneLabel={sender.name}
|
||||||
room={room}
|
room={room}
|
||||||
showBroadcast={showBroadcast}
|
showBroadcast={showBroadcast}
|
||||||
|
showBuffering={buffering}
|
||||||
/>);
|
/>);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,6 +56,16 @@ describe("VoiceBroadcastHeader", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when rendering a live broadcast header with broadcast info", () => {
|
describe("when rendering a live broadcast header with broadcast info", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
container = renderHeader("live", true, true).container;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render the header with a red live badge", () => {
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when rendering a buffering live broadcast header with broadcast info", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
container = renderHeader("live", true).container;
|
container = renderHeader("live", true).container;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,55 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`VoiceBroadcastHeader when rendering a buffering live broadcast header with broadcast info should render the header with a red 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"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_Icon mx_Icon_16"
|
||||||
|
/>
|
||||||
|
Live
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`VoiceBroadcastHeader when rendering a live (grey) broadcast header with broadcast info should render the header with a grey 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>
|
||||||
<div
|
<div
|
||||||
|
@ -87,6 +137,22 @@ exports[`VoiceBroadcastHeader when rendering a live broadcast header with broadc
|
||||||
/>
|
/>
|
||||||
Voice broadcast
|
Voice broadcast
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_VoiceBroadcastHeader_line"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_Spinner"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Loading..."
|
||||||
|
class="mx_Spinner_icon"
|
||||||
|
data-testid="spinner"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
Buffering…
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mx_LiveBadge"
|
class="mx_LiveBadge"
|
||||||
|
|
|
@ -242,9 +242,17 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s
|
||||||
class="mx_VoiceBroadcastHeader_line"
|
class="mx_VoiceBroadcastHeader_line"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_Icon mx_Icon_16"
|
class="mx_Spinner"
|
||||||
/>
|
>
|
||||||
Voice broadcast
|
<div
|
||||||
|
aria-label="Loading..."
|
||||||
|
class="mx_Spinner_icon"
|
||||||
|
data-testid="spinner"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
Buffering…
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -270,14 +278,13 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mx_Spinner"
|
aria-label="pause voice broadcast"
|
||||||
|
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Loading..."
|
class="mx_Icon mx_Icon_16"
|
||||||
class="mx_Spinner_icon"
|
|
||||||
data-testid="spinner"
|
|
||||||
role="progressbar"
|
|
||||||
style="width: 32px; height: 32px;"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
Loading…
Reference in a new issue