Flowplayer and single-page applications
Abstract
When designing Flowplayer Native, the rise of the SPAs (Single Page Application) had already occurred. There are hundreds of various front-end frameworks these days, but React is probably the leader for now.
SPAs have evolved drastically since their inception, originally designed around the idea of making the Web faster by needing less data, like an HTTP
PATCH
operation, they now use a complex set of algorithms designed for the purpose of rendering a web page very quickly without needing to reload it.
Over the course of a lifecycle with a modern SPA the DOM can change drastically and quickly. It's possible for tens and maybe even hundreds of players to be created and destroyed, which means special care must be given to handling both creating and destroying an individual player.
Creating a player must be cheap
Creating a player must be as cheap as possible. This is accomplished through lazy instantiation as many complex objects, such as the hls.js
and dash.js
instances. We defer creation of these complex instances until it's likely the user wants to interact with the video. By defering this we can be sure that rendering results in snappy UI transitions and we can render player instances as quickly as possible without performance drawbacks.
Lazy evaluation was not without some sticking points, especially with video where there are often several logical branches that can lead to needing switch between eager and lazy. A prime example of this is autoplay
where we must eagerly create these objects to be sure that we minimize our time to first video frame.
Destroying a player must be safe
Internally we aggressively cache DOM lookups and store references to the resulting HTMLElement
s because of how video works, there are a lot of events that can happen quite quickly. Reducing the load on the event loop helps to maintain a high UX for older and mobile devices.
Most JavaScript engines use a technique known as reference counting to determine if an Object should be garbage collected. This can lead to a problem of Objects that are no longer being used not able to be garbage collected due to the garbage collector not being able to tell if a reference is valid or not. Primitive types, like Number
, String
, and Boolean
are always considered leaf-nodes by the garbage collector.
This is very problematic in SPA's as each create/destroy cycle will result in more and more of these Objects to build up over time, this is sometimes known as a zombie reference, because it's not alive but it also cannot be killed (released) by the internal garbage collection engine.
This was initially a tricky problem, because we wanted a way to easily ensure that when destroy()
was called on a player, all of these unsafe cache references were destroyed. Using the properties of primitive types we were able to build an Array<string>
where each element is an Object path.
Internally this looks something like:
const reaper = video.reaper = new Reaper()
reaper.put("some_dom_element_name",
player.find(".an-unsafe-reference"))
// video.some_dom_element == player.find(".an-unsafe-reference")
// added an unsafe reference for caching
reaper.reap()
// video.some_dom_element == 0
// it's been pruned
How the pieces fit together
All decent frameworks provide a way to have lifecycle hooks around DOM manipulation, for instance in React there is the componentWillUnmount()
hook precisely for this problem. Our internal implementation details for each player do not matter at this point, all a developer needs to do is use the player.destroy()
method. This ensures your SPA can live a full and happy life without bursting at the memory seams, and making it easy to not have to think about all of these problems we have already solved for you.