How to Use Node.js Workers for Video Encoding
Video encoding is very CPU-intensive, but let’s look at how to do this anyway

Last night, I again worked on my side project, Mini Video Encoder. I added a new service for encoding video. The Workflow Encoder, as I call the service, uses Node.js. You may know that video encoding is very CPU-intensive.
In the past, Node.js has never been a good candidate for performing CPU-intensive tasks. The reason for this is Node.js uses a single thread to execute JavaScript code. But since the release of 11.7.0, Node.js has a new module called worker_threads
.
The Node.js team describes Workers like this:
“Workers (threads) are useful for performing CPU-intensive JavaScript operations. They will not help much with I/O-intensive work. Node.js’s built-in asynchronous I/O operations are more efficient than Workers can be.”
Because of this, I implemented the Workflow Encoder using Workers. In this piece, I describe the implementation.
Encoding Video
Before describing the implementation, I need to talk a bit about why encoding is necessary for streaming video.
The Workflow Encoder is part of a larger project called Mini Video Encoder (MVE). MVE is an open-source platform for converting videos. After conversion, a regular HTTP server can deliver the videos using adaptive streaming.
What is adaptive streaming?
Adaptive streaming changes the bit rate and resolution of a video during playback. A video player continuously measures the bandwidth of the connection and increases or decreases the quality of the video.
To make this possible, Workflow Encoder creates many versions of the same video. Each version has a different bit rate and resolution. This list of bit rates and resolutions is called an encoding ladder.
Apple recommends the following encoding ladder for a 1080p video. If you encode your videos, use this ladder — the video will play correctly on Apple devices.
Using this ladder means the Workflow Encoder has to create nine different encodings. So you understand why we need to use the most efficient method for video encoding.
The Workflow Encoder uses FFmpeg 4.2.2. FFmpeg is an open-source video and audio encoder. It also uses the Fluent ffmpeg-API to make interacting with FFmpeg easier.
For testing, I used the 1080p version of Caminandes 3: Llamigos. Caminandes 3 is a funny little open-source animation video of 2.5 minutes from the Blender Institute.
Implementing the Workflow Encoder Using Workers
When the Workflow Engine receives a video job, it first splits the job. For each bit-rate and resolution combination from the encoding ladder, the Workflow Engine creates a task.

The Workflow Encoder communicates with the Workflow engine and asks if there’s a task to perform. The Workflow Encoder uses the REST API of the Workflow engine for communication.
Creating and starting a Worker
If there’s a task to perform, the Workflow Encoder calls the function startEncoder
. The startEncoder
function creates and starts the Worker. It creates the Worker
object by calling the constructor and passing a relative path to a JavaScript file. This file contains the function that must be executed on a different thread.
The second argument of the Worker
constructor is an options object. I use this to set workerData
to the encodingInstructions
. This assignment clones encodingInstructions
and makes it available in the Worker function.
The actual function performing the work is encode
in encoder.js
. The file contains a single function that the Worker executes. I left out most to focus only on the essential part.
On line 5, I get the encodingInstructions
by reading workerData
. At the start of the file, I also require parentPort
, which the function uses to perform communication between the main thread and the Worker thread.
How to communicate from the Worker to the main thread
The Worker module allows bidirectional communication between the main and worker thread. I’d like to get feedback from the Worker thread on the main thread about the progress.
Most examples use postMessage
to send a string. Instead, I communicate an object. I want to send different messages and be able to distinguish them in the main thread.
The main thread, on the other end, receives these messages through events. On line 8, worker.on
defines the function that receives the message events.
Depending on the type of message, the function performs a specific action. In the case of the PROGRESS
message, it logs the message using the log object. This way, we see the progress of the worker in the main thread.

How to communicate from the main thread to the Worker
We also want to be able to communicate the other way around, from the main thread to the Worker — for example, when we want to stop a running encoding task.
The mechanism is almost the same as communicating from the Worker to the main thread. Here, we use the postMessage
method on the worker
object.
The Worker uses the parentPort
to create an event handler for receiving messages.
When the Worker receives STOP_ENCODING
, it stops the running encoding task. It stops the task by calling ffmpegCommand.kill()
. This will SIGKILL
to the FFmpeg process and stop it.

Conclusion
I like how the Node.js team implemented threading using Workers. By having an explicit communication channel between threads, they prevent synchronization issues. Synchronization causes a lot of issues with other programming languages.
The current implementation of the Workflow Encoder uses Workers for encoding video. You can find the source on GitHub. It’s still a work in progress.
Thank you for reading.