Hi,
I’m writing an multimédia ebook reader on Ionic 5 / Angular 8. I have this simple video component with custom controls :
<div class="video-player">
<video #video [src]="content.src" controls controlslist="nodownload" disablePictureInPicture playsinline preload="none">
</video>
<div class="video-actions">
<div class="video-actions-left">
<ion-icon name="logo-closed-captioning" (click)="captions()"></ion-icon>
</div>
<div class="video-actions-center">
<ion-icon [name]="playing ? 'pause':'play'" (click)="play()"></ion-icon>
</div>
<div class="video-actions-right">
<ion-icon name="expand" (click)="fullscreen()"></ion-icon>
</div>
</div>
</div>
import {Component, ElementRef, Input, OnInit, ViewChild} from '@angular/core';
import {Video} from '../../../classes/contents/video';
@Component({
selector: 'video-player',
templateUrl: './video.component.html',
styleUrls: ['./video.component.scss'],
})
export class VideoComponent implements OnInit {
@ViewChild('video', {static: true}) videoRef: ElementRef;
@Input() content: Video;
video: HTMLVideoElement;
videoSrc: string;
playing = false;
constructor(
public toastController: ToastController
) {}
ngOnInit() {
this.video = this.videoRef.nativeElement;
this.video.addEventListener('play', (event) => {
this.playing = true;
});
this.video.addEventListener('pause', (event) => {
this.playing = false;
});
}
play() {
if (this.video.paused) {
this.video.play();
} else {
this.video.pause();
}
}
}
This takes forever to load and the app kind of freezes for a moment (~10s) :
If i remove the [src]
attribute it’s much quicker (~1s) :
Any ideas why and how to fix ?
The videos (4 of them) are locally stored (/assets
) and I set preload=none
to prevent fetching data for each videos.
You could use something like intersection observer to watch for when the element enters the view port and then load the video?
I have this snippet right here which does that, but for images. You’d need to adapt it a bit for videos.
import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
ElementRef,
HostBinding,
Input,
ViewChild,
ɵmarkDirty as markDirty,
} from '@angular/core';
@Component({
selector: 'lazy-img',
templateUrl: './lazy-img.component.html',
styleUrls: ['./lazy-img.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LazyImgComponent implements AfterViewInit {
observer: IntersectionObserver;
@Input() src = '';
@Input() alt = '';
@HostBinding('class.loaded') isLoaded = false;
@ViewChild('lazyImage', { static: true }) lazyImage: ElementRef<
HTMLImageElement
>;
constructor() {}
async ngAfterViewInit() {
const options: IntersectionObserverInit = {
root: this.lazyImage.nativeElement.closest('ion-content'),
};
if ('IntersectionObserver' in window) {
this.observer = new IntersectionObserver(
await this.onObserve.bind(this),
options
);
this.observer.observe(this.lazyImage.nativeElement);
} else {
setTimeout(() => this.preload(this.lazyImage.nativeElement), 200);
}
}
async onObserve(
data: IntersectionObserverEntry[]
): Promise<IntersectionObserverCallback> {
if (data[0].isIntersecting) {
await this.preload(data[0].target as HTMLImageElement);
this.observer.disconnect();
return;
}
}
applyImage(target: HTMLImageElement, src: string) {
return new Promise((resolve) => {
target.src = src;
target.crossOrigin = 'anonymous';
resolve();
});
}
fetchImage(url: string) {
return new Promise((resolve, reject) => {
const image = new Image();
image.src = url;
image.crossOrigin = 'anonymous';
image.onload = resolve;
image.onerror = reject;
});
}
async preload(targetEl: HTMLImageElement) {
// prefect the image and prime it
await this.fetchImage(this.src);
// ok, actually apply the image to the real img tag
await this.applyImage(targetEl, this.src);
this.isLoaded = true
markDirty(this)
}
}
2 Likes
In the end, I went with an easier solution. I dynamically load the video on play.
play() {
if (!this.video.src) {
this.video.src = this.content.source;
this.video.load();
this.video.play();
} else {
if (this.video.paused) {
this.video.play();
} else {
this.video.pause();
}
}
}