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.
-
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", {type: "mp4",serverPath: "input1.mp4",2 collapsed lines});await smelter.registerInput("input_2", {type: "mp4",serverPath: "input2.mp4",2 collapsed lines});await smelter.registerOutput("output", <App />, {type: "rtp_stream",transportProtocol: "udp",11 collapsed linesport: 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_1andinput_2) and output stream that generates a blank output. -
Add
input_1to 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 linesasync 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 the1270x720output. as a result only part of the stream is visible. -
Resize
input_1to 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 linesasync 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
Viewcomponent in the example takes it size from the output itself (1280x720). Rescaleris the only child without width/height specified, so it takes its size from theViewcomponent.InputStreamis resized byRescalerto fit inside it.
- Root
-
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 linesasync 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
Viewcomponent in the example takes it size from the output itself (1280x720). - Root
Viewhas 2 child components, each without any dimensions specified, so both child components (Rescaler) will have size640x720. InputStreamis resized byRescalerto fit inside it. Aspect ratio does not match, so it is centered vertically.
- Root
-
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 linesasync 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
Viewcomponent in the example takes it size from the output itself (1280x720) - Root
Viewhas 2 child components, but only one is positioned statically. (See absolute positioning)- First
Rescaleris the only static child of the View, so it has the same size as its parent - Second
Rescalerhas fieldstopandrightdefined 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.
- First
InputStreamcomponents are resized byRescalercomponents to fit inside them.
- Root