Ionic 4: Autoplay videos on scroll

Often, achieving Instagram or Facebook type experience is more difficult than it sounds in Ionic. To bridge that gap, I’m going to show you how to automatically play and pause videos in your app as your user scrolls.

Jun 14 · 9 min read
Photo by Hermes Rivera on Unsplash

This article will be a bit longer, despite the topic being pretty simple. The reason being, is I like to explain WHY we’re doing things, instead of just having you blindly copy and paste, without understanding what’s actually going on in your application.


Let’s get started

First and foremost, we need to edit our config.xml file to include the following preference.

<preference name=”AllowInlineMediaPlayback” value=”true” />

This tells our app that we want to be able to choose which videos are played inline, without opening the native video player — which can be annoying in an app with a lot of user-generated videos.

Edit Config.xml

Your config.xml file should end up looking something like this:

...
<preference name="ScrollEnabled" value="false" />
<preference name="android-minSdkVersion" value="19" />
<preference name="BackupWebStorage" value="none" />
<preference name="SplashMaintainAspectRatio" value="true" />
<preference name="FadeSplashScreenDuration" value="300" />
<preference name="SplashShowOnlyFirstTime" value="false" />
<preference name="SplashScreen" value="screen" />
<preference name="SplashScreenDelay" value="3000" />
<preference name=”AllowInlineMediaPlayback” value=”true” />
...

Your values might be a little different, but that’s ok. We just needed to add the bolded text to our preferences section.

Unfortunately, that’s not all we need to do, because if you reload, you’ll notice that nothing happened. The videos are just stuck on the first frame and aren’t playing when they scroll into view 🙁.

Package — ng-in-viewport to the rescue!

ng-in-viewport is a nifty little npm package designed to handle all of the setup required to determine if an element (our video) is currently in the viewport or not via the intersection-observer api.

While we could do this setup on our own, i’m of the mentality that we shouldn’t be re-inventing the wheel when there is a vetted & tested package that can do what we want to accomplish; and do it better.

You’ll need to install the package via npm install ng-in-viewport.

Let’s generate some code!

Another lesson I try to always follow (though I’m not perfect) is generating components and classes when something is going to be used in multiple places in my app. It simplifies logic, cuts down on file sizes and allows us to quickly and easily modify our app.

Use the command ionic g component components/inline-video to generate a new component called inline-video, which we’ll use to quickly and easily display inline-videos in our Ionic application.

Next, we’re going to use the command ionic g class classes/video to create a new class called video. This is going to help us bundle all of our video metadata information into one central location, that we can quickly and easily use later on.

Imports — App.module.ts

We need to make sure that the ng-in-viewport package is properly imported into our app.module.ts file. You’ll notice that our inline-video component has already been imported via the CLI, which is nice quality of life feature to have in V4.

import { InViewportDirective } from 'ng-in-viewport';
...
@NgModule({
declarations: [
InViewportDirective,
...
],
...
exports: [
...
InViewportDirective,
],

Lets get down to business…to defeat…the videos!

Who doesn’t love a good Mulan reference? Okay. Back to coding…

Class — video.ts

This is what our video class is going to look like. In this simple example, we are exposing two properties:

src — which is our video file source

id — which is a unique id for our video. I’m generating this via Firebase and passing it through the constructor, but you can use another method. I’ll explain why this is a good idea to do, in a little bit.

export interface VideoInterface {
src?: string;
id: string;
}

export class Video implements VideoInterface {

public src: string;
public id: string;

constructor(id: string) {
this.id = id;
this.src = 'https://ia800501.us.archive.org/10/items/BigBuckBunny_310/big_buck_bunny_640_512kb.mp4';
}
}

I’ve included a free to use video for testing purposes as the video source. This means that all of our videos will be playing the same thing. This is good enough for testing purposes.

Component — inline-video.component.ts

We’re going to import some information from our new package & class into the inline-video.component.ts file, same as we would anything else.

import { InViewportMetadata } from 'ng-in-viewport';
import { Video } from '../../../classes/video';

Even though the package is technically a directive, we’ll need the InViewportMetadata information to properly interact with our videos.

We’ll also want to create a video object to use in the component, using our new video class we implemented earlier.

public video: Video = new Video('someUniqueId');

You’ll also notice that i’m passing a unique ID to the class, the I mentioned earlier. The reason i’m doing this, is because Ionic sometimes gets confused and will mute/unmute the audio on ALL videos, so we use this to specify which video we want to be interacting with.

Next, we’re going to create two functions. One will take care of changing our video audio — which will be muted by default, and the second will handle playing & pausing the video as we scroll.

Function — changeVideoAudio(id: string)

public changeVideoAudio(id: string) {
let vid: any = document.getElementById('media-' + id);
vid.muted = !vid.muted;
}

This function is aptly named changeVideoAudio, because that’s all it does. When a video starts playing in our app, by default, it’ll be muted. This is just standard good UX and if you don’t do this by default, your user-base will probably hate you.

So we grab the video element via document.getElementById('media-' + id); with the video’s unique ID. media- is just an additional unique identifier, incase we use the unique id elsewhere.

Lastly, we’re going to access the video element (which we named vid) and change the muted property equal to the opposite of itself. This makes it easy for us to mute and unmute the video without having to worry about the state of the audio.

Function — onIntersection($event)

The onIntersection function will be what is going to determine whether the video should be paused or playing as the user scrolls. It’s pretty straight forward, but i’ll explain it a bit more in depth.

onIntersection($event) {
const { [InViewportMetadata]: { entry }, target } = $event;
const ratio = entry.intersectionRatio;
const vid = target;

ratio >= 0.65 ? vid.play() : vid.pause();
}

We start out by declaring an odd looking set of variables

const { [InViewportMetadata]: { entry }, target } = $event;

This will allow us to properly access the ratio of the video in the view, as well as the target video object itself. You can read more about why this declaration works, but I won’t be covering that in this article. For simplicities sake, just trust me that it works.

We next have two constants, these aren’t required, but I like to name my variables as something that I can quickly understand, and manipulate. Hence:

const ratio = entry.intersectionRatio;
const vid = target;

The constant called ratio is how much the video is in our view. We’ll configure this setting a bit more in our html file, but it’ll be a value between 0 and 1 at any given time. 0 meaning that the video is not in view at all, and 1 meaning that the video is 100% in the view.

const vid = target is just me renaming the video object from target to vid for clarity’s sake.

ratio >= 0.65 ? vid.play() : vid.pause();

Lastly, I’m just using a ternary operator to check if the video is greater than or equal to 65% in the view. You can play around with this number, but this is what I’ve found to be the best ratio to start & stop videos at from a User Experience perspective. If the ratio is >= 65%, then we play the video. If it’s less than 65%, we pause the video.

Component — inline-video.component.html

Your html file is going to be a little bit complicated, so bear with me.

To start, we’ll add a simple parent div element, which will allow us to change the audio of our video. The reason we’re doing this on a div, and not the actual video, is because Ionic is weird, and doesn’t like us doing it on the video element itself.

“I don’t write the framework bugs, I just find work arounds for them…”
— Jordan Benge circa 2019

<div tappable (tap)="changeVideoAudio(video?.id)">
...
</div>

This is pretty self explanatory, we’re just passing the video id to our changeVideoAudio function we created earlier.

Next, let’s add in the video element. I’ll walk through each line, and explain what’s going on, like usual.

<div tappable (tap)="changeVideoAudio(video?.id)">
<video
inViewport
[inViewportOptions]="{ threshold: [0, 0.65], partial: true }"
(inViewportAction)="onIntersection($event)"
playsinline loop
[muted]="'muted'" preload="auto" muted="muted"
[poster]="video?.src"
[id]="'media-' + video?.id" class="video-media">
<source [src]="video.src" type="video/mp4" src="">
</video>
</div>

inViewport lets Ionic & Angular know that we’re adding in the inViewport directive to our video element. This is what will allow us to know if the video is in the viewport or not. 🙌 pretty neat huh?

[inViewportOptions]="{ threshold: [0, 0.65], partial: true }"

This line is required configuration for our inViewport direction. I have the threshold set to be between 0 and 0.65 or 65%. This is because I want to know when the element enters the viewport as well as when it leaves. You can add additional values if you’d like, i’ve linked the threshold api documentation above, but this is the best setup in my opinion.

partial: true just means that we want to know when the element partially enters the viewport. Otherwise, the element would only fire when it was 100% in the viewport, instead of 65% like we’ve configured.

(inViewportAction)="onIntersection($event)"

This is the event that fires and calls our onIntersection() function when an element position changes in the viewport, relative to the configuration we setup above.

playsinline — tells our app that this video should be played inline — instead of using the native video player from our device. Without this, the video would not automatically play on scroll.

loop — just loops our video indefinitely, which is required if we want the video to repeat itself after it’s finished, without any additional user interaction.

[muted]="'muted'" preload="auto" muted="muted"

preload — just tells the browser that we want the video to start loading & buffering when the page loads. It’s not required, but I recommend it for a faster and better UX.

[muted]/muted — the reason i’ve included both muted attributes is, because there is often issues with the different phone platforms where [muted] works on one but the other. So I just include them both, for simplicities sake.

[id]="'media-' + video?.id" class="video-media">

id — pretty typical Angular HTML stuff going on here, i’m setting the element id to be media- + video?.id, so we can grab the correct video when we’re changing the audio from muted to unmuted & vise-versa by tapping it. The class is just so I can style the video however I like.

<source [src]="video.src" type="video/mp4">

This is the video source element, and it’s pretty self explanatory. The type is important, because you could serve up different videos based on the user’s device. I just default to using video/mp4 because it’s universally supported, but you might also want to use others such as video/ogg or video/webm depending on your target audience.

example demo

That’s it!

If you restart your application, you should be able to see your videos auto playing as they scroll into view. If you tap it, it’ll change the audio so it’s no longer muter. Tap it again, and it’ll re-mute it! Continue scrolling past the video, and it should automatically pause it once it’s past the 65% threshold.

Pretty neat stuff, huh? 😄


Questions?

You can find me on:
- GitHub: https://github.com/bengejd/
- Medium: https://medium.com/@JordanBenge
- Twitter: https://twitter.com/J_Benge13

Who am I? My name is Jordan Benge, I am a Software Developer who loves helping others and contributing to Open-Source. I’ve been working in the Ionic Framework since Ionic 1, and have tried to keep up to date on the latest and greatest when it comes to Hybrid Mobile App Development.

If you enjoyed this story, please click the 👏 button and share to help others find it! Feel free to leave a comment below if you need any help.

Jordan Benge

Written by

My name is Jordan Benge, I’m a freelance developer, and sometimes like to write helpful articles on Medium for the dazed and confused developers — like myself.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade