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.



src?: string;

id: string;

}



export class Video implements VideoInterface {



public src: string;

public id: string;



constructor(id: string) {

this.id = id;

this.src = '

}

} 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? 😄