"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = WebVideo;
exports.ProgressiveMP4Player = ProgressiveMP4Player;
exports.ProgressiveWebMPlayer = ProgressiveWebMPlayer;
exports.HLSPlayer = HLSPlayer;
exports.WebRTCPlayer = WebRTCPlayer;
exports.WebRTCPlayerInner = WebRTCPlayerInner;
exports.WebcamIngestPlayer = WebcamIngestPlayer;
const tslib_1 = require("tslib");
const jsx_runtime_1 = require("react/jsx-runtime");
const hls_js_1 = tslib_1.__importDefault(require("hls.js"));
const react_1 = require("react");
const __1 = require("../..");
const atoms_1 = require("../../lib/theme/atoms");
const index_1 = require("../ui/index");
const loader_1 = require("../ui/loader");
const shared_1 = require("./shared");
const use_webrtc_1 = tslib_1.__importStar(require("./use-webrtc"));
const webrtc_diagnostics_1 = require("./webrtc-diagnostics");
const webrtc_primitives_1 = require("./webrtc-primitives");
function assignVideoRef(ref, instance) {
    if (!ref)
        return;
    if (typeof ref === "function")
        ref(instance);
    else
        ref.current = instance;
}
function useVideoDimensions(videoRef) {
    const [dimensions, setDimensions] = (0, react_1.useState)({ width: 0, height: 0 });
    (0, react_1.useEffect)(() => {
        if (!videoRef.current)
            return;
        function updateSize() {
            setDimensions({
                width: videoRef.current?.videoWidth || 0,
                height: videoRef.current?.videoHeight || 0,
            });
        }
        updateSize();
        const observer = new window.ResizeObserver(updateSize);
        observer.observe(videoRef.current);
        videoRef.current.addEventListener("loadedmetadata", updateSize);
        videoRef.current.addEventListener("resize", updateSize);
        return () => {
            observer.disconnect();
            videoRef.current?.removeEventListener("loadedmetadata", updateSize);
            videoRef.current?.removeEventListener("resize", updateSize);
        };
    }, [videoRef, videoRef.current]);
    return dimensions;
}
function WebVideo(props) {
    const inProto = (0, __1.usePlayerStore)((x) => x.protocol);
    const isIngesting = (0, __1.usePlayerStore)((x) => x.ingestConnectionState !== null);
    const selectedRendition = (0, __1.usePlayerStore)((x) => x.selectedRendition);
    const src = (0, __1.usePlayerStore)((x) => x.src);
    const setPlayerWidth = (0, __1.usePlayerStore)((x) => x.setPlayerWidth);
    const setPlayerHeight = (0, __1.usePlayerStore)((x) => x.setPlayerHeight);
    const { url, protocol } = (0, shared_1.srcToUrl)({ src: src, selectedRendition }, inProto);
    const videoRef = (0, react_1.useRef)(null);
    const dimensions = useVideoDimensions(videoRef);
    (0, react_1.useEffect)(() => {
        if (videoRef.current) {
            setPlayerWidth(dimensions.width);
            setPlayerHeight(dimensions.height);
        }
    }, [dimensions, setPlayerWidth, setPlayerHeight]);
    const playerProps = {
        url,
        videoRef,
        objectFit: props?.objectFit,
        pictureInPictureEnabled: props?.pictureInPictureEnabled,
    };
    return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: isIngesting ? ((0, jsx_runtime_1.jsx)(WebcamIngestPlayer, { ...playerProps })) : protocol === __1.PlayerProtocol.PROGRESSIVE_MP4 ? ((0, jsx_runtime_1.jsx)(ProgressiveMP4Player, { ...playerProps })) : protocol === __1.PlayerProtocol.PROGRESSIVE_WEBM ? ((0, jsx_runtime_1.jsx)(ProgressiveWebMPlayer, { ...playerProps })) : protocol === __1.PlayerProtocol.HLS ? ((0, jsx_runtime_1.jsx)(HLSPlayer, { ...playerProps })) : protocol === __1.PlayerProtocol.WEBRTC ? ((0, jsx_runtime_1.jsx)(WebRTCPlayer, { ...playerProps })) : ((() => {
            throw new Error(`unknown playback protocol ${inProto}`);
        })()) }));
}
const updateEvents = {
    playing: true,
    waiting: true,
    stalled: true,
    pause: true,
    suspend: true,
    mute: true,
    error: true,
};
const VideoElement = (0, react_1.forwardRef)((props, ref) => {
    const x = (0, __1.usePlayerStore)((x) => x);
    const mode = (0, __1.usePlayerStore)((x) => x.mode);
    const url = (0, __1.useStreamplaceStore)((x) => x.url);
    const playerEvent = (0, __1.usePlayerStore)((x) => x.playerEvent);
    const setMuteWasForced = (0, __1.usePlayerStore)((x) => x.setMuteWasForced);
    const ingest = (0, __1.usePlayerStore)((x) => x.ingestConnectionState !== null);
    const volume = (0, __1.useEffectiveVolume)();
    const muted = (0, __1.useMuted)();
    const setMuted = (0, __1.useSetMuted)();
    const setStatus = (0, __1.usePlayerStore)((x) => x.setStatus);
    const setUserInteraction = (0, __1.usePlayerStore)((x) => x.setUserInteraction);
    const setVideoRef = (0, __1.usePlayerStore)((x) => x.setVideoRef);
    const setPlayTime = (0, __1.usePlayerStore)((x) => x.setPlayTime);
    const setDuration = (0, __1.usePlayerStore)((x) => x.setDuration);
    const setBufferedEnd = (0, __1.usePlayerStore)((x) => x.setBufferedEnd);
    const event = (evType) => (e) => {
        console.log(evType);
        const now = new Date();
        if (updateEvents[evType]) {
            x.setStatus(evType);
        }
        console.log("Sending", evType, "status to", url);
        playerEvent(url, now.toISOString(), evType, {});
    };
    const [firstAttempt, setFirstAttempt] = (0, react_1.useState)(true);
    const setAutoplayFailed = (0, __1.usePlayerStore)((x) => x.setAutoplayFailed);
    const localVideoRef = props.videoRef ?? (0, react_1.useRef)(null);
    // setPipAction comes from Zustand store
    (0, react_1.useEffect)(() => {
        if (typeof x.setPipAction === "function") {
            const fn = () => {
                if (localVideoRef.current) {
                    try {
                        localVideoRef.current.requestPictureInPicture?.();
                    }
                    catch (err) {
                        console.error("Error requesting Picture-in-Picture:", err);
                    }
                }
                else {
                    console.log("No video ref available for PiP");
                }
            };
            x.setPipAction(fn);
        }
        // Cleanup on unmount
        return () => {
            if (typeof x.setPipAction === "function") {
                x.setPipAction(undefined);
            }
        };
    }, []);
    const canPlayThrough = (e) => {
        console.log("canPlayThrough called", {
            firstAttempt,
            videoRef: !!localVideoRef.current,
        });
        setStatus(__1.PlayerStatus.PLAYING);
        event("canplaythrough")(e);
        if (firstAttempt && localVideoRef.current) {
            setFirstAttempt(false);
            console.log("Attempting to play video");
            localVideoRef.current.play().catch((err) => {
                console.log("error playing video", err.name);
                if (err.name === "NotAllowedError") {
                    if (mode === "vod") {
                        setAutoplayFailed(true);
                    }
                    else if (localVideoRef.current) {
                        console.log("Setting muted and retrying");
                        setMuted(true);
                        localVideoRef.current.muted = true;
                        localVideoRef.current
                            .play()
                            .then(() => {
                            console.log("Muted play succeeded");
                            setMuteWasForced(true);
                        })
                            .catch((err) => {
                            console.error("Muted play also failed", err);
                            setAutoplayFailed(true);
                        });
                    }
                }
                else {
                    // For other errors (not NotAllowedError), also show play button
                    setAutoplayFailed(true);
                }
            });
        }
    };
    const handlePlaying = (e) => {
        setAutoplayFailed(false);
        event("playing")(e);
    };
    (0, react_1.useEffect)(() => {
        return () => {
            setStatus(__1.PlayerStatus.START);
        };
    }, []);
    (0, react_1.useEffect)(() => {
        if (localVideoRef.current) {
            localVideoRef.current.volume = volume;
            console.log("Setting volume to", volume);
        }
    }, [volume]);
    (0, react_1.useEffect)(() => {
        console.log(localVideoRef.current?.width, localVideoRef.current?.height);
        setVideoRef(localVideoRef);
    }, [setVideoRef, localVideoRef]);
    const handleVideoRef = (videoElement) => {
        if (typeof ref === "function") {
            ref(videoElement);
        }
        else if (ref) {
            ref.current =
                videoElement;
        }
        localVideoRef.current = videoElement;
    };
    const eventLogger = (evType) => (e) => {
        console.log("📺 Video event:", evType);
        const now = new Date();
        if (updateEvents[evType]) {
            x.setStatus(evType);
        }
        console.log("Sending", evType, "status to", url);
        playerEvent(url, now.toISOString(), evType, {});
    };
    return ((0, jsx_runtime_1.jsx)("video", { autoPlay: true, playsInline: true, ref: handleVideoRef, controls: false, src: ingest ? undefined : props.url, muted: muted, crossOrigin: "anonymous", onMouseMove: setUserInteraction, onClick: setUserInteraction, onAbort: event("abort"), onCanPlay: eventLogger, onCanPlayThroughCapture: eventLogger, onCanPlayThrough: canPlayThrough, onEmptied: event("emptied"), onEncrypted: event("encrypted"), onEnded: event("ended"), onError: event("error"), onLoadedData: event("loadeddata"), onLoadedMetadata: event("loadedmetadata"), onLoadStart: event("loadstart"), onPause: event("pause"), onPlay: event("play"), onPlaying: handlePlaying, onRateChange: event("ratechange"), onSeeked: event("seeked"), onSeeking: event("seeking"), onStalled: event("stalled"), onSuspend: event("suspend"), onTimeUpdate: (e) => {
            setPlayTime(e.target.currentTime);
        }, onDurationChange: (e) => {
            const d = e.target.duration;
            if (isFinite(d))
                setDuration(d);
        }, onProgress: (e) => {
            const el = e.target;
            if (el.buffered.length > 0) {
                setBufferedEnd(el.buffered.end(el.buffered.length - 1));
            }
        }, onVolumeChange: event("volumechange"), onWaiting: event("waiting"), style: {
            objectFit: props.objectFit || "contain",
            backgroundColor: "transparent",
            width: "100%",
            height: "100%",
            maxWidth: "100%",
            maxHeight: "100%",
            transform: ingest ? "scaleX(-1)" : undefined,
        }, disablePictureInPicture: props.pictureInPictureEnabled === false }));
});
function ProgressiveMP4Player(props) {
    return (0, jsx_runtime_1.jsx)(VideoElement, { ...props });
}
function ProgressiveWebMPlayer(props) {
    return (0, jsx_runtime_1.jsx)(VideoElement, { ...props });
}
function HLSPlayer(props) {
    const localRef = (0, react_1.useRef)(null);
    const hlsRef = (0, react_1.useRef)(null);
    const setVodLevels = (0, __1.usePlayerStore)((x) => x.setVodLevels);
    const setPlayingVODRendition = (0, __1.usePlayerStore)((x) => x.setPlayingVODRendition);
    const selectedRendition = (0, __1.usePlayerStore)((x) => x.selectedRendition);
    const mode = (0, __1.usePlayerStore)((x) => x.mode);
    // other players set some status on start, HLS doesn't, so
    // do this to make sure we we reset off of "error" state
    const setStatus = (0, __1.usePlayerStore)((x) => x.setStatus);
    (0, react_1.useEffect)(() => {
        setStatus(__1.PlayerStatus.START);
    }, [setStatus]);
    (0, react_1.useEffect)(() => {
        if (!localRef.current) {
            return;
        }
        if (hls_js_1.default.isSupported()) {
            var hls = new hls_js_1.default({ maxAudioFramesDrift: 20 });
            hlsRef.current = hls;
            hls.loadSource(props.url);
            try {
                hls.attachMedia(localRef.current);
            }
            catch (e) {
                console.error("error on attachMedia");
                hls.stopLoad();
                return;
            }
            hls.on(hls_js_1.default.Events.MANIFEST_PARSED, () => {
                if (!localRef.current) {
                    return;
                }
                localRef.current.play();
                if (mode === "vod" && hls.levels.length > 1) {
                    setVodLevels(hls.levels.map((l) => ({
                        name: l.height > 0
                            ? `${l.height}p`
                            : `${Math.round(l.bitrate / 1000)}k`,
                    })));
                }
            });
            hls.on(hls_js_1.default.Events.LEVEL_SWITCHED, (_, data) => {
                const l = hls.levels[data.level];
                if (!l)
                    return;
                const name = l.height > 0 ? `${l.height}p` : `${Math.round(l.bitrate / 1000)}k`;
                setPlayingVODRendition(name);
            });
            return () => {
                hls.stopLoad();
                hlsRef.current = null;
                setVodLevels([]);
                setPlayingVODRendition(null);
            };
        }
        else if (localRef.current.canPlayType("application/vnd.apple.mpegurl")) {
            localRef.current.src = props.url;
            localRef.current.addEventListener("canplay", () => {
                if (!localRef.current) {
                    return;
                }
                localRef.current.play();
            });
        }
    }, [props.url]);
    (0, react_1.useEffect)(() => {
        const hls = hlsRef.current;
        if (!hls || mode !== "vod")
            return;
        if (selectedRendition === "source" || selectedRendition === "auto") {
            hls.currentLevel = -1;
            return;
        }
        const idx = hls.levels.findIndex((l) => {
            const name = l.height > 0 ? `${l.height}p` : `${Math.round(l.bitrate / 1000)}k`;
            return name === selectedRendition;
        });
        if (idx !== -1)
            hls.currentLevel = idx;
    }, [selectedRendition, mode]);
    return (0, jsx_runtime_1.jsx)(VideoElement, { ...props, ref: localRef });
}
function WebRTCPlayer(props) {
    const [webrtcError, setWebrtcError] = (0, react_1.useState)(null);
    const setStatus = (0, __1.usePlayerStore)((x) => x.setStatus);
    const setProtocol = (0, __1.usePlayerStore)((x) => x.setProtocol);
    const diagnostics = (0, webrtc_diagnostics_1.useWebRTCDiagnostics)();
    // Check WebRTC compatibility on component mount
    (0, react_1.useEffect)(() => {
        try {
            (0, webrtc_primitives_1.checkWebRTCSupport)();
            console.log("WebRTC Player - Browser compatibility check passed");
            (0, webrtc_diagnostics_1.logWebRTCDiagnostics)();
        }
        catch (error) {
            console.error("WebRTC Player - Compatibility error:", error.message);
            setWebrtcError(error.message);
            setStatus(__1.PlayerStatus.START);
            return;
        }
    }, []);
    // Monitor diagnostics for errors
    (0, react_1.useEffect)(() => {
        if (!diagnostics.browserSupport && diagnostics.errors.length > 0) {
            setWebrtcError(diagnostics.errors.join(", "));
        }
    }, [diagnostics]);
    if (!diagnostics.done)
        return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {});
    if (webrtcError) {
        setProtocol(__1.PlayerProtocol.HLS);
        return ((0, jsx_runtime_1.jsx)(index_1.View, { backgroundColor: "#111", children: (0, jsx_runtime_1.jsxs)(index_1.View, { children: [(0, jsx_runtime_1.jsx)(index_1.View, { children: (0, jsx_runtime_1.jsx)(index_1.Text, { children: "WebRTC Not Supported" }) }), (0, jsx_runtime_1.jsx)(index_1.Text, { children: webrtcError }), diagnostics.errors.length > 0 && ((0, jsx_runtime_1.jsxs)(index_1.View, { children: [(0, jsx_runtime_1.jsx)(index_1.Text, { children: "Technical Details:" }), diagnostics.errors.map((error, index) => ((0, jsx_runtime_1.jsxs)(index_1.Text, { children: ["\u2022 ", error] }, index)))] })), (0, jsx_runtime_1.jsx)(index_1.Text, { children: "\u2022 To use WebRTC, you may need to disable any blocking extensions or update your browser." }), (0, jsx_runtime_1.jsx)(index_1.Text, { style: [atoms_1.mt[2]], children: "Switching to HLS..." })] }) }));
    }
    return (0, jsx_runtime_1.jsx)(WebRTCPlayerInner, { url: props.url, videoRef: props.videoRef });
}
const connectionStatusMessages = {
    initializing: "Starting up...",
    connecting: "Connecting...",
    "connection-failed": "Connecting...",
    connected: "Connected",
    reconnecting: "Reconnecting...",
    checking: "Checking connection...",
};
function WebRTCPlayerInner({ videoRef, url, width, height, }) {
    const [connectionStatus, setConnectionStatus] = (0, react_1.useState)("initializing");
    const status = (0, __1.usePlayerStore)((x) => x.status);
    const setStatus = (0, __1.usePlayerStore)((x) => x.setStatus);
    const src = (0, __1.usePlayerStore)((x) => x.src);
    const playerEvent = (0, __1.usePlayerStore)((x) => x.playerEvent);
    const spurl = (0, __1.useStreamplaceStore)((x) => x.url);
    const [mediaStream, stuck] = (0, use_webrtc_1.default)(src);
    (0, react_1.useEffect)(() => {
        if (stuck) {
            setConnectionStatus("connection-failed");
        }
        else if (mediaStream) {
            setConnectionStatus("connected");
        }
        else {
            setConnectionStatus("connecting");
        }
    }, [url, mediaStream, stuck, status]);
    (0, react_1.useEffect)(() => {
        if (stuck && status === __1.PlayerStatus.PLAYING) {
            setStatus(__1.PlayerStatus.STALLED);
        }
        if (!stuck && status === __1.PlayerStatus.STALLED) {
            setStatus(__1.PlayerStatus.PLAYING);
        }
    }, [stuck, status, mediaStream]);
    (0, react_1.useEffect)(() => {
        if (!mediaStream) {
            return;
        }
        const evt = (evType) => (e) => {
            console.log("webrtc event", evType);
            playerEvent(spurl, new Date().toISOString(), evType, {});
        };
        const active = evt("active");
        const inactive = evt("inactive");
        const ended = evt("ended");
        const mute = evt("mute");
        const unmute = evt("playing");
        mediaStream.addEventListener("active", active);
        mediaStream.addEventListener("inactive", inactive);
        mediaStream.addEventListener("ended", ended);
        for (const track of mediaStream.getTracks()) {
            track.addEventListener("ended", ended);
            track.addEventListener("mute", mute);
            track.addEventListener("unmute", unmute);
        }
        return () => {
            for (const track of mediaStream.getTracks()) {
                track.removeEventListener("ended", ended);
                track.removeEventListener("mute", mute);
                track.removeEventListener("unmute", unmute);
            }
            mediaStream.removeEventListener("active", active);
            mediaStream.removeEventListener("inactive", inactive);
            mediaStream.removeEventListener("ended", ended);
        };
    }, [mediaStream]);
    (0, react_1.useEffect)(() => {
        if (!videoRef || !videoRef.current) {
            return;
        }
        videoRef.current.srcObject = mediaStream;
    }, [mediaStream]);
    if (!mediaStream) {
        const isError = connectionStatus === "connection-failed";
        return ((0, jsx_runtime_1.jsx)(index_1.View, { backgroundColor: "#111", style: {
                minWidth: "100%",
                minHeight: "100%",
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
            }, children: (0, jsx_runtime_1.jsxs)(index_1.View, { style: {
                    borderRadius: atoms_1.borderRadius.md,
                    padding: 24,
                    alignItems: "center",
                    gap: 16,
                }, children: [!isError && (0, jsx_runtime_1.jsx)(loader_1.Loader, { size: "large" }), (0, jsx_runtime_1.jsx)(index_1.Text, { size: "lg", weight: "semibold", children: connectionStatusMessages[connectionStatus] || "Connecting..." })] }) }));
    }
    return (0, jsx_runtime_1.jsx)(VideoElement, { url: url, ref: videoRef });
}
function WebcamIngestPlayer(props) {
    const ingestMediaSource = (0, __1.usePlayerStore)((x) => x.ingestMediaSource);
    const ingestAutoStart = (0, __1.usePlayerStore)((x) => x.ingestAutoStart);
    const setIngestLive = (0, __1.usePlayerStore)((x) => x.setIngestLive);
    const [error, setError] = (0, react_1.useState)(null);
    let streamKey = null;
    const [videoElement, setVideoElement] = (0, react_1.useState)(null);
    const handleRef = (0, react_1.useCallback)((node) => {
        if (node) {
            setVideoElement(node);
        }
    }, []);
    const url = (0, __1.useStreamplaceStore)((x) => x.url);
    const [localMediaStream, setLocalMediaStream] = (0, react_1.useState)(null);
    // we assign a stream key in the webrtcingest hook
    const [remoteMediaStream, setRemoteMediaStream] = (0, use_webrtc_1.useWebRTCIngest)({
        endpoint: `${url}/api/ingest/webrtc`,
    });
    (0, react_1.useEffect)(() => {
        if (ingestMediaSource === __1.IngestMediaSource.DISPLAY) {
            navigator.mediaDevices
                .getDisplayMedia({
                audio: true,
                video: true,
            })
                .then((stream) => {
                setLocalMediaStream(stream);
            })
                .catch((e) => {
                console.error("error getting display media", e);
            });
        }
        else {
            navigator.mediaDevices
                .getUserMedia({
                audio: true,
                video: {
                    width: { min: 200, ideal: 1080, max: 2160 },
                    height: { min: 200, ideal: 1920, max: 3840 },
                },
            })
                .then((stream) => {
                setLocalMediaStream(stream);
            })
                .catch((e) => {
                console.error("error getting user media", e.name);
                if (e.name == "NotAllowedError") {
                    setError(new Error("Unable to access video! Please allow it in your browser settings."));
                }
            });
        }
    }, [ingestMediaSource]);
    (0, react_1.useEffect)(() => {
        // if (!ingestAutoStart) {
        //   setRemoteMediaStream(null);
        //   return;
        // }
        if (!localMediaStream) {
            return;
        }
        console.log("setting remote media stream", localMediaStream);
        setIngestLive(true);
        setRemoteMediaStream(localMediaStream);
    }, [localMediaStream, setIngestLive, setRemoteMediaStream]);
    (0, react_1.useEffect)(() => {
        if (!videoElement) {
            return;
        }
        if (!localMediaStream) {
            return;
        }
        videoElement.srcObject = localMediaStream;
    }, [videoElement, localMediaStream]);
    if (error) {
        return ((0, jsx_runtime_1.jsx)(index_1.View, { backgroundColor: "#111", style: {
                minWidth: "100%",
                minHeight: "100%",
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
            }, children: (0, jsx_runtime_1.jsx)(index_1.View, { backgroundColor: atoms_1.colors.destructive[900], style: {
                    borderRadius: atoms_1.borderRadius.md,
                    padding: 24,
                    alignItems: "center",
                    gap: 16,
                    maxWidth: 400,
                }, children: (0, jsx_runtime_1.jsx)(index_1.Text, { size: "xl", weight: "extrabold", color: "default", children: error.message }) }) }));
    }
    return (0, jsx_runtime_1.jsx)(VideoElement, { ...props, ref: handleRef });
}
//# sourceMappingURL=video.js.map