Skip to content

Basic layouts

In this guide, you’ll learn how layouts in Smelter works and how to position and resize components. It includes examples and explanation how component size is calculated.

  1. Init Smelter and connect inputs and outputs

    import { View } from "@swmansion/smelter"
    import Smelter from "@swmansion/smelter-node"
    function App() {
    return (
    <View style={{ backgroundColor: "#4d4d4d" }} />
    )
    }
    async function start() {
    const smelter = new Smelter();
    await smelter.init();
    await smelter.registerInput("input_1", {
    2 collapsed lines
    type: "mp4",
    serverPath: "input1.mp4"
    });
    await smelter.registerInput("input_2", {
    2 collapsed lines
    type: "mp4",
    serverPath: "input2.mp4"
    })
    await smelter.registerOutput("output", <App />, {
    11 collapsed lines
    type: "rtp_stream",
    transportProtocol: "udp",
    port: 9004,
    ip: "127.0.0.1",
    video: {
    resolution: { width: 1280, height: 720 },
    encoder: { type: "ffmpeg_h264" },
    },
    audio: {
    encoder: { type: "opus", channels: "stereo" },
    }
    });
    await smelter.start();
    }

    Starts the Smelter server with 2 input streams (input_1 and input_2) and output stream that generates a blank output.

  2. Add input_1 to the scene.

    import { View, InputStream } from "@swmansion/smelter"
    import Smelter from "@swmansion/smelter-node"
    function App() {
    return (
    <View style={{ backgroundColor: "#4d4d4d" }}>
    <InputStream inputId="input_1" />
    </View>
    )
    }
    30 collapsed lines
    async function start() {
    const smelter = new Smelter();
    await smelter.init();
    await smelter.registerInput("input_1", {
    type: "mp4",
    serverPath: "input1.mp4"
    });
    await smelter.registerInput("input_2", {
    type: "mp4",
    serverPath: "input2.mp4"
    });
    await smelter.registerOutput("output", <App />, {
    type: "rtp_stream",
    transportProtocol: "udp",
    port: 9004,
    ip: "127.0.0.1",
    video: {
    resolution: { width: 1280, height: 720 },
    encoder: { type: "ffmpeg_h264" },
    },
    audio: {
    encoder: { type: "opus", channels: "stereo" },
    }
    });
    await smelter.start();
    }

    The input stream in the example has a resolution 1920x1080, but it is rendered on the 1270x720 output. as a result only part of the stream is visible.

  3. Resize input_1 to fill the output.

    import { View, Rescaler, InputStream } from "@swmansion/smelter"
    import Smelter from "@swmansion/smelter-node"
    function App() {
    return (
    <View style={{ backgroundColor: "#4d4d4d" }}>
    <Rescaler>
    <InputStream inputId="input_1" />
    </Rescaler>
    </View>
    )
    }
    30 collapsed lines
    async function start() {
    const smelter = new smelter();
    await smelter.init();
    await smelter.registerInput("input_1", {
    type: "mp4",
    serverPath: "input1.mp4"
    });
    await smelter.registerInput("input_2", {
    type: "mp4",
    serverPath: "input2.mp4"
    })
    await smelter.registerOutput("output", <App />, {
    type: "rtp_stream",
    transportProtocol: "udp",
    port: 9004,
    ip: "127.0.0.1",
    video: {
    resolution: { width: 1280, height: 720 },
    encoder: { type: "ffmpeg_h264" },
    },
    audio: {
    encoder: { type: "opus", channels: "stereo" },
    }
    });
    await smelter.start();
    }

    Input stream now fully fits inside the output.

    Explanation:

    • Root View component in the example takes it size from the output itself (1280x720).
    • Rescaler is the only child without width/height specified, so it takes its size from the View component.
    • InputStream is resized by Rescaler to fit inside it.
  4. Show both inputs side-by-side.

    import { View, Rescaler, InputStream } from "@swmansion/smelter"
    import Smelter from "@swmansion/smelter-node"
    function App() {
    return (
    <View style={{ backgroundColor: "#4d4d4d" }}>
    <Rescaler>
    <InputStream inputId="input_1" />
    </Rescaler>
    <Rescaler>
    <InputStream inputId="input_2" />
    </Rescaler>
    </View>
    )
    }
    30 collapsed lines
    async function start() {
    const smelter = new smelter();
    await smelter.init();
    await smelter.registerInput("input_1", {
    type: "mp4",
    serverPath: "input1.mp4"
    });
    await smelter.registerInput("input_2", {
    type: "mp4",
    serverPath: "input2.mp4"
    })
    await smelter.registerOutput("output", <App />, {
    type: "rtp_stream",
    transportProtocol: "udp",
    port: 9004,
    ip: "127.0.0.1",
    video: {
    resolution: { width: 1280, height: 720 },
    encoder: { type: "ffmpeg_h264" },
    },
    audio: {
    encoder: { type: "opus", channels: "stereo" },
    }
    });
    await smelter.start();
    }

    Both input streams are now visible.

    Explanation:

    • Root View component in the example takes it size from the output itself (1280x720).
    • Root View has 2 child components, each without any dimensions specified, so both child components (Rescaler) will have size 640x720.
    • InputStream is resized by Rescaler to fit inside it. Aspect ratio does not match, so it is centered vertically.
  5. Put one of the inputs in the corner

    import { View, Rescaler, InputStream } from "@swmansion/smelter"
    import Smelter from "@swmansion/smelter-node"
    function App() {
    return (
    <View style={{ backgroundColor: "#4d4d4d" }}>
    <Rescaler>
    <InputStream inputId="input_1" />
    </Rescaler>
    <Rescaler style={{ width: 320, height: 180, top: 20, right: 20 }}>
    <InputStream inputId="input_2" />
    </Rescaler>
    </View>
    )
    }
    30 collapsed lines
    async function start() {
    const smelter = new Smelter();
    await smelter.init();
    await smelter.registerInput("input_1", {
    type: "mp4",
    serverPath: "input1.mp4"
    });
    await smelter.registerInput("input_2", {
    type: "mp4",
    serverPath: "input2.mp4"
    })
    await smelter.registerOutput("output", <App />, {
    type: "rtp_stream",
    transportProtocol: "udp",
    port: 9004,
    ip: "127.0.0.1",
    video: {
    resolution: { width: 1280, height: 720 },
    encoder: { type: "ffmpeg_h264" },
    },
    audio: {
    encoder: { type: "opus", channels: "stereo" },
    }
    });
    await smelter.start();
    }

    Both input streams are now visible.

    Explanation:

    • Root View component in the example takes it size from the output itself (1280x720)
    • Root View has 2 child components, but only one is positioned statically. (See absolute positioning)
      • First Rescaler is the only static child of the View, so it has the same size as its parent
      • Second Rescaler has fields top and right defined so it is positioned absolutely. It is rendered on top of static content of the View and it does not affect layout of the other sibling components.
    • InputStream components are resized by Rescaler components to fit inside them.