使用 reveal.js 逐步浏览视频文件

Stepping through a video file with reveal.js

问题与疑问

在 reveal.js 演示文稿中,我想包含一个长视频文件。我想让 playblack 停在某些位置,这样我就有时间向观众解释他们看到的是什么。然后,我希望在单击时继续播放。我该怎么做?

到目前为止未成功的尝试

我的尝试如下。我将视频文件分成 1.webm2.webm3.webm 等部分,这样每个部分都在我想休息的地方结束。那么我的想法是

  1. 覆盖 Reveal.js 的 keydown 事件,这样它就不会转到下一张幻灯片,而是执行我的 Javascript。我怎样才能做这样的事情?

    <div class="slides">
        <section class="video-stepper">
            <video>
                <source data-src="1.webm" type="video/webm" />
            </video>
        </section>
    </div>
    
    <script>
        $(function() {
            // How can I do this?
            Reveal.addEventListener('click', function(event) {
                if ($(event.currentSlide).hasClass('video-stepper')) {
                    event.preventDefault();
                    // change 'src' of the video element and start the playback.
                }
            });
        });
    </script>
    
  2. 使用片段并在显示时自动播放视频:

    <div class="slides">
        <section class="video-stepper">
            <video class="fragment current-visible video-step">
                <source data-src="1.webm" type="video/webm" />
            </video>
            <video class="fragment current-visible video-step">
                <source data-src="2.webm" type="video/webm" />
            </video>
            <video class="fragment current-visible video-step">
                <source data-src="3.webm" type="video/webm" />
            </video>
        </section>
    </div>
    
    <script>
        $(function() {
            Reveal.addEventListener('fragmentshown', function(event) {
                if ($(event.fragment).hasClass('video-step')) {
                    event.fragment.play();
                }
            });
        });
    </script>
    

    还有一些 CSS 取自问题 Hide reveal.js fragments after their appearance,因此片段相互堆叠:

    .fragment.current-visible.visible:not(.current-fragment) {
        display: none;
        height:0px;
        line-height: 0px;
        font-size: 0px;
    }
    

    但是,这带有一些淡入和淡出,看起来很糟糕。如何避免褪色?

进入视频幻灯片时,您基本上可以通过调用 Reveal.disableEventListeners() 来禁用 reveal.js,然后将您自己的逻辑绑定到 keydown 事件,直到您浏览完所有视频,然后再启用 reveal.js 再次 Reveal.addEventListeners().

需要一些额外的努力来避免切换到下一个视频时出现闪烁。您可以为新视频添加一个新的 <video> 元素,借助 CSS z-index 将其置于当前 <video> 之上,播放新视频,然后删除老.

HTML

<section class="video-stepper">
    <!-- Unlike the other <video> element, this one is not absolutely 
         positioned. We hide it with CSS, but use it to reserve space
         on the slide and compute the optimal width and height. -->
    <video class="placeholder stretch">
        <source src="1.webm">
    </video>

    <video class="video-step" data-sources='["1.webm","2.webm","3.webm"]'></video>
</section>

CSS

.video-stepper {
    position: relative;
}

video.video-step {
    position: absolute;
    top: 0;
    left: 0;
}

video.video-step.front {
    z-index: 10;
}

video.placeholder {
    visibility: hidden;
}

Javascript

这有点冗长,但效果很好。

Reveal.addEventListener('slidechanged', function(event) {
    if ($(event.currentSlide).hasClass('video-stepper')) {
        // When we enter a slide with a step-by-step video, we stop reveal.js
        //  from doing anything. Below, we define our own keystroke handler.
        Reveal.removeEventListeners();

        // Set the width and height of the video so that it fills the slide.
        var stretcher = $(event.currentSlide).find('video.placeholder').get(0);
        var video = $(event.currentSlide).find('video.video-step').get(0);
        video.setAttribute('width', stretcher.getAttribute('width'));
        video.setAttribute('height', stretcher.getAttribute('height'));

        // Convert the data-sources attribute to an array of strings. We will
        // iterate through the array with current_video_index.
        var sources = JSON.parse(video.getAttribute('data-sources'));
        var current_video_index = 0;

        // Add a <source> element to the video and set the 'src' to
        // the first video.
        var source = document.createElement('source');
        source.setAttribute('src', sources[0]);
        video.appendChild(source);

        document.addEventListener('keydown', function step_through_videos(event) {
            if (event.which == 39) {
                // right arrow key: show next video

                // For the next video, create a new <video> element
                // and place it on top of the old <video> element.
                // Then load and play the new. This avoids flickering.
                var new_video = $(video).clone().get(0);
                var new_video_source = $(new_video).children('source').get(0);
                new_video_source.src = sources[current_video_index];
                new_video.load();
                $(new_video).addClass('front video-step');
                $(new_video).insertAfter(video);
                new_video.play();

                // Wait a little before removing the old video.
                new Promise((resolve) => setTimeout(resolve, 500)).then(function() {
                    video.remove();
                    video = new_video;
                    $(video).removeClass('front');
                });

                current_video_index = current_video_index + 1;

                event.preventDefault();
            } else if (event.which == 37) {
                // left arrow key: return the counter to previous video
                current_video_index = current_video_index - 1;

                event.preventDefault();
            }

            if (0 > current_video_index || current_video_index >= sources.length) {
                // Reinstall reveal.js handlers.

                document.removeEventListener('keydown', step_through_videos, true);
                Reveal.addEventListeners();
                console.log('Added reveal.js event listeners.');
            }
        }, true);
    }
});