2026 East Asia Influencer Marketing Playbook
Mar 10, 2026
Blog
Influencer Marketing
Hi! This is Peter Li, a backend engineer in Anymind DOOH Team. I’m excited to share the design of DOOH video player and how we solve a problem with WebAssembly.
Our DOOH system includes a camera above a digital signboard that leverages on computer vision to understand human traffic to determine advertisement viewability and non-personally identifiable audience data for gender, age and expression attributes.
This information is used to alternate and deliver advertising creatives along with measuring advertising effectiveness, providing more relevant advertising based on audience demographics. Additionally, to protect the privacy of individuals, video from the camera is not transferred into the storage server, and only text-based attributes are processed.

We divide core features into different modules. Let me introduce some key features in DOOH Devices.
In our design, we have multiple devices in one location just like the picture above, and we want to
synchronize each device to make sure that all the devices should play the same
video or switch videos at exactly the same time.
In our previous implementation, we implement our video player 100% by Javascript. The video player will read playlist, synchronize with current time, and decide which video should be played in which second. But we always have a problem that there is always a gap up to 1 second between each device.
Here is a pseudo-code of our previous video player:
setInterval(function() {
// get the video should be displayed
const videoIdShouldBePlayed = getVideoFromPlaylist();
// if video is still playing, ignore it
if (videoIdShouldBePlayed === playingVideoId) {
return
}
// get the start time
const startAt = getStartAt();
// then play this video
playVideo(videoIdShouldBePlayed, startAt)
}, 50)
From the pseudo-code, we can see that the setInterval() loop is running
every 50ms, which means new video should be played in maximum 50ms
after previous video is finished. But why we can observe a gap up to
1 second? After some testing, we found out that the problem is
Javascript event loop.
JavaScript has a single thread concurrency model based on an event loop, which is responsible
for executing the code, collecting and processing events, and executing queued sub-tasks.
The job of Event Loop is to monitor the Call Stack and the Callback Queue. If the Call Stack is empty, the Event Loop will take the first event from the queue and will push it to the Call Stack, which effectively runs it.
However, this single thread model will have a problem that we can not guarantee that the function will be executed every exactly 50ms. According to MDN:
The timeout can also fire later than expected if the page (or the OS/browser) is busy with other tasks.
In other words, it is just meant that the function will be executed after a given delay, and once the browser’s thread is free to execute it.
WebAssembly is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.
WebAssembly supports multiple languages like C, Golang, Rust. Since most of our tech stacks are using golang, we also selected golang to build the WebAssembly binary.
As we mentioned above, our performance bottleneck is the timer, and Javascript
cannot provide an accurate timer because of the single thread event loop architecture.
However, in golang, we can use time.Timer to solve the problem easily.
According to official document:
A Time represents an instant in time with nanosecond precision.
So we can simply make a timer controller like this
func (w *WebManager) Start() {
remaining := w.playCurrentVideo()
timer := time.NewTimer(remaining)
for {
<- timer.C
// now it's time to switch video!
remaining = w.playCurrentVideo()
timer.Reset(remaining)
}
}
WebManager.Start() function can be called any time. When it’s being called,
playCurrentVideo() will control the html to play the correct video in the right
second, and return the remaining time of this video in milliseconds.
Then we can start a timer and wait for the channel to be triggered.
In line <- timer.C, when it’s triggered, then we know it’s the correct time
to start playing a new video! Then just reset the timer to the new remaining time
and keep looping.
As a result, WebAssembly helped us to solve the problem. Now each device can be synchronized perfectly. Here’s the summary of advantages and disadvantages of WebAssembly: