Skip to content

Commit

Permalink
fix hls stream on ios
Browse files Browse the repository at this point in the history
  • Loading branch information
sainak committed Sep 17, 2024
1 parent b0151a9 commit 5828bbc
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 248 deletions.
85 changes: 38 additions & 47 deletions src/Components/CameraFeed/CameraFeed.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { LegacyRef, useCallback, useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { AssetData } from "../Assets/AssetTypes";
import useOperateCamera, { PTZPayload } from "./useOperateCamera";
import usePlayer from "./usePlayer";
import { getStreamUrl } from "./utils";
import { classNames, isIOS } from "../../Utils/utils";
import FeedAlert, { FeedAlertState } from "./FeedAlert";
import FeedNetworkSignal from "./FeedNetworkSignal";
import FeedNetworkSignal, { StreamStatus } from "./FeedNetworkSignal";
import NoFeedAvailable from "./NoFeedAvailable";
import FeedControls from "./FeedControls";
import FeedWatermark from "./FeedWatermark";
import useFullscreen from "../../Common/hooks/useFullscreen";
import useBreakpoints from "../../Common/hooks/useBreakpoints";
import { GetPresetsResponse } from "./routes";
import VideoPlayer from "./videoPlayer";

interface Props {
children?: React.ReactNode;
Expand All @@ -32,15 +32,13 @@ interface Props {
export default function CameraFeed(props: Props) {
const playerRef = useRef<HTMLVideoElement | null>(null);
const playerWrapperRef = useRef<HTMLDivElement>(null);
const streamUrl = getStreamUrl(props.asset);
const [streamUrl, setStreamUrl] = useState<string>("");
const inlineControls = useBreakpoints({ default: false, sm: true });

const player = usePlayer(playerRef);

const [isFullscreen, setFullscreen] = useFullscreen();
const [state, setState] = useState<FeedAlertState>();
useEffect(() => setState(player.status), [player.status, setState]);

const [playedOn, setPlayedOn] = useState<Date>();
const [playerStatus, setPlayerStatus] = useState<StreamStatus>("stop");
// Move camera when selected preset has changed
useEffect(() => {
async function move(preset: PTZPayload) {
Expand Down Expand Up @@ -74,40 +72,27 @@ export default function CameraFeed(props: Props) {

const initializeStream = useCallback(async () => {
if (!playerRef.current) return;

const _streamUrl = await props
setPlayerStatus("loading");
await props
.operate({ type: "get_stream_token" })
.then(({ res, data }) => {
if (res?.status != 200) {
setState("authentication_error");
return props.onStreamError?.();
}
const result = data?.result as { token: string };
return getStreamUrl(props.asset, result.token);
return setStreamUrl(getStreamUrl(props.asset, result.token));
})
.catch(() => {
setState("host_unreachable");
return props.onStreamError?.();
});

if (!_streamUrl) return;
player.initializeStream({
url: _streamUrl,
onSuccess: async () => {
props.onStreamSuccess?.();
const { res } = await props.operate({ type: "get_status" });
if (res?.status === 500) {
setState("host_unreachable");
}
},
onError: props.onStreamError,
});
}, [playerRef.current]);
}, []);

// Start stream on mount
useEffect(() => {
initializeStream();
}, [initializeStream]);
}, []);

const resetStream = () => {
setState("loading");
Expand Down Expand Up @@ -174,7 +159,7 @@ export default function CameraFeed(props: Props) {
isFullscreen ? "hidden lg:flex" : "flex",
"items-center justify-between px-4 py-0.5 transition-all duration-500 ease-in-out lg:py-1",
(() => {
if (player.status !== "playing") {
if (playerStatus !== "playing") {
return "bg-black text-zinc-400";
}

Expand All @@ -188,7 +173,7 @@ export default function CameraFeed(props: Props) {
>
<div
className={classNames(
player.status !== "playing"
playerStatus !== "playing"
? "pointer-events-none opacity-10"
: "opacity-100",
"transition-all duration-200 ease-in-out",
Expand All @@ -209,8 +194,8 @@ export default function CameraFeed(props: Props) {
>
<FeedNetworkSignal
playerRef={playerRef as any}
playedOn={player.playedOn}
status={player.status}
playedOn={playedOn}
status={playerStatus}
onReset={resetStream}
/>
</div>
Expand All @@ -220,7 +205,7 @@ export default function CameraFeed(props: Props) {
<div className="group relative aspect-video bg-black">
{/* Notifications */}
<FeedAlert state={state} />
{player.status === "playing" && <FeedWatermark />}
{playerStatus === "playing" && <FeedWatermark />}

{/* No Feed informations */}
{(() => {
Expand All @@ -231,7 +216,7 @@ export default function CameraFeed(props: Props) {
message="Host Unreachable"
className="text-warning-500"
icon="l-exclamation-triangle"
streamUrl={streamUrl}
streamUrl=""
asset={props.asset}
onResetClick={resetStream}
/>
Expand All @@ -242,7 +227,7 @@ export default function CameraFeed(props: Props) {
message="Authentication Error"
className="text-warning-500"
icon="l-exclamation-triangle"
streamUrl={streamUrl}
streamUrl=""
asset={props.asset}
onResetClick={resetStream}
/>
Expand All @@ -253,7 +238,7 @@ export default function CameraFeed(props: Props) {
message="Offline"
className="text-secondary-500"
icon="l-exclamation-triangle"
streamUrl={streamUrl}
streamUrl=""
asset={props.asset}
onResetClick={resetStream}
/>
Expand All @@ -262,26 +247,32 @@ export default function CameraFeed(props: Props) {
})()}

{/* Video Player */}
<video
onContextMenu={(e) => e.preventDefault()}
<VideoPlayer
playerRef={playerRef}
streamUrl={streamUrl}
className="absolute inset-x-0 mx-auto aspect-video max-h-full w-full"
id="mse-video"
autoPlay
muted
disablePictureInPicture
playsInline
onPlay={player.onPlayCB}
onEnded={() => player.setStatus("stop")}
ref={playerRef as LegacyRef<HTMLVideoElement>}
onPlay={() => {
setPlayedOn(new Date());
setState("playing");
setPlayerStatus("playing");
}}
onEnded={() => setPlayerStatus("stop")}
onSuccess={async () => {
props.onStreamSuccess?.();
const { res } = await props.operate({ type: "get_status" });
if (res?.status === 500) {
setState("host_unreachable");
}
}}
onError={props.onStreamError}
/>

{inlineControls && player.status === "playing" && controls}
{inlineControls && playerStatus === "playing" && controls}
</div>
{!inlineControls && (
<div
className={classNames(
"py-4 transition-all duration-500 ease-in-out",
player.status !== "playing"
playerStatus !== "playing"
? "pointer-events-none px-6 opacity-30"
: "px-12 opacity-100",
)}
Expand Down
38 changes: 18 additions & 20 deletions src/Components/CameraFeed/CameraFeedOld.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
deleteAssetBed,
} from "../../Redux/actions.js";
import { CameraPTZ, getCameraPTZ } from "../../Common/constants.js";
import { StreamStatus, useMSEMediaPlayer } from "./useMSEplayer.js";
import { useFeedPTZ } from "./useFeedPTZ.js";
import * as Notification from "../../Utils/Notifications.js";
import { AxiosError } from "axios";
Expand All @@ -20,6 +19,14 @@ import ConfirmDialog from "../Common/ConfirmDialog.js";
import { FieldLabel } from "../Form/FormFields/FormField.js";
import useFullscreen from "../../Common/hooks/useFullscreen.js";
import TextFormField from "../Form/FormFields/TextFormField.js";
import VideoPlayer from "./videoPlayer.js";

export enum StreamStatus {
Playing,
Stop,
Loading,
Offline,
}

export const FeedCameraPTZHelpButton = (props: { cameraPTZ: CameraPTZ[] }) => {
const { cameraPTZ } = props;
Expand Down Expand Up @@ -103,10 +110,7 @@ const CameraFeedOld = (props: any) => {
const isExtremeSmallScreen =
width <= extremeSmallScreenBreakpoint ? true : false;
const liveFeedPlayerRef = useRef<any>(null);

const videoEl = liveFeedPlayerRef.current as HTMLVideoElement;

const { startStream } = useMSEMediaPlayer({ videoEl });
const [streamUrl, setStreamUrl] = useState<string>("");

const refreshPresetsHash = props.refreshPresetsHash;

Expand Down Expand Up @@ -238,21 +242,16 @@ const CameraFeedOld = (props: any) => {
const startStreamFeed = useCallback(async () => {
if (!liveFeedPlayerRef.current) return;

let _streamUrl = "";
await getStreamToken({
onSuccess: (data) => {
_streamUrl = `wss://${middlewareHostname}/stream/${cameraAsset?.accessKey}/channel/0/mse?uuid=${cameraAsset?.accessKey}&channel=0&token=${data.token}`;
setStreamUrl(
`wss://${middlewareHostname}/stream/${cameraAsset?.accessKey}/channel/0/mse?uuid=${cameraAsset?.accessKey}&channel=0&token=${data.token}`,
);
},
onError: () => {
setStreamStatus(StreamStatus.Offline);
},
});
if (!_streamUrl) return;
startStream({
url: _streamUrl,
onSuccess: () => setStreamStatus(StreamStatus.Playing),
onError: () => setStreamStatus(StreamStatus.Offline),
});
}, [liveFeedPlayerRef.current]);

const viewOptions = (page: number) => {
Expand Down Expand Up @@ -412,13 +411,10 @@ const CameraFeedOld = (props: any) => {
<div className="relative mt-4 flex flex-col gap-4 lg:flex-row">
<div className="flex-1">
<div className="relative mb-4 aspect-video w-full rounded bg-primary-100 lg:mb-0">
<video
id="mse-video"
autoPlay
muted
playsInline
<VideoPlayer
playerRef={liveFeedPlayerRef}
streamUrl={streamUrl}
className="z-10 h-full w-full"
ref={liveFeedPlayerRef}
onPlay={() => {
setVideoStartTime(() => new Date());
}}
Expand All @@ -428,7 +424,9 @@ const CameraFeedOld = (props: any) => {
setStreamStatus(StreamStatus.Loading);
}
}}
></video>
onSuccess={() => setStreamStatus(StreamStatus.Playing)}
onError={() => setStreamStatus(StreamStatus.Offline)}
/>

{streamStatus === StreamStatus.Playing &&
calculateVideoLiveDelay() > 3 && (
Expand Down
2 changes: 1 addition & 1 deletion src/Components/CameraFeed/FeedAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Transition } from "@headlessui/react";
import { useEffect, useState } from "react";
import CareIcon, { IconName } from "../../CAREUI/icons/CareIcon";
import { classNames } from "../../Utils/utils";
import { StreamStatus } from "./usePlayer";
export type StreamStatus = "playing" | "stop" | "loading" | "offline";

export type FeedAlertState =
| StreamStatus
Expand Down
7 changes: 5 additions & 2 deletions src/Components/CameraFeed/FeedNetworkSignal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useEffect, useState } from "react";
import { calculateVideoDelay } from "./utils";
import NetworkSignal from "../../CAREUI/display/NetworkSignal";
import { StreamStatus } from "./usePlayer";

export type StreamStatus = "playing" | "stop" | "loading" | "offline";

interface Props {
playerRef: React.RefObject<HTMLVideoElement>;
Expand All @@ -23,7 +24,9 @@ export default function FeedNetworkSignal(props: Props) {
// 2. This value may become negative when the web-socket stream
// disconnects while the tab was not in focus.
if (-5 > delay || delay > 5) {
props.onReset();
if (document.hasFocus() && props.status !== "loading") {
props.onReset();
}
}
}, 1000);

Expand Down
Loading

0 comments on commit 5828bbc

Please sign in to comment.