sampotts / plyr

A simple HTML5, YouTube and Vimeo player
https://plyr.io
MIT License
26.37k stars 2.92k forks source link

Auto-pause when out of view, or when another video plays #1864

Open AndersDK12 opened 4 years ago

AndersDK12 commented 4 years ago

I am not sure if this feature already exists, but I have Googled for a few hours, and looked through the issues and pull requests here, to no luck. First of all, (well 2nd..) thanks for everyone who contributed to making this player, it's good!

This is the code I have so far: (Not sure why some of the code has not been marked..?) ` function initPlyr(){ const video = document.querySelectorAll('.plyr-video[data-loaded="0"]'); for (var i = 0; i < video.length; i++) {

            const source    = video[i].getAttribute("data-src");
            const isEmbed   = video[i].getAttribute("data-embed");
            const player    = new Plyr(video[i], {
                muted: true,
                autopause: true,
                controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'airplay', 'fullscreen'],
                settings: ['captions', 'quality', 'speed', 'loop']
            });
            // Only use HLS on content from CDN, not embedded videos (YT).
            if(!isEmbed){
                if (!Hls.isSupported()) {
                    video[i].src = source[i];
                } else {
                    const hls = new Hls();
                    hls.loadSource(source);
                    hls.attachMedia(video[i]);
                    window.hls = hls;
                }
            }

            video[i].setAttribute("data-loaded", "1");
            window.player = player;
        }
    }

    // Multiple instances
    document.addEventListener('DOMContentLoaded', () => {
        initPlyr();
    });`

This code works fine for multiple video instances, using HLS.js (m3u8) with my own minor tweaks. This will also work when loading dynamic content through AJAX, and then just call the initPlyr() function again, to initialize new dynamic elements (if anyone else can use this).

The only performance issue is that videos continues to play, when a) they are outside of view, or b) the user clicks on another video.

As far as I could tell there is not currently such an option to set, or am I mistaken?

Thanks again. :)

aubreyz commented 4 years ago

Not sure about "out of view" - think that will be pretty hard...

but try adding a bit of script like this to turn off all video players when another one starts

$(function(){
    $("video").on("play", function() {
        $("video").not(this).each(function(index, video) {
            video.pause();
       });
    });
});
AndersDK12 commented 4 years ago

Seems like something like this would do it (pause when out of view). I haven't tested it yet, but I assume it would work for <video> elements. However, what about embedded videos (YouTube/Vimeo)? And how would it access the specific Plyr control/element with pause(). Will look into this tomorrow, and do some more testing.. Also not a big fan of the .each function though.. perhaps an event listener would be better. (Not to mention, this requires Jquery) Uses https://github.com/zeusdeux/isInViewport

$('video').each(function(){ if (!$(this).is(":in-viewport")) { $(this)[0].pause(); } })

Edit: This library seems helpful too: https://cdnjs.com/libraries/vissense/tutorials/autoplay-video

Edit 2: (Seems promising too, though bad for iframe, as it reloads src) https://gist.github.com/cferdinandi/9044694

AndersDK12 commented 4 years ago

This seems to work fairly well (Auto-pause other videos playing), not yet sure about performance wise.. ? (Can anyone look into that) But the way it works, is that it uses an array to hold the instances, once they are played, so only played videos gets stored here. It uses the unique (I guess) id from the event.detail.plyr.id as key, and then the plyr instance as value. When the play event is called, for any videos (works for YouTube embeds too) it will check if the instance is in the array, and if it doesn't match the current id, it will pause the player(s), and continue to play the current.

// Pause all instances. var instances = []; player.on('play', () => { for (const [key, instance] of Object.entries(instances)) { if(key != event.detail.plyr.id){ instance.plyr.pause(); } } instances[event.detail.plyr.id] = event.detail; });

AndersDK12 commented 4 years ago

So the full code would look something like:

`var instances = []; function initPlyr(){ const video = document.querySelectorAll('.plyr-video[data-loaded="0"]'); for (var i = 0; i < video.length; i++) {

            const source    = video[i].getAttribute("data-src");
            const isEmbed   = video[i].getAttribute("data-embed");
            const player    = new Plyr(video[i], {
                muted: true,
                autopause: true,
                controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'airplay', 'fullscreen'],
                settings: ['captions', 'quality', 'speed', 'loop']
            });
            // Only use HLS on content from CDN, not embedded videos (YT).
            if(!isEmbed){
                if (!Hls.isSupported()) {
                    video[i].src = source[i];
                } else {
                    const hls = new Hls();
                    hls.loadSource(source);
                    hls.attachMedia(video[i]);
                    window.hls = hls;
                }
            }
            // Pause all instances.
            player.on('play', () => {
                for (const [key, instance] of Object.entries(instances)) {
                    if(key != event.detail.plyr.id){
                        instance.plyr.pause();
                    }
                }
                instances[event.detail.plyr.id] = event.detail;
            });
            // Change loaded status.
            video[i].setAttribute("data-loaded", "1");
        }
    }

    // Multiple instances
    document.addEventListener('DOMContentLoaded', () => {
        initPlyr();
    });`
AndersDK12 commented 4 years ago

Here is my new code with the

Just posting here in case anyone else can use it, or if it can be implemented in a later version. Ideas and improvements are welcome!

`var instances = []; function initPlyr(){ const video = document.querySelectorAll('.plyr-video[data-loaded="0"]'); for (var i = 0; i < video.length; i++) {

            const source    = video[i].getAttribute("data-src");
            const isEmbed   = video[i].getAttribute("data-embed");
            const player    = new Plyr(video[i], {
                muted: true,
                autopause: true,
                controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'airplay', 'fullscreen'],
                settings: ['captions', 'quality', 'speed', 'loop']
            });
            // Only use HLS on content from CDN, not embedded videos (YT).
            if(!isEmbed){
                if (!Hls.isSupported()) {
                    video[i].src = source[i];
                } else {
                    const hls = new Hls();
                    hls.loadSource(source);
                    hls.attachMedia(video[i]);
                    window.hls = hls;
                }
            }
            // Pause all instances (except current).
            player.on('play', () => {
                for (const [key, instance] of Object.entries(instances)) {
                    if(key != event.detail.plyr.id){
                        instance.plyr.pause();
                    }
                }
                instances[event.detail.plyr.id] = event.detail;
                // Fix for embedded videos. SEE (1)
                event.target.setAttribute("data-plyr-id", event.detail.plyr.id);
            });
            // Set player id, and change loaded status.
            video[i].setAttribute("data-plyr-id", player.id);
            video[i].setAttribute("data-loaded", "1");
            // Create new observer for this element.
            observer.observe(video[i]);
        }
    }

    // Multiple instances
    document.addEventListener('DOMContentLoaded', () => {
        initPlyr();
    });

    // Observer (pause when no longer visible)
    let options = {
        root: null,
        rootMargin:'0px',
        threshold:1.0 // 100%
    };
    let callback = (entries, observer)=>{
        entries.forEach(entry => {
            const playerId = entry.target.getAttribute("data-plyr-id");
            if(playerId){
                if(instances[playerId] !== undefined){
                    if(!entry.isIntersecting){
                        // PAUSE
                        instances[playerId].plyr.pause();
                    }else{
                        // PLAY
                        // Will not work, since player has not been added to instances, before on play event is called. Not needed for my application though..
                    }
                }
            }
        });
    }
    let observer = new IntersectionObserver(callback, options);
    `

1: player.id is undefined for embedded videos (YouTube), is this a bug?