Search code examples
hoverhtml5-videoaureliajquery-hover

Hovering and playing video


I'm trying to make a gallery of videos that play on hover...and only when hovered. But for some reason, sometimes the videos continue to play even when the mouse exits them.

I'm using aurelia's mouseover.delegate="hoverVideo($index)"and mouseout.delegate="hideVideo($index)" to initiate the playback.

  hoverVideo = index => {
    let id = "#video" + "-" + index.toString();

    let isPlaying =
      $(id).get(0).currentTime > 0 &&
      !$(id).get(0).paused &&
      !$(id).get(0).ended &&
      $(id).get(0).readyState > 2;

    if (!isPlaying) {
      $(id)
        .get(0)
        .play();
    }
  };
  hideVideo = index => {
    let id = "#video" + "-" + index.toString();
    let isPlaying =
    $(id).get(0).currentTime > 0 &&
    !$(id).get(0).paused &&
    !$(id).get(0).ended &&
    $(id).get(0).readyState > 2;

    if (isPlaying) {
    $(id)
      .get(0)
      .pause();
    }
  };

How do I get the videos to ALWAYS stop playing on mouse exit?


Solution

  • Try using mouseenter.trigger and mouseleave.trigger. Trigger event listeners are attached directly to the html element (as opposed to on the body tag like delegates are) and can be a little more responsive depending on how big your pages age.

    Additionally, you could try wrapping (some of) your event handler logic in a TaskQueue.queueMicroTask call inside the event callbacks. This executes your code the next requestAnimationFrame and ensures the element is ready to respond to your changes.

    And perhaps store the element in a variable so you don't need to query the DOM more than once.

    Example:

    hideVideo = index => {
      const id = "#video" + "-" + index.toString();
      const video = $(id).get(0);
      const isPlaying = video.currentTime > 0
        && !video.paused 
        && !video.ended
        && video.readyState > 2;
    
      if (isPlaying) {
        // if the element is still somehow busy during mouseleave
        this.taskQueue.queueMicroTask(() => {
          video.pause();
        });
      }
    }
    

    Or:

    hideVideo = index => {
      const id = "#video" + "-" + index.toString();
      const video = $(id).get(0);
    
      // if the state isn't always up-to-date in time
      this.taskQueue.queueMicroTask(() => {
        const isPlaying = video.currentTime > 0
          && !video.paused 
          && !video.ended
          && video.readyState > 2;
    
        if (isPlaying) {
          video.pause();
        }
      });
    }
    

    If that still doesn't work, maybe the state of the video is not what you're expecting it to be. An operation like pause() should be idempotent, so you could try just skipping the isPlaying check altogether.