Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 73d2cec8 authored by Simon Chan's avatar Simon Chan
Browse files

feat(demo): add a full screen status bar

parent c5e3e62d
Loading
Loading
Loading
Loading
+4 −7
Original line number Diff line number Diff line
@@ -77,13 +77,10 @@ const ITEMS = computed(() => {
        iconProps: { iconName: Icons.FullScreenMaximize },
        iconOnly: true,
        text: "Fullscreen",
        onClick: (async () => {
            STATE.deviceView?.enterFullscreen();
            if ("keyboard" in navigator) {
                // @ts-expect-error
                await navigator.keyboard.lock();
            }
        }) as () => void,
        onClick: action(() => {
            STATE.fullScreenContainer?.requestFullscreen();
            STATE.isFullScreen = true;
        }),
    });

    result.push(
+21 −11
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import {
import { action, autorun, makeAutoObservable, runInAction } from "mobx";
import { GLOBAL_STATE } from "../../state";
import { ProgressStream } from "../../utils";
import { DeviceViewRef } from "../device-view";
import { fetchServer } from "./fetch-server";
import { MuxerStream, RECORD_STATE } from "./recorder";
import { H264Decoder, SETTING_STATE } from "./settings";
@@ -36,9 +35,11 @@ const NOOP = () => {
export class ScrcpyPageState {
    running = false;

    deviceView: DeviceViewRef | null = null;
    fullScreenContainer: HTMLDivElement | null = null;
    rendererContainer: HTMLDivElement | null = null;

    isFullScreen = false;

    logVisible = false;
    log: string[] = [];
    demoModeVisible = false;
@@ -89,8 +90,8 @@ export class ScrcpyPageState {
            start: false,
            stop: action.bound,
            dispose: action.bound,
            handleDeviceViewRef: action.bound,
            handleRendererContainerRef: action.bound,
            setFullScreenContainer: action.bound,
            setRendererContainer: action.bound,
            clientPositionToDevicePosition: false,
        });

@@ -100,6 +101,16 @@ export class ScrcpyPageState {
            }
        });

        if (typeof document === "object") {
            document.addEventListener("fullscreenchange", () => {
                if (!document.fullscreenElement) {
                    runInAction(() => {
                        this.isFullScreen = false;
                    });
                }
            });
        }

        autorun(() => {
            if (this.rendererContainer && this.decoder) {
                while (this.rendererContainer.firstChild) {
@@ -370,21 +381,20 @@ export class ScrcpyPageState {
        this.fps = "0";
        clearTimeout(this.fpsCounterIntervalId);

        if (this.isFullScreen) {
            document.exitFullscreen().catch(NOOP);
        if ("keyboard" in navigator) {
            // @ts-expect-error
            navigator.keyboard.unlock();
            this.isFullScreen = false;
        }

        this.client = undefined;
        this.running = false;
    }

    handleDeviceViewRef(element: DeviceViewRef | null) {
        this.deviceView = element;
    setFullScreenContainer(element: HTMLDivElement | null) {
        this.fullScreenContainer = element;
    }

    handleRendererContainerRef(element: HTMLDivElement | null) {
    setRendererContainer(element: HTMLDivElement | null) {
        this.rendererContainer = element;
    }

+9 −4
Original line number Diff line number Diff line
@@ -192,10 +192,18 @@ function handleBlur() {
export function VideoContainer() {
    const classes = useClasses();

    useEffect(() => {
        window.addEventListener("blur", handleBlur);

        return () => {
            window.removeEventListener("blur", handleBlur);
        };
    }, []);

    const [container, setContainer] = useState<HTMLDivElement | null>(null);

    useEffect(() => {
        STATE.handleRendererContainerRef(container);
        STATE.setRendererContainer(container);

        if (!container) {
            return;
@@ -205,11 +213,8 @@ export function VideoContainer() {
            passive: false,
        });

        window.addEventListener("blur", handleBlur);

        return () => {
            container.removeEventListener("wheel", handleWheel);
            window.removeEventListener("blur", handleBlur);
        };
    }, [container]);

+73 −19
Original line number Diff line number Diff line
import { Dialog, LayerHost, ProgressIndicator, Stack } from "@fluentui/react";
import { useId } from "@fluentui/react-hooks";
import { makeStyles, shorthands } from "@griffel/react";
import { WebCodecsDecoder } from "@yume-chan/scrcpy-decoder-webcodecs";
import { action, runInAction } from "mobx";
import { observer } from "mobx-react-lite";
@@ -16,9 +17,38 @@ import {
    SettingItem,
    VideoContainer,
} from "../components/scrcpy";
import { GLOBAL_STATE } from "../state";
import { CommonStackTokens, RouteStackProps, formatSpeed } from "../utils";

const useClasses = makeStyles({
    layerHost: {
        position: "absolute",
        top: 0,
        bottom: 0,
        left: 0,
        right: 0,
        pointerEvents: "none",
        ...shorthands.margin(0),
    },
    fullScreenContainer: {
        flexGrow: 1,
        display: "flex",
        flexDirection: "column",
        backgroundColor: "black",
    },
    fullScreenStatusBar: {
        display: "flex",
        color: "white",
        columnGap: "12px",
        ...shorthands.padding("8px", "20px"),
    },
    spacer: {
        flexGrow: 1,
    },
});

const ConnectionDialog = observer(() => {
    const classes = useClasses();
    const layerHostId = useId("layerHost");

    const [isClient, setIsClient] = useState(false);
@@ -33,18 +63,7 @@ const ConnectionDialog = observer(() => {

    return (
        <>
            <LayerHost
                id={layerHostId}
                style={{
                    position: "absolute",
                    top: 0,
                    bottom: 0,
                    left: 0,
                    right: 0,
                    margin: 0,
                    pointerEvents: "none",
                }}
            />
            <LayerHost id={layerHostId} className={classes.layerHost} />

            <Dialog
                hidden={!STATE.connecting}
@@ -97,6 +116,8 @@ const ConnectionDialog = observer(() => {
});

const Scrcpy: NextPage = () => {
    const classes = useClasses();

    useEffect(() => {
        // Detect WebCodecs support at client side
        if (
@@ -113,6 +134,25 @@ const Scrcpy: NextPage = () => {
        }
    }, []);

    const [keyboardLockEnabled, setKeyboardLockEnabled] = useState(false);
    useEffect(() => {
        if (!("keyboard" in navigator)) {
            return;
        }

        // Keyboard Lock is only effective in fullscreen mode,
        // but the `lock` method can be called at any time.

        // @ts-expect-error
        navigator.keyboard.lock();
        setKeyboardLockEnabled(true);

        return () => {
            // @ts-expect-error
            navigator.keyboard.unlock();
        };
    }, []);

    return (
        <Stack {...RouteStackProps}>
            <Head>
@@ -122,14 +162,28 @@ const Scrcpy: NextPage = () => {
            <ScrcpyCommandBar />

            <Stack horizontal grow styles={{ root: { height: 0 } }}>
                <div
                    ref={STATE.setFullScreenContainer}
                    className={classes.fullScreenContainer}
                >
                    {keyboardLockEnabled && STATE.isFullScreen && (
                        <div className={classes.fullScreenStatusBar}>
                            <div>{GLOBAL_STATE.backend?.serial}</div>
                            <div>FPS: {STATE.fps}</div>

                            <div className={classes.spacer} />

                            <div>Press and hold ESC to exit full screen</div>
                        </div>
                    )}
                    <DeviceView
                    ref={STATE.handleDeviceViewRef}
                        width={STATE.rotatedWidth}
                        height={STATE.rotatedHeight}
                        BottomElement={NavigationBar}
                    >
                        <VideoContainer />
                    </DeviceView>
                </div>

                <div
                    style={{