Skip to content

Commit

Permalink
Merge branch 'simplify-client'
Browse files Browse the repository at this point in the history
It's been a while since I've looked deeply at the client code, and I
found a ton of (in retrospect) obvious ways to remove unnecessary layers
of abstraction and generally make the whole thing easier to understand.
  • Loading branch information
ahamlinman committed Mar 28, 2024
2 parents b5f81c8 + 4044da3 commit 2e79410
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 146 deletions.
26 changes: 4 additions & 22 deletions client/src/App/ChannelSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,22 @@ export default function ChannelSelector({
onTune,
}: {
selected?: string;
onTune: (ch: string) => Promise<void>;
onTune: (ch: string) => void;
}) {
const channelNames = useConfig<string[]>("channels");

const handleTune = async (selected: string) => {
if (selected === undefined) {
throw new Error("tried to tune before channels loaded");
}

try {
await onTune(selected);
} catch (e) {
console.error("Tune request failed", e);
}
};

if (!(channelNames instanceof Array)) {
return null;
}

return (
return channelNames instanceof Array ? (
<aside className="ChannelSelector">
{channelNames.map((ch) => (
<Channel
key={ch}
name={ch}
active={ch === selected}
onClick={() => {
handleTune(ch);
}}
onClick={() => onTune(ch)}
/>
))}
</aside>
);
) : null;
}

function Channel({
Expand Down
53 changes: 15 additions & 38 deletions client/src/App/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ function StatusIndicator() {
const webRTC = useWebRTC();
const tunerStatus = useTunerStatus();

const indicatorActive = isActiveAndPlaying(webRTC, tunerStatus);
const indicatorActive =
webRTC.Connection.Status === "Connected" &&
tunerStatus.Connection === "Connected" &&
tunerStatus.State === "Playing";

return (
<div className="StatusIndicator">
Expand All @@ -73,55 +76,29 @@ function StatusIndicator() {
);
}

function isActiveAndPlaying(
webRTC: WebRTCState,
tunerStatus: TunerStatus,
): boolean {
if (webRTC.Connection.Status !== "Connected") {
return false;
}

return (
tunerStatus.Connection === "Connected" && tunerStatus.State === "Playing"
);
}

function statusString(webRTC: WebRTCState, tunerStatus: TunerStatus): string {
const webRTCString = webRTCStatusString(webRTC);
if (webRTCString !== undefined) {
return webRTCString;
}

return tunerStatusString(tunerStatus);
}

function webRTCStatusString(state: WebRTCState): string | undefined {
if (state.Connection.Status !== "Connected") {
return state.Connection.Status;
if (webRTC.Connection.Status !== "Connected") {
return webRTC.Connection.Status;
}

return undefined;
}

function tunerStatusString(status: TunerStatus): string {
if (status.Connection !== "Connected") {
return status.Connection;
if (tunerStatus.Connection !== "Connected") {
return tunerStatus.Connection;
}

if (status.State === "Stopped") {
if (status.Error !== undefined) {
if (tunerStatus.State === "Stopped") {
if (tunerStatus.Error !== undefined) {
return "Failed to Tune";
}
return "Powered Off";
}

if (status.State === "Starting") {
return `Tuning to ${status.ChannelName}`;
if (tunerStatus.State === "Starting") {
return `Tuning to ${tunerStatus.ChannelName}`;
}

if (status.State === "Playing") {
return `Watching ${status.ChannelName}`;
if (tunerStatus.State === "Playing") {
return `Watching ${tunerStatus.ChannelName}`;
}

return status.State;
return tunerStatus.State;
}
19 changes: 0 additions & 19 deletions client/src/App/Title.tsx

This file was deleted.

37 changes: 0 additions & 37 deletions client/src/App/VideoPlayer.tsx

This file was deleted.

22 changes: 4 additions & 18 deletions client/src/App/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,6 @@ $mobile-break: 639px;
}
}

@mixin if-motion-safe {
@media (prefers-reduced-motion: no-preference) {
@content;
}
}

.AppContainer {
width: 100%;
height: 100%;
Expand Down Expand Up @@ -97,9 +91,7 @@ $mobile-break: 639px;
cursor: pointer;

background-color: $foreground;
@include if-motion-safe {
transition: background-color $transition-duration;
}
transition: background-color $transition-duration;

&--Active {
background-color: $accent;
Expand Down Expand Up @@ -139,9 +131,7 @@ $mobile-break: 639px;
margin-right: 6px;

background-color: $foreground;
@include if-motion-safe {
transition: background-color $transition-duration;
}
transition: background-color $transition-duration;

&--Active {
background-color: $accent;
Expand Down Expand Up @@ -175,14 +165,10 @@ $mobile-break: 639px;

color: $foreground;
background-color: $base-reallydark;
@include if-motion-safe {
transition: background-color ease-out $transition-duration;
}
transition: background-color ease-out $transition-duration;
&:hover {
background-color: $base-darker;
@include if-motion-safe {
transition-timing-function: step-start;
}
transition-timing-function: step-start;
}

&--Active {
Expand Down
63 changes: 52 additions & 11 deletions client/src/App/index.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,77 @@
import React from "react";
import { Helmet } from "react-helmet";

import { useWebRTC } from "../WebRTC";
import rpc from "../rpc";

import "./index.scss";

import Title from "./Title";
import Header from "./Header";
import ChannelSelector from "./ChannelSelector";
import VideoPlayer from "./VideoPlayer";
import { useTunerStatus } from "../TunerStatus";

export default function App() {
const webRTC = useWebRTC();
const tunerStatus = useTunerStatus();

const selectedChannel =
tunerStatus.Connection === "Connected" && tunerStatus.State !== "Stopped"
? tunerStatus.ChannelName
: undefined;

return (
<div className="AppContainer">
<Title />
<Header />
<ChannelSelector
selected={
tunerStatus.Connection === "Connected" &&
tunerStatus.State !== "Stopped"
? tunerStatus.ChannelName
: undefined
}
onTune={(ChannelName) =>
rpc("tune", { ChannelName }).catch(console.error)
}
selected={selectedChannel}
onTune={(ch) => rpc("tune", { ChannelName: ch }).catch(console.error)}
/>
<VideoPlayer stream={webRTC.MediaStream} />
</div>
);
}

function Title() {
const tunerStatus = useTunerStatus();

const titleText =
tunerStatus.Connection === "Connected" && tunerStatus.State !== "Stopped"
? `${tunerStatus.ChannelName} | Hypcast`
: "Hypcast";

return (
<Helmet>
<title>{titleText}</title>
</Helmet>
);
}

function VideoPlayer({ stream }: { stream: undefined | MediaStream }) {
const videoElement = React.useRef<null | HTMLVideoElement>(null);

React.useEffect(() => {
if (videoElement.current !== null) {
videoElement.current.srcObject = stream ?? null;
}
}, [stream]);

/* eslint-disable jsx-a11y/media-has-caption */
// Lack of closed caption support is a longstanding deficiency in Hypcast.
// After experimenting with several approaches in GStreamer, I'm ashamed to
// say that I have yet to identify one that works reliably and consistently.
// Also, it is not clear that the eventual implementation of closed captions
// will involve WebVTT, which is what this rule actually looks for (through
// the presence of a <track> element), so it may need to remain disabled even
// after closed caption support is in place.
return (
<main className="VideoPlayer">
<video
style={{ display: stream === undefined ? "none" : undefined }}
ref={videoElement}
autoPlay
controls
/>
</main>
);
}
2 changes: 1 addition & 1 deletion client/src/WebRTC/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const Context = React.createContext<null | State>(null);
export const useWebRTC = (): State => {
const state = React.useContext(Context);
if (state === null) {
throw new Error("useWebRTC must be called from within a <WebRTCProvider>");
throw new Error("useWebRTC must be used within <WebRTCProvider>");
}
return state;
};
Expand Down

0 comments on commit 2e79410

Please sign in to comment.