Home > Net >  Resume video if user is inactive/idle for 30 seconds (RxJs)
Resume video if user is inactive/idle for 30 seconds (RxJs)

Time:11-18

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();
  }
}

Running example

I'm interested in other solutions, as mine does not seems to be simple. I feel it can be done simpler too.

  • Related