Pinterest: Turbocharge Android Video with These Simple Steps
Social media giant Pinterest improved Android video performance by optimizing warm-ups, configurations, and player pooling with a few simple steps, according to a recent blog post from company engineers.
Engineers of the site consider their work a “never-ending” investigation into the inner workings of ExoPlayer, their preferred application-level media player for Android.
Although geared to Android, the blog post — written by Grey Skold, former Pinterest Android video engineer, Lin Wang, Android performance engineer, and Sheng Liu, Android performance engineer — featured underlying concepts that are applicable in other areas.
Let’s take a look:
The key learning here was to establish the network connection right away. Rather than waiting to return video URLs from the server, they establish a network connection during the application start-up time via a dummy HTTP HEAD request. This same connection is used to play future videos.
This same strategy is applied to UI rendering.
A similar, pre-establishing strategy is employed for UI rendering. ExoPlayer’s default is to parse the video URL to:
- calculate the aspect ratio
- invoke on onContentAspectRarioChanged()
- inform the AspectRatioFrameLayout of the aspect ratio.
Since most of the video’s aspect ratios are pre-determined, the following functions can prevent that work from happening:
- setting the video aspect ratio via AspectRatioFrameLayout.setAspectRatio()
- Override the PlayerView.onContentAspectRatioChanged() method with an empty body (this prevents the player from trying to recalculate the aspect ratio).
Short Term Buffering
Buffering delays playback until enough data is buffered. Since most of Pinterest’s video is short form, the engineers opted for shorter buffering durations. The result is less time to wait and faster time to load. setBufferDurationMs() is the ExoPlayer function for setting buffering durations. The rebuffer rate increased but the overall video UX improved.
Limiting Size and Sound
The two parameters below load videos in-feed and limit the size to the correct viewport size. This eliminates any 1080p video sizes in 360-pixel viewports.
This set of parameters disables audio rendering, allowing Pinterest to play multiple muted videos at the same time. This saves network bandwidth from the download and memory consumption from processing.
Cleaning a Dirty Cache
ExoPlayer provides a cache interface that kept downloaded media files on disk. The catch though is that when Pinterest ran into fatal errors caused by backend bugs, the bad contents also stuck in the cache causing errors long after the bugs were fixed.
The solution is to use SimpleCache.removeResource() to purge the dirty cache when the following fatal IO errors are returned from the Player.Listener.onPlayerError():
- ERROR_CODE_IO_UNSPECIFIED 2000
- ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE 2003
- ERROR_CODE_IO_BAD_HTTP_STATUS 2004
- ERROR_CODE_IO_FILE_NOT_FOUND 2005
- ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE 2008
Pinterest builds the cache to pool players as needed. Historically it instantiated new player instances on the fly but that caused significant memory and bandwidth overhead. Their high-level learnings are below.
Separate by Encoding
It takes an incredible amount of work to switch contexts between different decoders. Player instances hold on to an underlying decoder reference based on the last rendered media’s encoding type. Pinterest moved toward basing player pooling on the initial decoding format. This removed latency caused by encoder switching enduring players were recycled with encoders matching the media’s encoding.
It was also a challenge to find the ideal pool size — it took multiple iterations to find the ideal space needed to house multiple video plays and avoided OutOfMemory (OOM) and Application Not Responding (ANR) errors. They employed the following two APIs to aid in this.
This callback informed Pinterest when it was time to clear out their player cache pool when they got close to sending OOMs.
This flag let ExoPlayer retain a direct reference to the video decoder and keep it in memory, even in the idle state. This does take a significant toll on memory and device stability. To conserve both, Pinterest built conservative logic around the method based on the current application lifecycle and available memory on the device.