From 5cb82a2520662104b9b5a0eb3e36459df340d16b Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 Jun 2022 18:18:13 -0400 Subject: [PATCH 1/3] videos now only play when visible on-screen --- public/js/hlsPlayback.js | 100 ++++++++++++++++++++++++++++++++------- 1 file changed, 82 insertions(+), 18 deletions(-) diff --git a/public/js/hlsPlayback.js b/public/js/hlsPlayback.js index 5cd46a6..44e43ed 100644 --- a/public/js/hlsPlayback.js +++ b/public/js/hlsPlayback.js @@ -1,25 +1,89 @@ // @license http://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0 // SPDX-License-Identifier: AGPL-3.0-only -function playVideo(overlay) { - const video = overlay.parentElement.querySelector('video'); - const url = video.getAttribute("data-url"); - video.setAttribute("controls", ""); - overlay.style.display = "none"; +(function() { + 'use strict' + + const thresholdLower = 0.8 + const thresholdUpper = 0.9 + const videoConfig = { + controls: true, + oldRatio: 0.0, + viewportChange: false + } + const videoObserver = new IntersectionObserver(onViewportChange, { + threshold: [thresholdLower, thresholdUpper] + }) + + function isMostlyInView(entry) { + return entry.intersectionRatio > thresholdUpper + } + + function isLeavingView(entry, video) { + return entry.intersectionRatio >= thresholdLower && video.oldRatio > entry.intersectionRatio + } + + // https://stackoverflow.com/questions/36803176 + function isPlaying(video) { + return video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA + } + + function observeVideo(video) { + videoObserver.observe(video) + video.addEventListener('pause', (evt) => { + video.viewportChange + ? (videoObserver.observe(video), video.viewportChange = false) + : videoObserver.unobserve(video) + }) + video.addEventListener('play', (evt) => { + videoObserver.observe(video) + }) + } + + function displayVideo(overlay, video) { + overlay.style.display = 'none' + Object.assign(video, videoConfig) + video.play().then(() => observeVideo(video)) + } + + function onViewportChange(entries) { + entries.forEach((entry) => { + const video = entry.target + if (isMostlyInView(entry) && !isPlaying(video)) { + video.play() + } else if ( + !isMostlyInView(entry) && + isLeavingView(entry, video) && + isPlaying(video) + ) { + video.viewportChange = true + video.pause() + } + video.oldRatio = entry.intersectionRatio + }) + } + + window.playVideo = function(overlay) { + if (!('Hls' in window)) { + console.error('ERROR: Hls not found, unable to play video!') + return + } + + const video = overlay.parentElement.querySelector('video') + const url = video.getAttribute('data-url') if (Hls.isSupported()) { - var hls = new Hls({autoStartLoad: false}); - hls.loadSource(url); - hls.attachMedia(video); - hls.on(Hls.Events.MANIFEST_PARSED, function () { - hls.loadLevel = hls.levels.length - 1; - hls.startLoad(); - video.play(); - }); + const hls = new Hls() + hls.attachMedia(video) + hls.on(Hls.Events.MEDIA_ATTACHED, () => { + hls.loadSource(url) + hls.on(Hls.Events.MANIFEST_PARSED, () => { + displayVideo(overlay, video) + }) + }) } else if (video.canPlayType('application/vnd.apple.mpegurl')) { - video.src = url; - video.addEventListener('canplay', function() { - video.play(); - }); + video.src = url + video.addEventListener('canplay', () => video.play()) } -} + } +})() // @license-end From 67156e56a809e49be3af736ff8d6eca7d7c56bfd Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 Jun 2022 23:45:45 -0400 Subject: [PATCH 2/3] fmt, cleanup, add min observer threshold --- public/js/hlsPlayback.js | 122 ++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 66 deletions(-) diff --git a/public/js/hlsPlayback.js b/public/js/hlsPlayback.js index 44e43ed..ee651e8 100644 --- a/public/js/hlsPlayback.js +++ b/public/js/hlsPlayback.js @@ -1,89 +1,79 @@ // @license http://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0 // SPDX-License-Identifier: AGPL-3.0-only (function() { - 'use strict' + 'use strict'; - const thresholdLower = 0.8 - const thresholdUpper = 0.9 - const videoConfig = { - controls: true, - oldRatio: 0.0, - viewportChange: false - } - const videoObserver = new IntersectionObserver(onViewportChange, { - threshold: [thresholdLower, thresholdUpper] - }) + const m3u8 = 'application/vnd.apple.mpegurl'; + const thresholds = [ 0.1, 0.9 ]; // check viewport at 10% and 90% + const videoConfig = { controls: true, oldRatio: 0, visible: true }; - function isMostlyInView(entry) { - return entry.intersectionRatio > thresholdUpper + const observer = new IntersectionObserver(observeEntries, { threshold: thresholds }); + + function isMostlyInView(currRatio) { + return (currRatio > thresholds[1]); } - function isLeavingView(entry, video) { - return entry.intersectionRatio >= thresholdLower && video.oldRatio > entry.intersectionRatio + function isLeavingView(currRatio, oldRatio) { + return (oldRatio > currRatio); } - // https://stackoverflow.com/questions/36803176 - function isPlaying(video) { - return video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA + // is paused -> https://stackoverflow.com/questions/36803176 + function isVideoPaused(video) { + return (video.paused || video.ended || video.readyState < video.HAVE_CURRENT_DATA); } - function observeVideo(video) { - videoObserver.observe(video) - video.addEventListener('pause', (evt) => { - video.viewportChange - ? (videoObserver.observe(video), video.viewportChange = false) - : videoObserver.unobserve(video) - }) - video.addEventListener('play', (evt) => { - videoObserver.observe(video) - }) + function loadVideo(video) { + Object.assign(video, videoConfig); + return video.play(); } - function displayVideo(overlay, video) { - overlay.style.display = 'none' - Object.assign(video, videoConfig) - video.play().then(() => observeVideo(video)) - } - - function onViewportChange(entries) { + function observeEntries(entries) { entries.forEach((entry) => { - const video = entry.target - if (isMostlyInView(entry) && !isPlaying(video)) { - video.play() - } else if ( - !isMostlyInView(entry) && - isLeavingView(entry, video) && - isPlaying(video) - ) { - video.viewportChange = true - video.pause() + const video = entry.target; + + const inView = isMostlyInView(entry.intersectionRatio) + const isPaused = isVideoPaused(video) + + if (inView && isPaused) video.play(); + else if (!inView && !isPaused && isLeavingView(entry.intersectionRatio, video.oldRatio)) { + video.visible = false; + video.pause(); } - video.oldRatio = entry.intersectionRatio - }) + + video.oldRatio = entry.intersectionRatio; + }); + } + + // we set the oldRatio to 0 on pauses, as we aren't observing where the viewport is + function observeVideo(video) { + observer.observe(video); + video.addEventListener('play', (evt) => observer.observe(video)); + video.addEventListener('pause', (evt) => { + video.visible ? (observer.unobserve(video), video.oldRatio = 0) : (observer.observe(video), video.visible = true) + }); + } + + function useHLS(video, url) { + const hls = new Hls(); + hls.on(Hls.Events.MEDIA_ATTACHED, () => hls.loadSource(url)); + hls.on(Hls.Events.MANIFEST_PARSED, () => loadVideo(video).then(() => observeVideo(video))); + hls.attachMedia(video); + } + + function useM3U8(video, url) { + video.src = url; + video.addEventListener('canplay', () => loadVideo(video)); } window.playVideo = function(overlay) { - if (!('Hls' in window)) { - console.error('ERROR: Hls not found, unable to play video!') - return - } + const video = overlay.parentElement.querySelector('video'); + const url = video.getAttribute('data-url'); - const video = overlay.parentElement.querySelector('video') - const url = video.getAttribute('data-url') + if ('Hls' in window && Hls.isSupported()) useHLS(video, url); + else if (video.canPlayType(m3u8)) useM3U8(video, url); - if (Hls.isSupported()) { - const hls = new Hls() - hls.attachMedia(video) - hls.on(Hls.Events.MEDIA_ATTACHED, () => { - hls.loadSource(url) - hls.on(Hls.Events.MANIFEST_PARSED, () => { - displayVideo(overlay, video) - }) - }) - } else if (video.canPlayType('application/vnd.apple.mpegurl')) { - video.src = url - video.addEventListener('canplay', () => video.play()) - } + overlay.style.display = 'none'; } + })() // @license-end From d7efa787b1edcaeca415ada97e400096f795b147 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 Jun 2022 23:55:41 -0400 Subject: [PATCH 3/3] misc. changes --- public/js/hlsPlayback.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/public/js/hlsPlayback.js b/public/js/hlsPlayback.js index ee651e8..0f7e430 100644 --- a/public/js/hlsPlayback.js +++ b/public/js/hlsPlayback.js @@ -5,18 +5,18 @@ const m3u8 = 'application/vnd.apple.mpegurl'; const thresholds = [ 0.1, 0.9 ]; // check viewport at 10% and 90% - const videoConfig = { controls: true, oldRatio: 0, visible: true }; + const videoConfig = { controls: true, oldRatio: 0, testViewport: true }; const observer = new IntersectionObserver(observeEntries, { threshold: thresholds }); - function isMostlyInView(currRatio) { - return (currRatio > thresholds[1]); - } - function isLeavingView(currRatio, oldRatio) { return (oldRatio > currRatio); } + function isMostlyInView(currRatio) { + return (currRatio > thresholds[1]); + } + // is paused -> https://stackoverflow.com/questions/36803176 function isVideoPaused(video) { return (video.paused || video.ended || video.readyState < video.HAVE_CURRENT_DATA); @@ -36,7 +36,7 @@ if (inView && isPaused) video.play(); else if (!inView && !isPaused && isLeavingView(entry.intersectionRatio, video.oldRatio)) { - video.visible = false; + video.testViewport = false; video.pause(); } @@ -44,12 +44,12 @@ }); } - // we set the oldRatio to 0 on pauses, as we aren't observing where the viewport is + // we set the oldRatio to 0 on manual pauses, as we don't know where the viewport will end up function observeVideo(video) { observer.observe(video); video.addEventListener('play', (evt) => observer.observe(video)); video.addEventListener('pause', (evt) => { - video.visible ? (observer.unobserve(video), video.oldRatio = 0) : (observer.observe(video), video.visible = true) + video.testViewport ? (observer.unobserve(video), video.oldRatio = 0) : (observer.observe(video), video.testViewport = true) }); } @@ -65,6 +65,7 @@ video.addEventListener('canplay', () => loadVideo(video)); } + // export playVideo window.playVideo = function(overlay) { const video = overlay.parentElement.querySelector('video'); const url = video.getAttribute('data-url');