Let me explain functionality a bit. My goal is when the video is playing if a user clicks on the video it will be paused then he might do something along the way. In the meantime I need to detect if a user is idle for 30 sec. If so then automatically resume the video again.
I have implemented using the javascript basic setTimeout
functionality. Let me share the code below
@HostListener('window:click', ['$event'])
onClick: (event) => {
// stop the video
this.activity = setTimeout(() => { /* resume */ } , 30000)
}
@HostListener('window:mousemove', ['$event'])
onMouseMove: (event) => {
clearTimeout(this.activity)
this.activity = setTimeout(() => { /* resume */ } , 30000)
}
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
Any suggestion how this can be solved using the RxJs
way?
CodePudding user response:
activity = new Subject<void>();
@HostListener('window:click', ['$event'])
onClick(event: any) {
// stop the video
interval(30000).pipe(takeUntil(this.activity)).subscribe(() => {
/* resume */
this.activity.next();
this.activity.complete();
});
}
@HostListener('window:mousemove', ['$event'])
onMouseMove(event: any) {
this.activity.next();
this.activity.complete();
this.activity = new Subject<void>();
interval(30000).pipe(takeUntil(this.activity)).subscribe(() => {
/* resume */
this.activity.next();
this.activity.complete();
});
}
CodePudding user response:
This is what I came up with:
It runs outside the Angular Zone. Won't burden your app with change detection when the mouse is moving. It triggers change detection only when the video is stopped or continued.
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable, NgZone } from '@angular/core';
import {
BehaviorSubject,
fromEvent,
merge,
Observable,
of,
Subject,
} from 'rxjs';
import {
distinctUntilChanged,
mapTo,
startWith,
switchMapTo,
debounceTime,
takeUntil,
} from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class VideoService {
private readonly onDestroy = new Subject<void>();
readonly isPlaying$: Observable<boolean> = new BehaviorSubject(true);
constructor(
@Inject(DOCUMENT) private readonly document: Document,
private readonly ngZone: NgZone
) {
// Run the whole logic outside of angular, so it won't burdent the app
this.ngZone.runOutsideAngular(() => {
fromEvent(this.document, 'click', { passive: true })
.pipe(
// On every click we create a merge observable, and unsubscribe from the previous one if exists
switchMapTo(
// We merge two observables
merge(
// one that sets the playing to false instantly
of(false),
// Subscribe to the documents mousemove
fromEvent(this.document, 'mousemove', { passive: true }).pipe(
// we start the stream with a value, so after the clicking it will go back to playing without moving the cursor.
startWith(null),
// With debounceTime we wait for no mousemove
debounceTime(1000),
// All values are mapped to true
mapTo(true)
)
)
),
distinctUntilChanged(),
takeUntil(this.onDestroy)
)
.subscribe((value) =>
// when we get a different value run it in the zone, so Angular will know to run change detection.
this.ngZone.run(() => {
(this.isPlaying$ as BehaviorSubject<boolean>).next(value);
})
);
});
}
ngOnDestroy() {
this.onDestroy.next();
this.onDestroy.complete();
}
}
I'm interested in other solutions, as mine does not seems to be simple. I feel it can be done simpler too.