Skip to content

Commit

Permalink
use managed media source api on safari
Browse files Browse the repository at this point in the history
  • Loading branch information
sainak committed Aug 28, 2024
1 parent bcf974f commit 74e2785
Show file tree
Hide file tree
Showing 8 changed files with 712 additions and 1,475 deletions.
2,000 changes: 658 additions & 1,342 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@
"react-infinite-scroll-component": "^6.1.0",
"react-markdown": "^8.0.7",
"react-pdf": "^9.1.0",
"react-player": "^2.16.0",
"react-redux": "^8.1.1",
"react-webcam": "^7.2.0",
"redux": "^4.2.1",
Expand Down Expand Up @@ -161,4 +160,4 @@
"node": ">=20.12.0"
},
"packageManager": "[email protected]"
}
}
17 changes: 0 additions & 17 deletions src/Common/hooks/useHLSPlayer.ts

This file was deleted.

16 changes: 12 additions & 4 deletions src/Common/hooks/useMSEplayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export const useMSEMediaPlayer = ({
if (mseQueue.length > 0) {
const packet = mseQueue.shift();
// Check if SourceBuffer has been removed before appending buffer
if (mseSourceBuffer.removed) {
if ("removed" in mseSourceBuffer && mseSourceBuffer.removed) {
console.error("Attempted to append to a removed SourceBuffer.");
return;
}
Expand All @@ -128,7 +128,7 @@ export const useMSEMediaPlayer = ({
const readPacket = (packet: any) => {
if (!mseStreamingStarted) {
// Check if SourceBuffer has been removed before appending buffer
if (mseSourceBuffer.removed) {
if ("removed" in mseSourceBuffer && mseSourceBuffer.removed) {
console.error("Attempted to append to a removed SourceBuffer.");
return;
}
Expand All @@ -146,8 +146,16 @@ export const useMSEMediaPlayer = ({
// location.protocol == 'https:' ? protocol = 'wss' : protocol = 'ws';
try {
wsRef.current?.close();
const mse = new MediaSource();
if (videoEl) {
if (!videoEl) return;

let mse: MediaSource;
if ("ManagedMediaSource" in window) {
mse = new (window.ManagedMediaSource as typeof MediaSource)();
console.log(mse);
videoEl.disableRemotePlayback = true;
videoEl.srcObject = mse;
} else {
mse = new MediaSource();
videoEl.src = window.URL.createObjectURL(mse);
}

Expand Down
49 changes: 12 additions & 37 deletions src/Components/CameraFeed/CameraFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { AssetData } from "../Assets/AssetTypes";
import useOperateCamera, { PTZPayload } from "./useOperateCamera";
import usePlayer from "./usePlayer";
import { getStreamUrl } from "./utils";
import ReactPlayer from "react-player";
import { classNames, isIOS } from "../../Utils/utils";
import FeedAlert, { FeedAlertState } from "./FeedAlert";
import FeedNetworkSignal from "./FeedNetworkSignal";
Expand Down Expand Up @@ -31,7 +30,7 @@ interface Props {
}

export default function CameraFeed(props: Props) {
const playerRef = useRef<HTMLVideoElement | ReactPlayer | null>(null);
const playerRef = useRef<HTMLVideoElement | null>(null);
const playerWrapperRef = useRef<HTMLDivElement>(null);
const streamUrl = getStreamUrl(props.asset);
const inlineControls = useBreakpoints({ default: false, sm: true });
Expand Down Expand Up @@ -225,42 +224,18 @@ export default function CameraFeed(props: Props) {
)}

{/* Video Player */}
{isIOS ? (
<div className="absolute inset-0">
<ReactPlayer
url={streamUrl}
ref={playerRef.current as LegacyRef<ReactPlayer>}
controls={false}
pip={false}
playsinline
playing
muted
width="100%"
height="100%"
onPlay={player.onPlayCB}
onEnded={() => player.setStatus("stop")}
onError={(e, _, hlsInstance) => {
if (e === "hlsError") {
const recovered = hlsInstance.recoverMediaError();
console.info(recovered);
}
}}
/>
</div>
) : (
<video
onContextMenu={(e) => e.preventDefault()}
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")}
<video
onContextMenu={(e) => e.preventDefault()}
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>}

Check failure on line 237 in src/Components/CameraFeed/CameraFeed.tsx

View workflow job for this annotation

GitHub Actions / lint

Delete `··`
/>
)}
/>

{inlineControls && player.status === "playing" && controls}
</div>
Expand Down
71 changes: 18 additions & 53 deletions src/Components/CameraFeed/CameraFeedOld.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import Page from "../Common/components/Page.js";
import ConfirmDialog from "../Common/ConfirmDialog.js";
import { FieldLabel } from "../Form/FormFields/FormField.js";
import useFullscreen from "../../Common/hooks/useFullscreen.js";
import ReactPlayer from "react-player";
import { isIOS } from "../../Utils/utils.js";

Check failure on line 25 in src/Components/CameraFeed/CameraFeedOld.tsx

View workflow job for this annotation

GitHub Actions / lint

'isIOS' is defined but never used. Allowed unused vars must match /^_/u
import TextFormField from "../Form/FormFields/TextFormField.js";

Expand Down Expand Up @@ -111,9 +110,7 @@ const CameraFeedOld = (props: any) => {

const videoEl = liveFeedPlayerRef.current as HTMLVideoElement;

const streamUrl = isIOS
? `https://${middlewareHostname}/stream/${cameraAsset?.accessKey}/channel/0/hls/live/index.m3u8?uuid=${cameraAsset?.accessKey}&channel=0`
: `wss://${middlewareHostname}/stream/${cameraAsset?.accessKey}/channel/0/mse?uuid=${cameraAsset?.accessKey}&channel=0`;
const streamUrl = `wss://${middlewareHostname}/stream/${cameraAsset?.accessKey}/channel/0/mse?uuid=${cameraAsset?.accessKey}&channel=0`;

const { startStream } = useMSEMediaPlayer({
config: {
Expand Down Expand Up @@ -416,56 +413,24 @@ const CameraFeedOld = (props: any) => {
<div className="mt-4 flex flex-col">
<div className="relative mt-4 flex flex-col gap-4 lg:flex-row">
<div className="flex-1">
{/* ADD VIDEO PLAYER HERE */}
<div className="relative mb-4 aspect-video w-full rounded bg-primary-100 lg:mb-0">
{isIOS ? (
<div className="absolute inset-0">
<ReactPlayer
url={streamUrl}
ref={liveFeedPlayerRef.current}
controls={false}
playsinline
playing
muted
width="100%"
height="100%"
onPlay={() => {
setVideoStartTime(() => new Date());
setStreamStatus(StreamStatus.Playing);
}}
onWaiting={() => {
const delay = calculateVideoLiveDelay();
if (delay > 5) {
setStreamStatus(StreamStatus.Loading);
}
}}
onError={(e, _, hlsInstance) => {
if (e === "hlsError") {
const recovered = hlsInstance.recoverMediaError();
console.info(recovered);
}
}}
/>
</div>
) : (
<video
id="mse-video"
autoPlay
muted
playsInline
className="z-10 h-full w-full"
ref={liveFeedPlayerRef}
onPlay={() => {
setVideoStartTime(() => new Date());
}}
onWaiting={() => {
const delay = calculateVideoLiveDelay();
if (delay > 5) {
setStreamStatus(StreamStatus.Loading);
}
}}
></video>
)}
<video
id="mse-video"
autoPlay
muted
playsInline
className="z-10 h-full w-full"
ref={liveFeedPlayerRef}
onPlay={() => {
setVideoStartTime(() => new Date());
}}
onWaiting={() => {
const delay = calculateVideoLiveDelay();
if (delay > 5) {
setStreamStatus(StreamStatus.Loading);
}
}}
></video>

{streamStatus === StreamStatus.Playing &&
calculateVideoLiveDelay() > 3 && (
Expand Down
26 changes: 10 additions & 16 deletions src/Components/CameraFeed/usePlayer.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,25 @@
import { MutableRefObject, useCallback, useState } from "react";
import ReactPlayer from "react-player";
import { isIOS } from "../../Utils/utils";
import { useHLSPLayer } from "../../Common/hooks/useHLSPlayer";
import { IOptions, useMSEMediaPlayer } from "../../Common/hooks/useMSEplayer";

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

export default function usePlayer(
streamUrl: string,
ref: MutableRefObject<HTMLVideoElement | ReactPlayer | null>,
ref: MutableRefObject<HTMLVideoElement | null>,
) {
const [playedOn, setPlayedOn] = useState<Date>();
const [status, setStatus] = useState<StreamStatus>("stop");

// Voluntarily disabling react-hooks/rules-of-hooks for this line as order of
// hooks is maintained (since platform won't change in runtime)
const _start = isIOS
? // eslint-disable-next-line react-hooks/rules-of-hooks
useHLSPLayer(ref.current as ReactPlayer).startStream
: // eslint-disable-next-line react-hooks/rules-of-hooks
useMSEMediaPlayer({
// Voluntarily set to "" as it's used by `stopStream` only (which is not
// used by this hook)
config: { middlewareHostname: "" },
url: streamUrl,
videoEl: ref.current as HTMLVideoElement,
}).startStream;
// eslint-disable-next-line react-hooks/rules-of-hooks
const _start = useMSEMediaPlayer({
// Voluntarily set to "" as it's used by `stopStream` only (which is not
// used by this hook)
config: { middlewareHostname: "" },
url: streamUrl,
videoEl: ref.current as HTMLVideoElement,
}).startStream;

const initializeStream = useCallback(
({ onSuccess, onError }: IOptions) => {
Expand All @@ -39,7 +33,7 @@ export default function usePlayer(
},
});
},
[ref.current, streamUrl],
[ref.current, streamUrl,],

Check failure on line 36 in src/Components/CameraFeed/usePlayer.tsx

View workflow job for this annotation

GitHub Actions / lint

Delete `,`
);

const onPlayCB = () => {
Expand Down
5 changes: 1 addition & 4 deletions src/Components/CameraFeed/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { MutableRefObject } from "react";
import { AssetClass, AssetData } from "../Assets/AssetTypes";
import { getCameraConfig } from "../../Utils/transformUtils";
import { isIOS } from "../../Utils/utils";

export const calculateVideoDelay = (
ref: MutableRefObject<HTMLVideoElement | null>,
Expand All @@ -26,7 +25,5 @@ export const getStreamUrl = (asset: AssetData) => {
const host = asset.resolved_middleware?.hostname;
const uuid = config.accessKey;

return isIOS
? `https://${host}/stream/${uuid}/channel/0/hls/live/index.m3u8?uuid=${uuid}&channel=0`
: `wss://${host}/stream/${uuid}/channel/0/mse?uuid=${uuid}&channel=0`;
return `wss://${host}/stream/${uuid}/channel/0/mse?uuid=${uuid}&channel=0`;
};

0 comments on commit 74e2785

Please sign in to comment.