Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit b357168

Browse files
Extend hls component (#72)
1 parent f313d18 commit b357168

8 files changed

+198
-30
lines changed

src/components/AddHlsComponent.tsx

+64-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import React, { FC, useState } from "react";
1+
import React, { FC } from "react";
22
import { useServerSdk } from "./ServerSdkContext";
3+
import { atomWithStorage } from "jotai/utils";
4+
import { useAtom } from "jotai";
35

46
type Props = {
57
roomId: string;
@@ -8,18 +10,71 @@ type Props = {
810
hasHlsComponent: boolean;
911
};
1012

13+
const isLLHlsAtom = atomWithStorage("hls-endpoint-is-LLHls", true);
14+
const persistentAtom = atomWithStorage("hls-endpoint-persistent", false);
15+
const subscribeModeAtom = atomWithStorage<"auto" | "manual">("hls-endpoint-subscribe-mode", "auto");
16+
const targetWindowDurationAtom = atomWithStorage<null | number>("hls-endpoint-target-window-duration", null);
17+
1118
const AddHlsComponent: FC<Props> = ({ roomId, refetchIfNeeded, isHLSSupported, hasHlsComponent }) => {
1219
const { roomApi } = useServerSdk();
13-
const [isLLHls, setIsLLHls] = useState(true);
20+
const [isLLHls, setIsLLHls] = useAtom(isLLHlsAtom);
21+
const [persistent, setPersistent] = useAtom(persistentAtom);
22+
const [subscribeMode, setSubscribeMode] = useAtom(subscribeModeAtom);
23+
const [targetWindowDuration, setTargetWindowDuration] = useAtom(targetWindowDurationAtom);
1424

1525
return (
1626
<div className="w-full card bg-base-100 shadow-xl indicator">
1727
<div className="card-body p-4">
18-
<div className="flex flex-row justify-between">
19-
<div className="flex flex-row gap-2 items-center">
20-
<h3>Low Latency HLS:</h3>
21-
<input type="checkbox" className="checkbox" checked={isLLHls} onChange={() => setIsLLHls(!isLLHls)} />
28+
<div className="flex w-full gap-2">
29+
<input
30+
value={targetWindowDuration ?? ""}
31+
onChange={(e) => {
32+
if (e.target.value === "") {
33+
setTargetWindowDuration(null);
34+
return;
35+
}
36+
37+
const parsed = parseInt(e.target.value);
38+
if (isNaN(parsed)) return;
39+
if (parsed < 0) return;
40+
setTargetWindowDuration(parsed);
41+
}}
42+
className="input input-bordered flex-1"
43+
placeholder="Target window duration"
44+
/>
45+
46+
<div className="flex flex-col justify-center tooltip" data-tip="Persistent">
47+
<input
48+
className="checkbox"
49+
type="checkbox"
50+
checked={persistent}
51+
onChange={() => setPersistent((prev) => !prev)}
52+
/>
53+
</div>
54+
55+
<div className="flex flex-col justify-center tooltip" data-tip="Low Latency HLS:">
56+
<input
57+
className="checkbox"
58+
type="checkbox"
59+
checked={isLLHls}
60+
onChange={() => setIsLLHls((prev) => !prev)}
61+
/>
2262
</div>
63+
64+
<label
65+
data-tip="Subscribe mode"
66+
className="flex flex-row justify-start gap-1 label cursor-pointer form-control tooltip tooltip-info tooltip-top"
67+
>
68+
<span className="label-text">manual</span>
69+
<input
70+
type="checkbox"
71+
className="toggle"
72+
checked={subscribeMode === "auto"}
73+
onChange={() => setSubscribeMode((prev) => (prev === "manual" ? "auto" : "manual"))}
74+
/>
75+
<span className="label-text">auto</span>
76+
</label>
77+
2378
<div
2479
className={isHLSSupported && !hasHlsComponent ? "" : "tooltip tooltip-info z-10"}
2580
data-tip={
@@ -38,6 +93,9 @@ const AddHlsComponent: FC<Props> = ({ roomId, refetchIfNeeded, isHLSSupported, h
3893
type: "hls",
3994
options: {
4095
lowLatency: isLLHls,
96+
persistent: persistent,
97+
subscribeMode: subscribeMode,
98+
targetWindowDuration: targetWindowDuration,
4199
},
42100
})
43101
.then(() => {

src/components/ComponentsInRoom.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const ComponentInRoom: FC<RoomComponentProps> = ({ component, refetchIfNeeded })
2626
}}
2727
/>
2828
<div className="card-body p-4">
29-
<div className="flex flex-col">
29+
<div className="flex flex-col gap-2">
3030
<div className="flex flex-row justify-between">
3131
<div className="card-title">
3232
<div
@@ -37,7 +37,7 @@ const ComponentInRoom: FC<RoomComponentProps> = ({ component, refetchIfNeeded })
3737
{component.type}
3838
</div>
3939
{component.id}
40-
<CopyToClipboardButton text={component.id} />
40+
<CopyToClipboardButton text={component.id} tooltipText="COPY COMPONENT ID" />
4141
</div>
4242
</div>
4343

src/components/CopyButton.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ const copyTokenToClipboard = (text: string) => {
2424
showToastInfo("Copied to clipboard", { duration: 1000 });
2525
};
2626

27-
export const CopyToClipboardButton = ({ text }: { text: string }) => {
27+
export const CopyToClipboardButton = ({ text, tooltipText }: { text: string; tooltipText?: string }) => {
2828
return (
29-
<div className="tooltip tooltip-info z-10" data-tip="COPY">
29+
<div className="tooltip tooltip-info z-10" data-tip={tooltipText ?? "COPY"}>
3030
<button className="btn btn-sm mx-1 my-0" onClick={() => copyTokenToClipboard(text)}>
3131
<GoCopy size={24} />
3232
</button>

src/components/HLSPlayback.tsx

+68-11
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,92 @@
1-
import { useCallback, useRef, useState } from "react";
1+
import React, { useCallback, useRef } from "react";
22
import Hls from "hls.js";
33
import { useServerSdk } from "./ServerSdkContext";
44
import { CopyLinkButton } from "./CopyButton";
5+
import { atomWithStorage } from "jotai/utils";
6+
import { useAtom } from "jotai";
7+
8+
const autoPlayHlsAtom = atomWithStorage("hls-auto-play", true);
9+
const showHlsPreviewAtom = atomWithStorage("show-hls-preveiew", true);
510

611
export default function HlsPlayback({ roomId, isPlayable }: { roomId: string; isPlayable: boolean }) {
712
const { signalingHost, currentHttpProtocol } = useServerSdk();
8-
const [autoPlay, setAutoPlay] = useState<boolean>(false);
13+
const [autoPlay, setAutoPlay] = useAtom(autoPlayHlsAtom);
14+
const [showHlsPreview, setShowHlsPreview] = useAtom(showHlsPreviewAtom);
915
const hls = useRef<Hls | null>(null);
16+
1017
const hlsLink = `${currentHttpProtocol}://${signalingHost}/hls/${roomId}/index.m3u8`;
18+
1119
const loadUrl = useCallback(
1220
(media: HTMLVideoElement | null) => {
1321
hls.current?.destroy();
1422
if (!media) return;
15-
hls.current = new Hls();
23+
24+
hls.current = new Hls({
25+
playlistLoadPolicy: {
26+
default: {
27+
maxTimeToFirstByteMs: 10000,
28+
maxLoadTimeMs: 20000,
29+
timeoutRetry: {
30+
maxNumRetry: 2,
31+
retryDelayMs: 0,
32+
maxRetryDelayMs: 0,
33+
},
34+
errorRetry: {
35+
backoff: "linear",
36+
maxNumRetry: 100,
37+
retryDelayMs: 1000,
38+
maxRetryDelayMs: 20000,
39+
},
40+
},
41+
},
42+
});
43+
1644
hls.current.loadSource(hlsLink);
1745
hls.current.attachMedia(media);
1846
},
1947
[hlsLink],
2048
);
2149

2250
return (
23-
<div className="w-full pt-2 flex flex-col gap-2">
51+
<div className="w-full flex flex-col gap-2">
2452
<div className="flex flex-row gap-2 items-center">
25-
<h3>Play HLS when ready</h3>
26-
<input type="checkbox" className="toggle" checked={autoPlay} onChange={() => setAutoPlay(!autoPlay)} />
27-
</div>
28-
<div className="flex flex-row gap-2">
29-
<h3>Copy HLS source:</h3>
30-
<CopyLinkButton url={hlsLink} />
53+
<div className="flex flex-row gap-2 items-center">
54+
<h3>Show HLS preview</h3>
55+
<input
56+
type="checkbox"
57+
className="toggle"
58+
checked={showHlsPreview}
59+
onChange={() => setShowHlsPreview((prev) => !prev)}
60+
/>
61+
</div>
62+
63+
<div className="flex flex-row items-center tooltip" data-tip="Auto play">
64+
<input
65+
className="checkbox"
66+
type="checkbox"
67+
checked={autoPlay}
68+
onChange={() => setAutoPlay((prev) => !prev)}
69+
/>
70+
</div>
71+
72+
<button
73+
className="btn btn-sm btn-info mx-1 my-0"
74+
onClick={() => {
75+
const hlsClient = hls.current;
76+
if (!hlsClient) return;
77+
78+
hlsClient.loadSource(hlsLink);
79+
}}
80+
>
81+
Refetch
82+
</button>
83+
84+
<div className="flex flex-row gap-2 items-center">
85+
<h3>Copy HLS source:</h3>
86+
<CopyLinkButton url={hlsLink} />
87+
</div>
3188
</div>
32-
{isPlayable && autoPlay && <video controls ref={loadUrl} autoPlay muted className="w-full" />}
89+
{showHlsPreview && isPlayable && <video controls ref={loadUrl} autoPlay={autoPlay} muted className="w-full" />}
3390
</div>
3491
);
3592
}

src/components/ServerSdkContext.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useContext, useMemo } from "react";
2-
import { RoomApi } from "../server-sdk";
2+
import { HlsApi, RoomApi } from "../server-sdk";
33
import axios from "axios";
44
import { ServerMessage } from "../protos/jellyfish/server_notifications";
55
import { showToastError } from "./Toasts";
@@ -10,6 +10,7 @@ export type ServerSdkType = {
1010
signalingPath: string | null;
1111
currentHttpProtocol: string | null;
1212
roomApi: RoomApi | null;
13+
hlsApi: HlsApi | null;
1314
httpApiUrl: string | null;
1415
serverWebsocket: WebSocket | null;
1516
serverToken: string | null;
@@ -69,12 +70,14 @@ export const ServerSDKProvider = ({
6970
});
7071
}
7172
const roomApi = useMemo(() => (httpApiUrl ? new RoomApi(undefined, httpApiUrl, client) : null), [client, httpApiUrl]);
73+
const hlsApi = useMemo(() => (httpApiUrl ? new HlsApi(undefined, httpApiUrl, client) : null), [client, httpApiUrl]);
7274

7375
return (
7476
<ServerSdkContext.Provider
7577
value={{
7678
httpApiUrl,
7779
roomApi,
80+
hlsApi,
7881
serverWebsocket,
7982
serverToken,
8083
signalingProtocol,

src/containers/Client.tsx

+45-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { CopyToClipboardButton } from "../components/CopyButton";
77
import { useServerSdk } from "../components/ServerSdkContext";
88
import { useLogging } from "../components/useLogging";
99
import { useConnectionToasts } from "../components/useConnectionToasts";
10-
import { showToastError } from "../components/Toasts";
10+
import { showToastError, showToastInfo } from "../components/Toasts";
1111
import { SignalingUrl, TrackEncoding } from "@jellyfish-dev/react-client-sdk";
1212
import { useStore } from "./RoomsContext";
1313
import { getBooleanValue } from "../utils/localStorageUtils";
@@ -20,6 +20,7 @@ import { checkJSON } from "./StreamingSettingsPanel";
2020
import { atomFamily, atomWithStorage } from "jotai/utils";
2121
import { useSetAtom } from "jotai";
2222
import { TrackMetadata } from "../jellyfish.types";
23+
import { ComponentOptionsHLSSubscribeModeEnum } from "../server-sdk";
2324

2425
type ClientProps = {
2526
roomId: string;
@@ -30,6 +31,7 @@ type ClientProps = {
3031
remove: (roomId: string) => void;
3132
setToken: (token: string) => void;
3233
removeToken: () => void;
34+
hlsMode?: ComponentOptionsHLSSubscribeModeEnum;
3335
};
3436

3537
export const createDefaultTrackMetadata = (type: string) =>
@@ -64,7 +66,23 @@ export const trackMetadataAtomFamily = atomFamily((clientId) =>
6466
atomWithStorage<Record<TrackId, TrackMetadata> | null>(`track-metadata-${clientId}`, null),
6567
);
6668

67-
export const Client = ({ roomId, peerId, token, id, refetchIfNeeded, remove, removeToken, setToken }: ClientProps) => {
69+
const prepareHlsButtonMessage = (hlsMode?: ComponentOptionsHLSSubscribeModeEnum): string | null => {
70+
if (hlsMode === undefined) return "There is no HLS component in this room";
71+
if (hlsMode === "auto") return "HLS is in automatic subscription mode";
72+
else return null;
73+
};
74+
75+
export const Client = ({
76+
roomId,
77+
peerId,
78+
token,
79+
id,
80+
refetchIfNeeded,
81+
remove,
82+
removeToken,
83+
setToken,
84+
hlsMode,
85+
}: ClientProps) => {
6886
const { state, dispatch } = useStore();
6987
const client = state.rooms[roomId].peers[peerId].client;
7088
const tracks = state.rooms[roomId].peers[peerId].tracks || [];
@@ -81,7 +99,7 @@ export const Client = ({ roomId, peerId, token, id, refetchIfNeeded, remove, rem
8199

82100
const api = client.useSelector((snapshot) => snapshot.connectivity.api);
83101
const jellyfishClient = client.useSelector((snapshot) => snapshot.connectivity.client);
84-
const { signalingHost, signalingPath, signalingProtocol } = useServerSdk();
102+
const { signalingHost, signalingPath, signalingProtocol, hlsApi } = useServerSdk();
85103
const [showClientState, setShowClientState] = useLocalStorageState(`show-client-state-json-${peerId}`);
86104
const [attachClientMetadata, setAttachClientMetadata] = useLocalStorageState(`attach-client-metadata-${peerId}`);
87105
const [showMetadataEditor, setShowMetadataEditor] = useLocalStorageState(`show-metadata-editor-${peerId}`);
@@ -228,6 +246,8 @@ export const Client = ({ roomId, peerId, token, id, refetchIfNeeded, remove, rem
228246
if (!trackId) throw Error("Adding track error!");
229247
};
230248

249+
const hlsMessage = prepareHlsButtonMessage(hlsMode);
250+
231251
return (
232252
<div className="flex flex-col gap-1 mx-1">
233253
<div className="card w-150 bg-base-100 shadow-xl indicator">
@@ -243,7 +263,7 @@ export const Client = ({ roomId, peerId, token, id, refetchIfNeeded, remove, rem
243263
<div className="flex flex-row justify-between gap-2 items-center">
244264
<h1 className="card-title relative">
245265
<div className="z-10">
246-
Client: <span className="text-xs">{peerId}</span>
266+
Peer: <span className="text-xs">{peerId}</span>
247267
</div>
248268
<div className="tooltip tooltip-top tooltip-primary absolute -ml-3 -mt-1 " data-tip={fullState?.status}>
249269
<BadgeStatus status={fullState?.status} />
@@ -382,6 +402,27 @@ export const Client = ({ roomId, peerId, token, id, refetchIfNeeded, remove, rem
382402
/>
383403
<span className="text ml-2">Show metadata editor</span>
384404
</label>
405+
<div className="tooltip tooltip-info z-10" data-tip={hlsMessage}>
406+
<button
407+
className="btn btn-sm btn-warning"
408+
disabled={hlsMode !== "manual"}
409+
onClick={() => {
410+
hlsApi
411+
?.subscribeHlsTo(roomId, {
412+
origins: [peerId],
413+
})
414+
.then(() => {
415+
showToastInfo(`Subscribed to all tracks of the user ${peerId}`);
416+
})
417+
.catch((e) => {
418+
console.error(e);
419+
showToastError(`Subscribing peer ${peerId} failed`);
420+
});
421+
}}
422+
>
423+
Add to hls
424+
</button>
425+
</div>
385426
</div>
386427
<div className="flex flex-col gap-2">
387428
{showMetadataEditor && (

src/containers/JellyfishInstance.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export const JellyfishInstance = ({
8484
<div className="card bg-base-100 shadow-xl">
8585
<div className="card-body p-4">
8686
<div className="flex flex-row">
87-
<div className="card-title flex flex-row flex-wrap items-start">
87+
<div className="card-title flex flex-row flex-wrap items-start items-center">
8888
Jellyfish: <span className="text-xs">{host}</span>
8989
<button
9090
className="btn btn-sm btn-info mx-1 my-0"

0 commit comments

Comments
 (0)