/// /// Copyright 2015-2026 Micro Focus or one of its affiliates. /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// import { Injectable } from '@angular/core'; import { concat, fromEvent, Observable, Observer } from 'rxjs'; @Injectable({ providedIn: 'root', }) export class FrameExtractionService { private createVideoPlayer(source: string): HTMLVideoElement { const videoPlayer = document.createElement('video'); videoPlayer.preload = 'auto'; videoPlayer.src = source; return videoPlayer; } private createCanvas(width: number, height: number): HTMLCanvasElement { const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; return canvas; } private goToFrame(videoPlayer: HTMLVideoElement, time: number): Observable { videoPlayer.currentTime = time; return fromEvent(videoPlayer, time === 0 ? 'loadeddata' : 'seeked'); } private getThumbnail( videoPlayer: HTMLVideoElement, canvas: HTMLCanvasElement, time: number, width: number = 160, height: number = 90 ): Observable { return Observable.create((observer: Observer) => { // go to specified frame const subscription = this.goToFrame(videoPlayer, time).subscribe(() => { // create image from current frame canvas.getContext('2d').drawImage(videoPlayer, 0, 0, width, height); observer.next({ image: canvas.toDataURL(), width, height, time }); observer.complete(); subscription.unsubscribe(); }); }); } getFrameThumbnail( source: string, width: number, height: number, time: number ): Observable { // create required elements let videoPlayer = this.createVideoPlayer(source); let canvas = this.createCanvas(width, height); const frameSubscription = this.getThumbnail(videoPlayer, canvas, time, width, height); // ensure we release memory after we are finished frameSubscription.subscribe(null, null, () => { videoPlayer = null; canvas = null; }); return frameSubscription; } getFrameThumbnails( source: string, width: number, height: number, start: number, end: number, skip: number = 5 ): Observable { // create required elements let videoPlayer = this.createVideoPlayer(source); let canvas = this.createCanvas(width, height); return Observable.create((observer: Observer) => { fromEvent(videoPlayer, 'loadedmetadata').subscribe(() => { // calculate the frames required const frames = []; for (let idx = start; idx < end; idx += skip) { frames.push(this.getThumbnail(videoPlayer, canvas, idx, width, height)); } concat(...frames).subscribe( (frame: ExtractedFrame) => observer.next(frame), null, () => { videoPlayer = null; canvas = null; observer.complete(); } ); }); }); } } export interface ExtractedFrame { image: string; width: number; height: number; time: number; }