Using Queues in Javascript to optimize animations on low-end devices for our platform Joyn?

Oleksandr Fedotov
ProSiebenSat.1 Tech Blog
6 min readMay 10, 2021

In the field of SmartTV development, where the challenge is to provide seamless user experiences on less powerful devices, animation bottlenecks can become a significant challenge. This article explores the intricacies of SmartTV application development, with a focus on web-based TV applications, and looks at the causes of animation slowdowns on platforms such as Samsung Tizen, LG WebOS, Panasonic Viera, and PlayStation 4/5.

In the following article, we will analyze the challenges faced by our Joyn* development team. It also describes how the team tackled these issues to ensure smooth and stutter-free animations on the big screen.

*Joyn has been a 100% subsidiary in the Entertainment segment of ProSiebenSat.1 Media SE since 2022. This means that all existing and future tech content about Joyn can also be found on the ProSiebenSat.1 Tech Blog.

TVs, Animations, what could ever go wrong…

At Joyn, we build streaming applications for various platforms like the web, iOS, Android, and different TV platforms. One of the most challenging platforms is a web-based TV application that has a web browser as the host environment for the application. Samsung Tizen, LG WebOS, Panasonic Viera, and PlayStation 4/5 are some of the platforms to which we currently ship our web-based React application. What most of these platforms have in common is that they are not nearly as powerful as the average laptop and full-fledged browser.

The Joyn UX team is doing an awesome job and we are proud to have one of the best-looking UIs in the industry. This, however, introduces challenges for our engineering team to build and maintain a UI that doesn’t just look awesome but is also appealing and responsive to user actions. One of the most popular techniques to guide the user through the application is to introduce animations to handle user interactions like scrolling up/down or left/right. Currently, there are a lot of solutions on the web on how to run performant animations.

But, unfortunately, most of the solutions don’t work well out of the box and we faced several optimization challenges along the way. Let’s dive into different bottlenecks that may slow down or even ruin the animations on the TVs.

Causes for TV Animation Bottlenecks

First things first — what causes these animation bottlenecks? We can track these animation issues to two main causes:

Background tasks

Offloading render operations to the GPU is the safest option when it comes to a TV’s hardware. This helps it be smooth and prevents choppy pixels from being rendered. However, due to slower upgrades to TV hardware — even the offloading doesn’t help if the hardware is busy with other tasks in parallel.

Similarly, the background tasks of the application can also result in significant drops in FPS. Tracking operations, content fetching, and image pre-loading are just a few example tasks. Pairing this multi-tasking nature with unoptimized code or features in a few devices — consistent FPS is always an issue.

For example, one unoptimized feature is the native key-up event in the LG WebOS platform. If you were to set up a listener for the key down event, the hardware still registers the key up event as well — even though we never subscribed to it. Similarly, memory leaks (followed by frequent garbage collection) can further worsen the performance of the JS-based application.

Simultaneous Animations

Rendering multiple animations at the same time is also a nuisance. For a test run, try rendering an animation in isolation — it is going to be smooth. As you start to add more animations to your application — your performance is going to take a dip.

Although this can sometimes be resolved by adding transition delays to the animations, it is still going to take you a lot of trial and error to get the animations and their delays just right.

Resolving Animation Bottlenecks on TV Screens

Let’s move towards the resolution now. One key solution to the bottleneck is to re-prioritize the execution of tasks such that resource-intensive operations come in later. This way, your animations take preference and can take their desired resources to execute to perfection.

We had a tough time trying to figure out a viable solution from the open-source community. Readily available components were either an addition to the resource consumption or were not optimized enough to aid in our use case. After numerous tries, we had to revert to the basics of Data Structures and Algorithms to solve the problem effectively.

One of the best data structures which fit this case is the Queue. Although the FIFO structure of a Queue works great in a few cases, one can re-order elements manually at will, which makes it great for this use case. This further helps us achieve the goal of prioritizing animations over generic operations.

Here’s how we implemented our idea practically:

  1. Identify each task that is scheduled for execution
  2. Create a queue of tasks which is based on our identified priorities
  3. Queue all intensive operations at the tail of the queue
  4. Queue all animations at the head of the queue
  5. Run an animation job from the head of the queue
  6. Pause processing of animations inside the Queue until the currently running animation job is executed

This implementation helped us avoid utilizing larger third-party libraries or implementing other performance-boosting techniques to create smooth animations. Not only does it help rely on readily available data structures, but it is also much easier to implement. Let’s explore this implementation in a scaled project where we explore the usage of queues to handle animation flows.

Animation Queues

If we put the points into practice; let’s start with creating a queue (from the package ‘queue’) which we can use flexibly to maintain the ordering of our animations. Secondly — we’ll also be making sure our queue maintains a single-task concurrency throughout the execution. Luckily, the library does provide that support by default.

After initialization, we start pushing our animations into the queue. For this particular example, we’ll be making use of promises, listeners to handle the animations, and functions to animate the objects on the screen upon the resolution of these promises.

The queue manages the flow of elements (or animations in our case) one by one. But how does it continue with the next animation? Each animation function, as soon as it completes, resolves a promise which passes control to the next animation. But how will the promise be resolved?

This is where our webkitTransitionEnd listeners come into play. We simply call the waitAnimation function to do two tasks:

  • Setup event listeners on webkitTransitionEnd to be called once the animation has ended on each element in our component hierarchy
  • Resolve the promise and allow other animations to continue via the callback function

Finally, the moveElement function is called which is the actual animation and can be replaced with other animatable components.

In a nutshell, what exactly does the queue do to resolve the TV bottlenecks we’ve encountered? Here’s it:

  • Allow only a single animation to continue at a time (setting the concurrency to 1)
  • Once the function responsible for animating the object kickstarts the animation, the control is passed back to the calling function. Then, as soon as the transition completes i.e., the webkitTransitionEnd event is fired, it ultimately resolves the promise from the calling function, and the queue can continue processing other animations
  • By creating two queues — one for animation and another for tasks — we can switch between the two by pausing the tasks queue while an animation is being rendered
  • It helps to drop some animations or re-prioritize them in the queue for a later time. This way, important animations which are necessary to start the application aren’t halted. Once necessary animations are completed, less significant (but important) animations can be rendered to the screen. For example, firstly run the vertical/horizontal scroll animation and then the card’s expanding animation.
Animation prioritization for vertical scroll

You can review the complete code to test out the functionality here: https://github.com/oleks-fedotov/animation-queue

Conclusion

Although TV hardware is continually being upgraded, loading intensive animations is still a complex problem to tackle. Although several third-party libraries tend to resolve the problem, they bring their own inconsistencies and optimization issues with them.

One other way would also be to sub-nest multiple callbacks to render our animations sequentially. However, this makes the code unreadable, makes it monolithic, and scalability is a big concern. Utilizing simple-to-use data structures easily resolved the problem at hand.

+++ This article was already published by the author on the former Joyn Tech Blog and has now been moved to the P7S1 Tech Blog in May 2021 and has been integrated here. From now on you will find all the content regarding Joyn on the P7S1 Tech Blog. +++

--

--