Skip to content

Commit

Permalink
feat: added broadcast to studio (#1798)
Browse files Browse the repository at this point in the history
* feat: added broadcast to studio

* fix: small casing fix

* fix: added docs link

* fix: remove dev code
  • Loading branch information
0xcadams authored Jul 14, 2023
1 parent bba592c commit c5063f5
Show file tree
Hide file tree
Showing 17 changed files with 770 additions and 181 deletions.
24 changes: 19 additions & 5 deletions packages/www/components/AppPlayer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,26 @@ import { Player } from "@livepeer/react";
import mux from "mux-embed";
import { memo, useCallback } from "react";

interface AppPlayerProps {
playbackUrl: string;
type AppPlayerProps = {
playbackUrl?: string;
playbackId?: string;
autoPlay?: boolean;
type: "asset" | "stream";
}
} & (
| {
playbackUrl: string;
}
| {
playbackId: string;
}
);

const AppPlayer = ({ playbackUrl, autoPlay = true, type }: AppPlayerProps) => {
const AppPlayer = ({
playbackUrl,
playbackId,
autoPlay = true,
type,
}: AppPlayerProps) => {
const mediaElementRef = useCallback((element: HTMLMediaElement) => {
mux.monitor(element, {
debug: false,
Expand All @@ -25,11 +38,12 @@ const AppPlayer = ({ playbackUrl, autoPlay = true, type }: AppPlayerProps) => {
return (
<Player
src={playbackUrl}
playbackId={playbackId}
autoPlay={autoPlay}
objectFit="contain"
mediaElementRef={mediaElementRef}
priority
muted
lowLatency
/>
);
};
Expand Down
5 changes: 4 additions & 1 deletion packages/www/components/AssetDetails/AssetSharePopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from "@livepeer/design-system";
import { CodeIcon, Link1Icon } from "@radix-ui/react-icons";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { isStaging } from "lib/utils";

const buttonLinkCss = {
display: "flex",
Expand All @@ -29,7 +30,9 @@ const AssetSharePopup = ({
onEmbedVideoClick,
}: AssetSharePopupProps) => {
const [openSnackbar] = useSnackbar();
const copyString = `https://lvpr.tv?v=${playbackId}`;
const copyString = isStaging()
? `https://monster.lvpr.tv?v=${playbackId}`
: `https://lvpr.tv?v=${playbackId}`;

return (
<Popover>
Expand Down
10 changes: 7 additions & 3 deletions packages/www/components/AssetDetails/EmbedVideoDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import {
TextField,
useSnackbar,
} from "@livepeer/design-system";
import { isStaging } from "lib/utils";
import { CopyToClipboard } from "react-copy-to-clipboard";

const embedStringForAsset = (playbackId: string) =>
`<iframe src="https://lvpr.tv?v=${playbackId}" frameborder="0" allowfullscreen allow="autoplay; encrypted-media; picture-in-picture" sandbox="allow-scripts"></iframe>`;
const embedStringForAsset = (embedUrl: string) =>
`<iframe src="${embedUrl}" frameborder="0" allowfullscreen allow="autoplay; encrypted-media; picture-in-picture" sandbox="allow-same-origin allow-scripts"></iframe>`;

export type EmbedVideoDialog = {
isOpen: boolean;
Expand All @@ -25,7 +26,10 @@ const EmbedVideoDialog = ({
onOpenChange,
playbackId,
}: EmbedVideoDialog) => {
const embedString = embedStringForAsset(playbackId);
const embedUrl = isStaging()
? `https://monster.lvpr.tv?v=${playbackId}`
: `https://lvpr.tv?v=${playbackId}`;
const embedString = embedStringForAsset(embedUrl);
const [openSnackbar] = useSnackbar();

const onCopy = () => {
Expand Down
22 changes: 18 additions & 4 deletions packages/www/components/StreamDetails/StreamChildrenHeadingBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export type StreamChildrenHeadingBoxProps = {
stream: Stream;
user: User;
activeTab: string;
setSwitchTab: Dispatch<SetStateAction<"Overview" | "Health">>;
setSwitchTab: Dispatch<SetStateAction<"Overview" | "Details" | "Health">>;
invalidateStream: () => void;
};

Expand All @@ -43,7 +43,7 @@ const StreamChildrenHeadingBox = ({
mb: "$4",
width: "100%",
}}>
<Box css={{ display: "flex" }}>
<Box css={{ display: "flex", gap: "$5" }}>
<Box
as="div"
onClick={() => setSwitchTab("Overview")}
Expand All @@ -54,14 +54,28 @@ const StreamChildrenHeadingBox = ({
textDecoration: "none",
borderBottom: "2px solid",
borderColor: activeTab === "Overview" ? "$green9" : "transparent",
mr: "$5",
"&:hover": {
textDecoration: "none",
},
}}>
Overview
</Box>

<Box
as="div"
onClick={() => setSwitchTab("Details")}
css={{
textDecoration: "none",
pb: "$2",
width: "100%",
cursor: "default",
borderBottom: "2px solid",
borderColor: activeTab === "Details" ? "$green9" : "transparent",
"&:hover": {
textDecoration: "none",
},
}}>
Details
</Box>
<Box
as="div"
onClick={() => setSwitchTab("Health")}
Expand Down
35 changes: 35 additions & 0 deletions packages/www/components/StreamDetails/StreamDetailsTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Text } from "@livepeer/design-system";
import MultistreamTargetsTable from "components/StreamDetails/MultistreamTargetsTable";
import SessionsTable from "./SessionsTable";

const StreamDetailsTab = ({ id, stream, streamHealth, invalidateStream }) => {
return (
<>
<MultistreamTargetsTable
stream={stream}
streamHealth={streamHealth}
invalidateStream={invalidateStream}
css={{ mb: "$7" }}
emptyState={
<Text variant="neutral" size="2">
No targets
</Text>
}
tableLayout="auto"
border
/>
<SessionsTable
streamId={id}
emptyState={
<Text variant="neutral" size="2">
No sessions
</Text>
}
tableLayout="auto"
border
/>
</>
);
};

export default StreamDetailsTab;
1 change: 0 additions & 1 deletion packages/www/components/StreamDetails/StreamHeadingBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ const StreamHeadingBox = ({
align="end"
css={{
pb: "$3",
mb: "$5",
width: "100%",
}}>
<Heading size="2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import RelativeTime from "../RelativeTime";
import ShowURL from "../ShowURL";
import Record from "./Record";
import { QuestionMarkCircledIcon as Help } from "@radix-ui/react-icons";
import { useState } from "react";
import { Stream } from "livepeer";

const Cell = ({ children, css = {} }) => {
Expand All @@ -15,22 +14,17 @@ const Cell = ({ children, css = {} }) => {
);
};

export type StreamDetailsBoxProps = {
export type StreamOverviewBoxProps = {
stream: Stream & { suspended?: boolean };
globalIngestUrl: string;
globalSrtIngestUrl: string;
globalPlaybackUrl: string;
invalidateStream: () => void;
};

const StreamDetailsBox = ({
const StreamOverviewBox = ({
stream,
globalIngestUrl,
globalSrtIngestUrl,
globalPlaybackUrl,
invalidateStream,
}: StreamDetailsBoxProps) => {
const [keyRevealed, setKeyRevealed] = useState(false);
}: StreamOverviewBoxProps) => {
return (
<>
<Box
Expand All @@ -42,7 +36,7 @@ const StreamDetailsBox = ({
width: "100%",
}}>
<Heading size="1" css={{ fontWeight: 600 }}>
Details
Overview
</Heading>
</Box>
<Flex
Expand All @@ -60,39 +54,10 @@ const StreamDetailsBox = ({
fontSize: "$2",
position: "relative",
}}>
<Cell css={{ color: "$hiContrast" }}>Stream name</Cell>
<Cell>{stream.name}</Cell>
<Cell css={{ color: "$hiContrast" }}>Stream ID</Cell>
<Cell>
<ClipButton value={stream.id} text={stream.id} />
</Cell>
<Cell css={{ color: "$hiContrast" }}>Stream key</Cell>
<Cell>
{keyRevealed ? (
<Flex>
<ClipButton value={stream.streamKey} text={stream.streamKey} />
</Flex>
) : (
<Button type="button" onClick={() => setKeyRevealed(true)}>
Reveal stream key
</Button>
)}
</Cell>
<Cell css={{ color: "$hiContrast" }}>RTMP ingest URL</Cell>
<Cell>
<ShowURL url={globalIngestUrl} anchor={false} />
</Cell>
<Cell css={{ color: "$hiContrast" }}>SRT ingest URL</Cell>
<Cell>
<ShowURL
url={globalSrtIngestUrl}
shortendUrl={globalSrtIngestUrl.replace(
globalSrtIngestUrl.slice(38),
"…"
)}
anchor={false}
/>
</Cell>
<Cell css={{ color: "$hiContrast" }}>Playback ID</Cell>
<Cell>
<ClipButton value={stream.playbackId} text={stream.playbackId} />
Expand All @@ -108,26 +73,6 @@ const StreamDetailsBox = ({
anchor={false}
/>
</Cell>
<Cell css={{ color: "$hiContrast" }}>Record sessions</Cell>
<Cell>
<Flex css={{ position: "relative", top: "2px" }}>
<Box css={{ mr: "$2" }}>
<Record stream={stream} invalidate={invalidateStream} />
</Box>
<Tooltip
multiline
content={
<Box>
When enabled, transcoded streaming sessions will be recorded
and stored by Livepeer Studio. Each recorded session will
have a recording .m3u8 URL for playback and an MP4 download
link. This feature is currently free.
</Box>
}>
<Help />
</Tooltip>
</Flex>
</Cell>
<Cell css={{ color: "$hiContrast" }}>Created at</Cell>
<Cell>
<RelativeTime
Expand All @@ -148,12 +93,30 @@ const StreamDetailsBox = ({
</Cell>
<Cell css={{ color: "$hiContrast" }}>Status</Cell>
<Cell>{stream.isActive ? "Active" : "Idle"}</Cell>
<Cell css={{ color: "$hiContrast" }}>Suspended</Cell>
<Cell>{stream.suspended ? "Suspended" : "Normal"}</Cell>
<Cell css={{ color: "$hiContrast" }}>Record sessions</Cell>
<Cell>
<Flex css={{ position: "relative", top: "2px" }}>
<Box css={{ mr: "$2" }}>
<Record stream={stream} invalidate={invalidateStream} />
</Box>
<Tooltip
multiline
content={
<Box>
When enabled, transcoded streaming sessions will be recorded
and stored by Livepeer Studio. Each recorded session will
have a recording .m3u8 URL for playback and an MP4 download
link. This feature is currently free.
</Box>
}>
<Help />
</Tooltip>
</Flex>
</Cell>
</Box>
</Flex>
</>
);
};

export default StreamDetailsBox;
export default StreamOverviewBox;
53 changes: 30 additions & 23 deletions packages/www/components/StreamDetails/StreamOverviewTab.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,39 @@
import SessionsTable from "./SessionsTable";
import MultistreamTargetsTable from "components/StreamDetails/MultistreamTargetsTable";
import { Text } from "@livepeer/design-system";
import { useApi } from "hooks";
import { useEffect, useState } from "react";
import StreamOverviewBox from "./StreamOverviewBox";

const StreamOverviewTab = ({ id, stream, streamHealth, invalidateStream }) => {
const { getIngest } = useApi();

const [ingest, setIngest] = useState(null);

useEffect(() => {
getIngest(true)
.then((ingest) => {
if (Array.isArray(ingest)) {
ingest.sort((a, b) => a.base.localeCompare(b.base));
}
if ((ingest?.length ?? 0) > 0) {
setIngest(ingest?.[0]);
}
})
.catch((err) => console.error(err)); // todo: surface this
}, [id]);

const playbackId = (stream || {}).playbackId || "";

let globalPlaybackUrl = "";

if (ingest) {
globalPlaybackUrl = `${ingest?.playback ?? ""}/${playbackId}/index.m3u8`;
}

return (
<>
<MultistreamTargetsTable
<StreamOverviewBox
stream={stream}
streamHealth={streamHealth}
globalPlaybackUrl={globalPlaybackUrl}
invalidateStream={invalidateStream}
css={{ mb: "$7" }}
emptyState={
<Text variant="neutral" size="2">
No targets
</Text>
}
tableLayout="auto"
border
/>
<SessionsTable
streamId={id}
emptyState={
<Text variant="neutral" size="2">
No sessions
</Text>
}
tableLayout="auto"
border
/>
</>
);
Expand Down
Loading

0 comments on commit c5063f5

Please sign in to comment.