Quick start - Node.js
In this guide, you’ll learn how to create a basic Smelter setup in Node.js using TypeScript SDK. We will show you how to take two input videos and produce an output video with both of the inputs displayed side-by-side.
-
Create Smelter object and initialize it.
import Smelter from "@swmansion/smelter-node"async function start() {const smelter = new Smelter();await smelter.init();}It will download appropriate binaries and start the Smelter server locally.
Alternatively, you can connect to an existing instance.
import Smelter, { ExistingInstanceManager } from "@swmansion/smelter-node"async function start() {const manager = new ExistingInstanceManager({ip: "127.0.0.1",port: 8000,protocol: 'http',});const smelter = new Smelter(manager);await smelter.init();} -
Register an output stream and call
Smelter.start()
.import { View } from "@swmansion/smelter"import Smelter from "@swmansion/smelter-node"async function start() {const smelter = new Smelter();await smelter.init();await smelter.registerOutput("output", <View />, {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();}This step creates an output that renders just an empty
<View />
component. Resulting blank image is encoded and sent over UDP to local port 9004 via RTP protocol.Calling
smelter.start()
will cause all registered outputs to start producing frames. All user-facing timestamp or offset are relative to this start call.How to receive and display this stream?
Above example sends that over UDP, so you need to start listening on that port before registering the output.
GStreamer
To view the output stream with GStreamer run the following command:
gst-launch-1.0 rtpptdemux name=demux \\udpsrc port=9004 ! \"application/x-rtp\" ! queue ! demux. \\demux.src_96 ! \"application/x-rtp,media=video,clock-rate=90000,encoding-name=H264\" ! queue \\! rtph264depay ! decodebin ! videoconvert ! autovideosink \\demux.src_97 ! \"application/x-rtp,media=audio,clock-rate=48000,encoding-name=OPUS\" ! queue \\! rtpopusdepay ! decodebin ! audioconvert ! autoaudiosink sync=falseFFmpeg (
ffplay
)To view the output stream with FFmpeg:
- Create the SDP file.
output.sdp v=0o=- 0 0 IN IP4 127.0.0.1s=No Namec=IN IP4 127.0.0.1m=video 9004 RTP/AVP 96a=rtpmap:96 H264/90000a=fmtp:96 packetization-mode=1a=rtcp-mux - Run the following command
ffplay -protocol_whitelist "file,rtp,udp" output.sdp
Note: FFmpeg does not support multiplexing audio and video on the same port, so this example will only work with video.
Or checkout other alternative protocols:
Option 1: WHIP output
With WHIP output you can send it e.g. to Twitch.
import { View } from "@swmansion/smelter"import Smelter from "@swmansion/smelter-node"async function start() {const smelter = new Smelter();await smelter.init();await smelter.registerOutput("output", <View />, {type: "whip",endpointUrl: "https://g.webrtc.live-video.net:4443/v2/offer",bearerToken: "<YOUR STREAM TOKEN>",video: {resolution: { width: 1280, height: 720 },encoder: { type: "ffmpeg_h264" },},audio: {encoder: { type: "opus", channels: "stereo" },}});await smelter.start();}Option 2: MP4 output
With mp4 output you can record the output to file.
import { View } from "@swmansion/smelter"import Smelter from "@swmansion/smelter-node"async function start() {const smelter = new Smelter();await smelter.init();await smelter.registerOutput("output", <View />, {type: "mp4",serverPath: "output.mp4",video: {resolution: { width: 1280, height: 720 },encoder: { type: "ffmpeg_h264" },},audio: {encoder: { type: "aac", channels: "stereo" },}});await smelter.start();}Note that mp4 must be gracefully terminated to write metadata, so if you just kill the application the file will be corrupted. When you want to finish recording, run:
await smelter.unregisterOutput('output'); - Create the SDP file.
-
Register 2 input streams.
For simplicity, in this example that will be two mp4 files, but the same logic would apply for any other input type.
import { View } from "@swmansion/smelter"import Smelter from "@swmansion/smelter-node"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", <View />, {11 collapsed linestype: "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();}If you run this example now, you will see the same output as in the previous step. Input streams are registered, but they are not used anywhere in the scene definition yet.
-
Render one of the registered input streams on the output.
In the earlier steps we passed
<View />
to theregisterOutput
call. Now let’s switch that to something more complex. We are defining here new React component calledApp
, and using it as an output.import { Tiles, InputStream } from "@swmansion/smelter"import Smelter from "@swmansion/smelter-node"function App() {return <InputStream inputId="input_1" volume={0.9} />}async function start() {const smelter = new Smelter();11 collapsed linesawait 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",10 collapsed linestransportProtocol: "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();}Resulting output stream will show
input_1
input stream and play the audio from that source with slightly lowered volume. (If input has a different resolution it won’t be automatically rescaled). -
Modify the components to display both inputs.
Tiles
component is a simple way to display element in a layout that uses available space efficiently (similar to stream layout in most video call apps).import { Tiles, InputStream } from "@swmansion/smelter"import Smelter from "@swmansion/smelter-node"function App() {return (<Tiles style={{ backgroundColor: "#4d4d4d" }}><InputStream inputId="input_1" volume={0.9} /><InputStream inputId="input_2" /></Tiles>)}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();}Resulting output stream will show both inputs side-by-side as shown below. Audio from both streams is mixed together, but
input_1
audio has slightly lowered volume.