Fast, Inline HTML5 Video In Ionic (3+)

One of my favorite pages in the 38Plank app is what I call the “Workout Completion” page. When a user wants to complete a workout they navigate here to watch videos of Katie Ring demonstrating the exercise, alongside their own status, and how many reps it would take to reach the beginner, intermediate, and advanced levels.

38Plank Full Body Workout Challenge, Day 1

To view this example live, checkout Day 1 of our 30-day Full Body Challenge.

In order to have this page work well with dynamically loaded content (the videos are stored in S3), there were a couple of major hurdles that we needed to overcome.

  1. Inline HTML5 video with auto-play
  2. Fast content loading, smooth transitions between videos
  3. Videos should not override any music that is playing

All three of these goals would have been challenging around a year and a half ago with a hybrid web application, but we were able to leverage new features in Safari and Chrome to accomplish this task.

iOS 10.3 and Inline HTML5 Video

I have previously mentioned that this kind of page would not have been possible a year ago, and this is why.

“Starting in iOS 10, WebKit relaxes its inline and autoplay policies to make these presentations possible” — Webkit Blog circa late 2016

Before iOS 10, inline video was very difficult, and auto-play with silent video playback were not allowed. There were good reasons for this at first, but with the efficiencies of HTML5 video accompanied by phones getting more powerful meant arguments for disabling these features were getting weaker and weaker. This allowed us to write a video element like this

// Inline, autobuffered, muted & looped!
<video muted="true" playsinline looped preload="auto"></video>

This small change in iOS 10 allowed for a whole new type of hybrid web application.

Android / Chrome also adopted this feature as of June 2016, so both platforms got onboard at the same time. A weird series of events where Android and iOS play nicely with each other, mainly driven by web browser competition between Safari and Chrome.

Fast Content Loading

Just because we can play inline auto-played videos does not mean that we should. If we can’t efficiently load videos faster than the user requests them, we will be stuck with a buffered video that never plays, ruining the user experience. With the 38Plank app, our videos are not stored in-app, but remotely on S3 so we had to be conscious about load times over various bandwidth scenarios.

There were three parts of this problem that we tackled to define the problem. Each of our videos are around 10–15s long, and they needed to be downloaded completely before the “rest” section was over, which was a low as 5s. So we needed to make sure each video fully loaded in 5s and was ready to play.

To improve the download speed of S3, we enlisted Cloud Front. This is AWS’s CDN that will host content previously downloaded on edge networks for increased speed and decreased latency.

To deliver content to end users with lower latency, Amazon CloudFront uses a global network of 113 Points of Presence (102 Edge Locations and 11 Regional Edge Caches) in 56 cities across 24 countries. — AWS

Instead of everyone around the world having to query and download from our services in the US, they cache content locally for easy access.

But even with a global CDN like Cloud Front, we still needed to improve the user experience. So the second step of the process was to be able to buffer content in the background of videos that were about to come up. To do that we keep two videos in the background buffering, the previous and the next workout video, so that when the user clicks Next>, the video has already been loaded.

// Previous Video That A User Might Go Back To
<video [hidden]="!isCurrent" [autoplay]="isCurrent" muted="true" playsinline looped preload="auto"></video>
// Current Video That Should Autoplay
<video [hidden]="!isCurrent" [autoplay]="isCurrent" muted="true" playsinline looped preload="auto"></video>
// Next Video, Preloaded for content
<video [hidden]="!isCurrent" [autoplay]="isCurrent" muted="true" playsinline looped preload="auto"></video>

There is a catch here. You cannot use *ngIf or another type of destroy/hide trick here. If you destroy and recreate the video element, it will destroy the buffer and you will loose all of your advantage. To get around this, we have the videos on a circular loop by using angular transitions to make it look like one continuous line of videos.

Angular Animations

With the release of angular 4.0, came the first release and inclusion of angular animations. I will not go into great detail here about what that means, but here is a great starter blog post from Josh Morony on the subject. Angular animations can get pretty complicated pretty quickly so it might take some practice to understand exactly what is going on.

To create the effect of a never ending slide deck, we need to make sure all “next” videos come in from the right, and all “previous” videos slide in from the left. We also need to make sure to not destroy any of our video elements on the way, so transitioning a slide from being the “previous” video, to being the “next” video will have to be done in an invisible manner.

So whenever the user triggers our app, we initiate a Shift animation, described below.

// workout-completion.page.ts
animations: [
trigger('shift', [
state('previous', style({
opacity: 0,
transform: 'translateX(-100%)',
'-webkit-transform' :'-webkit-translateX(-100%)'
})),
state('current', style({
opacity: 1,
transform: 'translateX(0%)',
'-webkit-transform' :'-webkit-translateX(0%)'
})),
state('next', style({
opacity: 0,
transform: 'translateX(100%)',
'-webkit-transform' :'-webkit-translateX(100%)'
})),
transition('* => *', animate('.5s'))
])
]

This moves the videos into the correct position, and with the correct opacity on our page. Then we initialize our three video elements with the correct states, i.e. previous, current, and next and the correct urls and poster attributes.

// workout-completion.page.ts
public vidState1 = 'previous';
public vidState2 = 'current';
public vidState3 = 'next';
@ViewChild('video1') video1;
@ViewChild('video2') video2;
@ViewChild('video3') video3;
// Setup Video Buffer Array, which will contain the urls that we wish to load into each video element
public videoBuffer: Array<any> = [this.emptyVideo, this.emptyVideo, this.emptyVideo];
public emptyVideo = {
url: '',
thumbnail: {
url: 'default_url'
}
};
// workout-completion.page.html
<video #video1 muted="true" playsinline
[attr.loop]="videoBuffer[0].looped"
[@shift]="vidState1" preload="auto"
[src]="videoBuffer[0].url" [poster]="videoBuffer[0].thumbnail?.url"></video>
<video #video2 muted="true" playsinline
[attr.loop]="videoBuffer[1].looped"
preload="auto"
[@shift]="vidState2" [poster]="videoBuffer[1].thumbnail.url"
[src]="videoBuffer[1].url"></video>
<video #video3 muted="true" playsinline
[attr.loop]="videoBuffer[2].looped"
preload="auto"
[poster]="videoBuffer[2].thumbnail.url"
[@shift]="vidState3" [src]="videoBuffer[2].url"></video>

Videos That Do Not Override Music Playing

One of our most common complaints about an earlier version of this page was that as soon as you started a workout, Spotify would cut out. I know that most people like to jam out to Taylor Swift while they are doing ab exercises and 38Plank should not stand in the way.

In the browser specs (both chrome and safari), the “muted” attribute is supposed to auto mute the video, but in my experience with the video setup the way we have it adding this will not always work. Even if it does mute the video, it will sometimes override music playing in the background.

In the end, we needed to remove the audio track all together from the file. There were a couple of options here to make this happen. In most videos, audio and video are separate channels, you can use audacity (free), or Adobe Premier if you have the money and the skills. We had our videographer (shout out to Derek) do the work for us, but it should be manageable on your own.

HTML5 Video Content

Having inline and auto played HTML5 video is great progress on the part of the browsers. These features will enable developers of PWA’s and hybrid web applications to compete with native applications by delivering quality content in much more user friendly manner.

If you are hoping to see this in action, check out our 30 day Full Body Challenge. There is a great set of workouts that will help start you on your journey to get in better shape, and with great content attached to it!

To view the original post, visit PWA Today

Written by

Senior Engineer @Forge. Creating liquidity in the private market. Former Co-Founder of 38Plank

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store