Comment to 'UNA 14.0.0 Timeline Autoplay Sound Conflict: Multiple Videos Playing Together'
  • Improvement Suggestion: Video Autoplay Single-Session Handling in Timeline

    We’ve improved the video autoplay logic in the BxTimelineView class to ensure a better user experience. In the original implementation, multiple videos could start playing simultaneously when scrolled into view, causing audio overlap and performance issues.

    What we changed:

    • Added playback control in initVideosAutoplay: Now, when a video starts playing via playerjs, it pauses all other initialized players before continuing.
    • Enhanced autoplayVideos logic: Instead of attempting to play/pause all videos on scroll, it now calculates which video is most visible in the viewport and ensures only that one plays, while pausing any others.
    • Tracked the currently playing video using _sCurrentPlayerKey to avoid redundant operations and maintain consistent state.
    • Kept backward compatibility with the original functions and admin options (onon_muteoff).

    This solution works well on the homepage timeline but in the the organization timeline however, I encounter an issue when transitioning from one video to another. It appears that for a brief moment, the videos still play simultaneously.

    Any suggestion is welcome:

    https://github.com/kabballa/timeline/commit/ee11eb89107b81c199ce9c9fcf8b5b42a463be5a

    Why it matters:

    This ensures a clean, intuitive browsing experience only one video plays at a time, without sound overlaps or excessive system resource use.

    NOTE this is a test not a final solution

    /**
     * Initialize iframe players and enforce single-session playback: only one video plays at a time.
     * Ensures that before any video plays, all others are paused.
     */
    BxTimelineView.prototype.initVideosAutoplay = function(oParent) {
        var $this = this;
        if (this._sVideosAutoplay === 'off')
            return;
    
        // Preserve original initializationthis.initVideos(oParent);var sPrefix = oParent.hasClass(this.sClassView)
            ? oParent.attr('id')
            : oParent.parents('.' + this.sClassView + ':first').attr('id');
    
        // Register each iframe player once
        oParent.find('iframe[id]').each(function() {
            var frame = this;
            var key = sPrefix + '_' + frame.id;
            if (window.glBxTimelineVapPlayers[key])
                return;
    
            var player = new playerjs.Player(frame);
            if ($this._sVideosAutoplay === 'on_mute')
                player.mute();
    
            // Sync container height on ready/playvar fFixHeight = function() {var video = $('#' + key).contents().find('video');if (video.length)
                    $('#' + key).height(video.height() + 'px');
            };
            player.on('ready', fFixHeight);
            player.on('play', fFixHeight);
    
            // When this player starts, pause all others
            player.on('play', function() {
                for (var k in window.glBxTimelineVapPlayers) {
                    if (k !== key) {
                        try {
    
    window.glBxTimelineVapPlayers[k].pause();
                        } catch (e) {
                            // ignore
                        }
                    }
                }
            });
    
            window.glBxTimelineVapPlayers[key] = player;
        });
    
        // Reset current session trackingthis._sCurrentPlayerKey = null;
    
        // Bind original scroll autoplay
    $(window).off('scroll.timelineVap').on('scroll.timelineVap', function() {
            if (!$this.oView.is(':visible'))
                return;
            if (!window.requestAnimationFrame) {
                setTimeout(function() {
                    $this.autoplayVideos($this.oView, $this._fVapOffsetStart, $this._fVapOffsetStop);
                }, 100);
            } else {
                window.requestAnimationFrame(function() {
                    $this.autoplayVideos($this.oView, $this._fVapOffsetStart, $this._fVapOffsetStop);
                });
            }
        });
    };
    
    /**
     * Autoplay the single most visible video on scroll: pause the previous and play the new one.
     */
    BxTimelineView.prototype.autoplayVideos = function(oView, fOffsetStart, fOffsetStop) {
        var sPrefix = oView.attr('id') + '_';
        var winTop = $(window).scrollTop();
        var winH = $(window).height();
        var bestKey = null;
        var bestRatio = 0;
    
        oView.find('.' + this.sClassItem + ' iframe[id]').each(function() {
            var $f = $(this);
            var top = $f.offset().top;
            var bottom = top + $f.height();
            var visTop = Math.max(top, winTop);
            var visBottom = Math.min(bottom, winTop + winH);
            var ratio = Math.max(0, visBottom - visTop) / $f.height();
            var key = sPrefix + this.id;
            if (ratio > bestRatio) {
                bestRatio = ratio;
                bestKey = key;
            }
        });
    
        // If a new best: pause old, play newif (bestKey !== this._sCurrentPlayerKey) {if (this._sCurrentPlayerKey && window.glBxTimelineVapPlayers[this._sCurrentPlayerKey]) {try {
    
    window.glBxTimelineVapPlayers[this._sCurrentPlayerKey].pause();
                } catch (e) {
                    // ignore
                }
            }
            if (bestKey && window.glBxTimelineVapPlayers[bestKey]) {
                try {
    
    window.glBxTimelineVapPlayers[bestKey].play();
                } catch (e) {
                    // ignore
                }
            }
            this._sCurrentPlayerKey = bestKey;
        }
    };
    
    /**
     * Manually trigger autoplay logic.
     */
    BxTimelineView.prototype.playVideos = function(oView) {
        if (this._sVideosAutoplay === 'off')
            return;
        this.autoplayVideos(oView, this._fVapOffsetStart, this._fVapOffsetStop);
    };
    
    /**
     * Immediately pause all videos and reset the current key.
     */
    BxTimelineView.prototype.pauseVideos = function(oView) {
        for (var k in window.glBxTimelineVapPlayers) {
            try {
    
    window.glBxTimelineVapPlayers[k].pause();
            } catch (e) {
                // ignore
            }
        }
        this._sCurrentPlayerKey = null;
    };