1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        怎么用 JavaScript 構(gòu)建自定義的 HTML5 視頻播放器

        共 21641字,需瀏覽 44分鐘

         ·

        2023-05-09 13:44

        在網(wǎng)頁中觀看和分享視頻內(nèi)容是一個(gè)很常見的功能,多年來,視頻嵌入網(wǎng)頁的方式發(fā)生了變化?,F(xiàn)在,我們?cè)诂F(xiàn)代瀏覽器中使用 <video> 標(biāo)簽就可以添加視頻文件到網(wǎng)頁上,該標(biāo)簽支持多個(gè)視頻格式。

        922698cebaf536331e5739495be96a79.webpvideo_element.webp

        當(dāng)使用 <video> 標(biāo)簽時(shí)的主要警告是渲染的視頻播放器會(huì)因?yàn)g覽器而異,如果你想提供一致的用戶體驗(yàn),使用原生操作并不理想。這就是為什么構(gòu)建自定義控件而不是使用瀏覽器默認(rèn)界面很有用的原因。

        在這個(gè)教程中,我將會(huì)帶你使用 JavaScript 構(gòu)建一個(gè)自定義的視頻播放器。目標(biāo)是如何利用瀏覽器 HTML5 Media API 來提升默認(rèn)設(shè)置的體驗(yàn)。

        我們將在本教程中構(gòu)建一個(gè)看起來像 YouTube 視頻播放器,因?yàn)槲艺J(rèn)為復(fù)制大多數(shù)人已經(jīng)熟悉的一些功能是個(gè)好主意。

        當(dāng)然,我們并不會(huì)實(shí)現(xiàn) YouTube 播放器上的所有功能,因?yàn)檫@會(huì)讓教程更長(zhǎng)、更復(fù)雜。然而,一旦你完成了本教程,我相信你能夠很輕松地加入新的功能。

        你可以查看我們將構(gòu)建的線上案例,或者在 GitHub 上查看源碼。

        準(zhǔn)備條件

        你需要對(duì) JavaScriptDOM 有基本的了解,才能繼續(xù)學(xué)習(xí)本教程。我推薦你使用最新版本的谷歌瀏覽器,因?yàn)樵诒疚木帉憰r(shí),我們將添加的一些功能(比如畫中畫功能)僅適用于谷歌(Webkit 內(nèi)核)瀏覽器。

        開始

        我在 GitHub 中為本教程準(zhǔn)備了開始文件。有需要的話,你可以克隆到自己的機(jī)器上,并在編輯器中打開。你將分別在 index.htmlstyle.css 中找到播放器的標(biāo)記文檔文件及其樣式,以及我們用來測(cè)試播放器的視頻文件。index.js 將是我們添加播放器工作所需的所有 JavaScript 代碼的地方。

        在終端中運(yùn)行 npm install 來安裝 browser-sync 作為啟動(dòng) Web 服務(wù)器的開發(fā)依賴項(xiàng),其在任何文件更改時(shí)自動(dòng)刷新頁面。然后 npm start 啟動(dòng)項(xiàng)目,監(jiān)聽瀏覽器 http://localhost:3000。

        目前都做了些什么

        現(xiàn)在,視頻播放器保留本機(jī)瀏覽器控件,正如你所期待那樣工作。自定義控件已經(jīng)被定義在 #video-controls 元素,但是它們被隱藏了。

              
              <!-- index.html -->
        . . .
        <div class="video-controls hidden" id="video-controls">
        <!-- Custom controls are defined here -->
        </div>
        . . .

        即使我們要為控件實(shí)現(xiàn)自定義界面,保留 <video> 元素上的 controls 屬性是個(gè)很好的主意,這樣用戶不管出于什么原因禁用 JavaScript,瀏覽器本機(jī)的控件依舊可使用。對(duì)于其他人,本機(jī)空間可以輕松隱藏并替換成自定義控件,這稍后進(jìn)行演示。

        海報(bào)圖像已經(jīng)添加到視頻中,設(shè)置 preload屬性值為 metadata,這指示瀏覽器僅獲取視頻元數(shù)據(jù)(比如 duration)。為了讓事情簡(jiǎn)單點(diǎn),我們只添加 MP4 類型的視頻源文件,因?yàn)樵擃愋偷囊曨l被所有主流瀏覽器兼容,是一個(gè)非常安全的默認(rèn)值。有關(guān)視頻格式和瀏覽器兼容性的更多信息,可參考該文檔。

              
              <!-- index.html -->
        . . .
        <video controls class="video" id="video" preload="metadata" poster="poster.jpg">
        <source src="video.mp4" type="video/mp4"></source>
        </video>
        . . .

        隱藏自帶控件

        我們首先需要做的事情是在確認(rèn)瀏覽器支持 HTML5 視頻后,隱藏默認(rèn)視頻控件并提供我們自己的界面。在你的 index.js 文件中輸入下面代碼片段來實(shí)現(xiàn)上面的功能:

              
              // index.js
        // Select elements here
        const video = document.getElementById('video');
        const videoControls = document.getElementById('video-controls');

        const videoWorks = !!document.createElement('video').canPlayType;
        if (videoWorks) {
        video.controls = false;
        videoControls.classList.remove('hidden');
        }

        canPlayType 屬性是我們檢查瀏覽器對(duì)視頻格式支持的方式。要使用它,我們需要?jiǎng)?chuàng)建 <video> 元素的實(shí)例并將檢查是否支持 canPlayType。如果支持,則可以安全地假設(shè)其支持 HTML 視頻,然后禁用默認(rèn)控件,啟用我們自定義的控件。

        b8187679eec8a5be00c80ad8e4187637.webpcanPlayType_support.webp

        默認(rèn)控件已經(jīng)被替換成自定義控件

        切換播放狀態(tài)

        讓我們從基礎(chǔ)開始。我們需要通過點(diǎn)擊播放按鈕來播放或者暫停視頻,并且更改應(yīng)該匹配視頻狀態(tài)的圖標(biāo)。我們從獲取視頻和播放按鈕開始,代碼在 index.js 頂部,如下:

              
              // index.js
        const playButton = document.getElementById('play');

        然后,我們創(chuàng)建一個(gè)函數(shù)來切換視頻播放狀態(tài):

              
              // index.js
        // Add functions here

        // togglePlay toggles the playback state of the video.
        // If the video playback is paused or ended, the video is played
        // 如果視頻播放是暫停或者視頻結(jié)尾狀態(tài),視頻播放
        // otherwise, the video is paused
        // 否則,視頻暫停
        function togglePlay() {
        if (video.paused || video.ended) {
        video.play();
        } else {
        video.pause();
        }
        }

        最后,我們創(chuàng)建一個(gè)時(shí)間監(jiān)聽器,當(dāng) playButton 按鈕被點(diǎn)擊后執(zhí)行 togglePlay 方法。

              
              // index.js
        // Add eventlisteners here
        playButton.addEventListener('click', togglePlay);

        夠簡(jiǎn)單吧?通過點(diǎn)擊瀏覽器中的播放按鈕對(duì)其測(cè)試。它應(yīng)該正確地播放和暫停視頻。

        7dff2ec356dd06796eb161006e82487c.webpappropriately_play_and_stop_video.webm.gif

        這實(shí)際上為本教程的其他部分定下了基調(diào)。我們通常會(huì)選擇一個(gè)視頻控件,創(chuàng)建一個(gè)實(shí)現(xiàn)特定功能的函數(shù),通過事件監(jiān)聽器將其連接起來。

        我們繼續(xù),根據(jù)視頻狀態(tài)更新播放按鈕。下面是 playButtonHTML 文件:

              
              <!-- index.html -->
        . . .
        <button data-title="Play (k)" id="play">
        <svg class="playback-icons">
        <use href="#play-icon"></use>
        <use class="hidden" href="#pause"></use>
        </svg>
        </button>
        . . .

        <svg> 元素中,我們有播放和暫停按鈕,但是一次我們只能展示其中一個(gè),另一個(gè)則隱藏?,F(xiàn)在我們要做的就是切換每個(gè)圖標(biāo)的 hidden 類,以便根據(jù)視頻的狀態(tài)展示正確的圖標(biāo)。

        首先,在 index.js 文件頂部選擇圖標(biāo):

              
              // index.js
        const playbackIcons = document.querySelectorAll('.playback-icons use');

        接著,在 togglePlay 函數(shù)下創(chuàng)建一個(gè)函數(shù),用來更新播放按鈕:

              
              // index.js
        // updatePlayButton updates the playback icon and tooltip
        // depending on the playback state
        // 根據(jù)播放狀態(tài),updatePlayButton 函數(shù)更新播放圖標(biāo)和提示
        function updatePlayButton() {
        playbackIcons.forEach(icon => icon.classList.toggle('hidden'));
        }

        最后,在文件底部添加如下事件監(jiān)聽器:

              
              // index.js
        video.addEventListener('play', updatePlayButton);
        video.addEventListener('pause', updatePlayButton);

        當(dāng)視頻播放或者暫停時(shí),updatePlayButton 函數(shù)都會(huì)被執(zhí)行,切換每個(gè)按鈕中的 hidden 類。因?yàn)闀和0粹o元素默認(rèn)值是 hidden 類,一旦視頻被播放,這個(gè)暫停圖標(biāo)出現(xiàn),播放圖標(biāo)將會(huì)隱藏。如果視頻被暫停,則會(huì)發(fā)生相反的情況。你可以在自己瀏覽器上測(cè)試。

        額外要做的事情是,當(dāng)鼠標(biāo)移動(dòng)到播放按鈕上,需要更新展示的提示文本。默認(rèn)提示是 play(k),但是當(dāng)視頻正在播放,需要更新提示信息為 pause(k)。k 是我們將在本教程后面添加播放或者暫停視頻的鍵盤快捷鍵。

        如下,更新 updatePlayButton 函數(shù):

              
              // index.js
        function updatePlayButton() {
        playbackIcons.forEach(icon => icon.classList.toggle('hidden'));

        if (video.paused) {
        playButton.setAttribute('data-title', 'Play (k)')
        } else {
        playButton.setAttribute('data-title', 'Pause (k)')
        }
        }

        當(dāng)視頻正在播放或者暫停時(shí),鼠標(biāo)移動(dòng)到按鈕上,應(yīng)該設(shè)置正確的提示文本。

        如果你想知道提示信息是怎么展示的,可以看下相關(guān)的 CSS

              
              // style.css
        . . .
        button::before {
        content: attr(data-title);
        position: absolute;
        display: none;
        right: 0;
        top: -50px;
        background-color: rgba(0, 0, 0, 0.6);
        color: #fff;
        font-weight: bold;
        padding: 4px 6px;
        word-break: keep-all;
        white-space: pre;
        }

        button:hover::before {
        display: inline-block;
        }
        . . .
        0ee7daba89c818b478d621e9b85a1f92.webpvideo-tooltip.gif

        展示視頻持續(xù)時(shí)間和經(jīng)過時(shí)間

        展示視頻時(shí)長(zhǎng)很必要,因?yàn)檫@是用戶首先想看到的,所以我們接下來將講解。

        下面是持續(xù)時(shí)長(zhǎng)和經(jīng)過時(shí)間的元素標(biāo)記:

              
              <!-- index.html -->
        <div class="time">
        <time id="time-elapsed">00:00</time>
        <span> / </span>
        <time id="duration">00:00</time>
        </div>

        通過 index.js 選擇這兩個(gè)控件(元素),如下:

              
              // index.js
        const timeElapsed = document.getElementById('time-elapsed');
        const duration = document.getElementById('duration');

        一旦頁面加載完成后,我們將使用 duration 屬性展示視頻的總時(shí)長(zhǎng)。這個(gè)屬性表示的是視頻的總秒數(shù),所以在展示之前,我們需要將其轉(zhuǎn)換成分秒。我們創(chuàng)建一個(gè) formatTime 函數(shù),將時(shí)間轉(zhuǎn)換成分秒:

              
              // index.js
        // formatTime takes a time length in seconds and returns the time in
        // minutes and seconds
        function formatTime(timeInSeconds) {
        const result = new Date(timeInSeconds * 1000).toISOString().substr(11, 8);

        return {
        minutes: result.substr(3, 2),
        seconds: result.substr(6, 2),
        };
        };

        接著,我們?cè)?formatTime 函數(shù)下創(chuàng)建 initializeVideo 函數(shù):

              
              // index.js
        // initializeVideo sets the video duration, and maximum value of the
        // progressBar
        function initializeVideo() {
        const videoDuration = Math.round(video.duration);
        const time = formatTime(videoDuration);
        duration.innerText = `${time.minutes}:${time.seconds}`;
        duration.setAttribute('datetime', `${time.minutes}m ${time.seconds}s`)
        }

        如上所示,視頻持續(xù)時(shí)長(zhǎng)被四舍五入,格式化為分秒,然后在屏幕上更新。datetime 同步更新為時(shí)間字符串,表示視頻持續(xù)時(shí)長(zhǎng)。

        接著,如下所示,讓我們將 initializeVideo 函數(shù)連接到 loadedmetadata 監(jiān)聽器上。當(dāng)元數(shù)據(jù)被加載之后,將會(huì)更新視頻的持續(xù)時(shí)長(zhǎng)。

              
              // index.js
        video.addEventListener('loadedmetadata', initializeVideo);
        028a87e3d9f3c48af7797a979a59ed2e.webpvideo-duration.webp

        同理,當(dāng)視頻播放過程中,我們更新播放經(jīng)過的時(shí)間。下面的函數(shù)能幫我們實(shí)現(xiàn)這個(gè)功能:

              
              // index.js
        // updateTimeElapsed indicates how far through the video
        // the current playback is
        function updateTimeElapsed() {
        const time = formatTime(Math.round(video.currentTime));
        timeElapsed.innerText = `${time.minutes}:${time.seconds}`;
        timeElapsed.setAttribute('datetime', `${time.minutes}m ${time.seconds}s`)
        }

        我們需要 timeupdate 事件監(jiān)聽視頻。無論什么時(shí)候,視頻的 currentTime 屬性值更新了,事件就會(huì)觸發(fā)。

              
              // index.js
        video.addEventListener('timeupdate', updateTimeElapsed);

        上面的代碼確保視頻的 currentTime 更新,經(jīng)過時(shí)間也會(huì)適當(dāng)更新。

        68fb2a6a25cdc5da7a7f819fb369ac04.webpcurrent-time-update.gif

        更新進(jìn)度條

        接下來我們要做的事情是當(dāng)視頻播放,更新進(jìn)度條。下面是進(jìn)度條的元素標(biāo)志:

              
              <!-- index.html -->
        . . .
        <div class="video-progress">
        <progress id="progress-bar" value="0" min="0"></progress>
        <input class="seek" id="seek" value="0" min="0" type="range" step="1">
        <div class="seek-tooltip" id="seek-tooltip">00:00</div>
        </div>
        . . .

        上面,我們有 progress 元素,用于顯示任務(wù)的進(jìn)度條,而 range 類型的 input 允許我們快速無縫瀏覽視頻。兩個(gè)元素我都用同個(gè)樣式修飾,所以它們有一樣的寬高,但是 input 是透明色(除了與進(jìn)度條內(nèi)相同的顏色的指示點(diǎn))。

        如果你很好奇,你可以仔細(xì)看 CSS 的內(nèi)容,看看我是怎么做的。讓進(jìn)度條看起來像一個(gè)單一的元素是一種 hack,但是我覺得對(duì)我們的用例來說很合理。

        兩者的 min 屬性被設(shè)置為 0,兩者的 value 屬性指向當(dāng)前時(shí)間值。它們還需要一個(gè) max 屬性,該屬性將設(shè)置為視頻的持續(xù)時(shí)間(以秒為單位),該屬性值來自 video.duration,如上所示。我們可以在 initializeVideo 函數(shù)中實(shí)現(xiàn),但是我們得先選擇元素:

              
              // index.js
        const progressBar = document.getElementById('progress-bar');
        const seek = document.getElementById('seek');

        然后如下更新 initializeVideo 函數(shù):

              
              // index.js
        function initializeVideo() {
        const videoDuration = Math.round(video.duration);
        seek.setAttribute('max', videoDuration);
        progressBar.setAttribute('max', videoDuration);
        const time = formatTime(videoDuration);
        duration.innerText = `${time.minutes}:${time.seconds}`;
        duration.setAttribute('datetime', `${time.minutes}m ${time.seconds}s`)
        }

        現(xiàn)在,進(jìn)度條元素的范圍輸入在 0 和以秒為單位的視頻持續(xù)時(shí)長(zhǎng)之間,如屬性 minmax 屬性。正如你將看到的,這使得我們能夠在任何時(shí)間點(diǎn)輕松地將進(jìn)度條和時(shí)間范圍同步。

        繼續(xù),當(dāng)視頻被播放我們就更新上述元素的值,以便進(jìn)度條發(fā)揮作用。如下,創(chuàng)建 updateProgress 函數(shù):

              
              // index.js
        // updateProgress indicates how far through the video
        // the current playback is by updating the progress bar
        function updateProgress() {
        seek.value = Math.floor(video.currentTime);
        progressBar.value = Math.floor(video.currentTime);
        }

        然后,在第一個(gè)事件監(jiān)聽器下,為 video 添加一個(gè)新的名為 timeupdate 事件監(jiān)聽器:

              
              // index.js
        video.addEventListener('timeupdate', updateProgress);

        刷新你的瀏覽器,然后嘗試。當(dāng)視頻被播放,你應(yīng)該看到進(jìn)度條更新。

        273af80162b22956221463fc0c3934fc.webpprogress-bar-update.gif

        預(yù)先跳轉(zhuǎn)

        大多數(shù)的播放器都允許你點(diǎn)擊進(jìn)度條跳轉(zhuǎn)到視頻指定的點(diǎn),我們的視頻播放器也將一樣。首先,我們需要選擇提示信息元素:

              
              // index.js
        const seekTooltip = document.getElementById('seek-tooltip');

        然后,添加一個(gè)函數(shù),用來當(dāng)光標(biāo)移動(dòng)到進(jìn)度條上在信息元素里展示時(shí)間戳:

              
              // index.js
        // updateSeekTooltip uses the position of the mouse on the progress bar to
        // roughly work out what point in the video the user will skip to if
        // the progress bar is clicked at that point
        function updateSeekTooltip(event) {
        const skipTo = Math.round((event.offsetX / event.target.clientWidth) * parseInt(event.target.getAttribute('max'), 10));
        seek.setAttribute('data-seek', skipTo)
        const t = formatTime(skipTo);
        seekTooltip.textContent = `${t.minutes}:${t.seconds}`;
        const rect = video.getBoundingClientRect();
        seekTooltip.style.left = `${event.pageX - rect.left}px`;
        }

        此函數(shù)在 seek 元素中,使用光標(biāo)位置粗略計(jì)算用戶懸停范圍輸入框的地方,然后將位置信息存放在 data-seek 屬性中,同時(shí)更新提示信息以反映該位置的時(shí)間戳。

        seek 控制器中關(guān)聯(lián) updateSeekTooltip 函數(shù)和 mousemove 來查看效果:

              
              // index.js
        seek.addEventListener('mousemove', updateSeekTooltip);
        9c7c774e1ce853da703da40e5ad1001a.webpmousemove-tooltip.gif

        不管是點(diǎn)擊或者拖拽指示點(diǎn),一旦 seek 元素值發(fā)生更改,我們希望跳轉(zhuǎn)到 data-seek 屬性設(shè)置的時(shí)間點(diǎn)。

        updateSeekTooltip 函數(shù)下,創(chuàng)建一個(gè)新的名為 skipAhead 的函數(shù):

              
              // index.js
        // skipAhead jumps to a different point in the video when
        // the progress bar is clicked
        function skipAhead(event) {
        const skipTo = event.target.dataset.seek ? event.target.dataset.seek : event.target.value;
        video.currentTime = skipTo;
        progressBar.value = skipTo;
        seek.value = skipTo;
        }

        使用 input 事件監(jiān)控 seek 元素發(fā)生更改時(shí),將執(zhí)行此函數(shù)。然后,我們獲取 data-seek 的值并檢查其是否有效。如果有效,我們獲取該值并更新視頻播放過的時(shí)間和進(jìn)度條的位置。如果 data-seek 屬性不存在(比如在手機(jī)端),改為使用 seek 元素的值。

        這產(chǎn)生跳轉(zhuǎn)到視頻指定位置的效果。

              
              // index.js
        seek.addEventListener('input', skipAhead);
        1a8263ea7dd0d473e5158fbbe6f6fd8f.webpjump-ahead-demo.gif

        音頻控制

              
              <!-- index.html -->
        . . .
        <div class="volume-controls">
        <button data-title="Mute (m)" class="volume-button" id="volume-button">
        <svg>
        <use class="hidden" href="#volume-mute"></use>
        <use class="hidden" href="#volume-low"></use>
        <use href="#volume-high"></use>
        </svg>
        </button>

        <input class="volume" id="volume" value="1" type="range" max="1" min="0" step="0.01">
        </div>
        . . .

        在上面代碼片段中,你可以找到所有相關(guān)音頻控件的標(biāo)記。我們有一個(gè)按鈕,根據(jù)視頻音頻的狀態(tài)展示,和一個(gè)控制音頻范圍的 input 元素。

        首先,當(dāng) #volume 元素的值發(fā)生更改,我們要做的就是更改視頻的音頻大小。我們也要更新視頻當(dāng)前的圖標(biāo)。

        正如你所見,音頻的輸入范圍是 01,并以 0.01 的值遞增。以這種方式設(shè)置它是為了使其與視頻的音量屬性值保持一致,該屬性值的范圍也是從 01,其中 0 是最低音量,1 是最高音量。

        繼續(xù),我們選擇按鈕,圖標(biāo)和輸入框,如下 index.js 所示:

              
              // index.js
        const volumeButton = document.getElementById('volume-button');
        const volumeIcons = document.querySelectorAll('.volume-button use');
        const volumeMute = document.querySelector('use[href="#volume-mute"]');
        const volumeLow = document.querySelector('use[href="#volume-low"]');
        const volumeHigh = document.querySelector('use[href="#volume-high"]');
        const volume = document.getElementById('volume');

        接著,創(chuàng)建一個(gè)新的名為 updateVolume 函數(shù),當(dāng)音頻輸入框值發(fā)生更改,該函數(shù)更新視頻音頻值:

              
              // index.js
        // updateVolume updates the video's volume
        // and disables the muted state if active
        function updateVolume() {
        if (video.muted) {
        video.muted = false;
        }

        video.volume = volume.value;
        }

        然后,將其和 volume 元素關(guān)聯(lián)起來,如下:

              
              // index.js
        volume.addEventListener('input', updateVolume);

        到這里,你將意識(shí)到當(dāng)你左滑輸入框時(shí),音量減少,反之音量增加。我們需要添加另一個(gè)函數(shù)來在音量變化時(shí)更新圖標(biāo):

              
              // index.js
        // updateVolumeIcon updates the volume icon so that it correctly reflects
        // the volume of the video
        function updateVolumeIcon() {
        volumeIcons.forEach(icon => {
        icon.classList.add('hidden');
        });

        volumeButton.setAttribute('data-title', 'Mute (m)')

        if (video.muted || video.volume === 0) {
        volumeMute.classList.remove('hidden');
        volumeButton.setAttribute('data-title', 'Unmute (m)')
        } else if (video.volume > 0 && video.volume <= 0.5) {
        volumeLow.classList.remove('hidden');
        } else {
        volumeHigh.classList.remove('hidden');
        }
        }

        當(dāng)這函數(shù)執(zhí)行,所有的圖標(biāo)都會(huì)隱藏,然后會(huì)根據(jù)條件顯示其中一個(gè)圖標(biāo)。

        我們可以通過監(jiān)聽視頻 volumechange 事件,在每次音量發(fā)生變化時(shí)運(yùn)行 updateVolumeIcon 函數(shù),如下:

              
              // index.js
        video.addEventListener('volumechange', updateVolumeIcon);

        添加上面的更改后,在你瀏覽上你可以看到下面的效果:

        41e33d50dc10c14c30eda330c383be7b.webpvolume-change.gif

        我們需要添加的另一個(gè)事件是能夠通過單擊音量圖標(biāo)使得視頻靜音和取消靜音。我們將創(chuàng)建一個(gè)名為 toggleMute 函數(shù):

              
              // index.js
        // toggleMute mutes or unmutes the video when executed
        // When the video is unmuted, the volume is returned to the value
        // it was set to before the video was muted
        function toggleMute() {
        video.muted = !video.muted;

        if (video.muted) {
        volume.setAttribute('data-volume', volume.value);
        volume.value = 0;
        } else {
        volume.value = volume.dataset.volume;
        }
        }

        當(dāng) volumeButton 被點(diǎn)擊后運(yùn)行該函數(shù):

              
              // index.js
        volumeButton.addEventListener('click', toggleMute);

        該函數(shù)切換視頻 muted 屬性的狀態(tài)為真或者假。當(dāng)視頻被靜音,音頻值就會(huì)存放在 volume 元素 data-volume 屬性上,以便當(dāng)視頻取消靜音時(shí),我們可以恢復(fù)音頻狀態(tài)之前的值。

        這里是實(shí)操效果:

        27df01cc5ba67dc52f7259c86a8b9d1d.webpvolume-muted-unmuted.gif

        點(diǎn)擊視頻播放或者暫停

        在很多視頻播放器應(yīng)用中,點(diǎn)擊視頻本身能夠快速進(jìn)行播放或者暫停,所以,在我們的播放器中也實(shí)現(xiàn)它。

        我們要做的就是監(jiān)聽 video 上的 click 事件,當(dāng)事件觸發(fā)就運(yùn)行 togglePlay 函數(shù):

              
              // index.js
        video.addEventListener('click', togglePlay);

        雖然這可行,但是讓我們通過在播放或者暫停視頻時(shí)添加一些反饋?zhàn)屵@更有趣,就像 YouTube 或者 Netflix 上一樣。

        這是我們動(dòng)畫的 HTML

              
              <!-- index.html -->
        . . .
        <div class="playback-animation" id="playback-animation">
        <svg class="playback-icons">
        <use class="hidden" href="#play-icon"></use>
        <use href="#pause"></use>
        </svg>
        </div>
        . . .

        下面是相關(guān)的 CSS

              
              // style.css
        .playback-animation {
        pointer-events: none;
        position: absolute;
        top: 50%;
        left: 50%;
        margin-left: -40px;
        margin-top: -40px;
        width: 80px;
        height: 80px;
        border-radius: 80px;
        background-color: rgba(0, 0, 0, 0.6);
        display: flex;
        justify-content: center;
        align-items: center;
        opacity: 0;
        }

        .playback-animation 元素通過 opacity 屬性,設(shè)置默認(rèn)值是透明色。為了復(fù)制 YouTube 中的動(dòng)效,我們將會(huì)使用 Web Animations API 來實(shí)現(xiàn)該元素透明度和縮放效果。

        index.js 文件頂部先選中該元素:

              
              // index.js
        const playbackAnimation = document.getElementById('playback-animation');

        然后在其他函數(shù)下創(chuàng)建下面的函數(shù):

              
              // index.js
        // animatePlayback displays an animation when
        // the video is played or paused
        function animatePlayback() {
        playbackAnimation.animate([
        {
        opacity: 1,
        transform: "scale(1)",
        },
        {
        opacity: 0,
        transform: "scale(1.3)",
        }], {
        duration: 500,
        });
        }

        animate 函數(shù)接受一個(gè)關(guān)鍵幀對(duì)象數(shù)組和一個(gè)控制動(dòng)畫時(shí)間等的可選對(duì)象。

        現(xiàn)在,為 video 元素添加第二個(gè) click 事件:

              
              // index.js
        video.addEventListener('click', animatePlayback);

        現(xiàn)在當(dāng)你點(diǎn)擊播放或者暫停視頻,可以看到簡(jiǎn)短的動(dòng)畫效果。

        90caebd77c6173317eaf9231335c98b3.webpplay-pause-video.gif

        視頻全屏

        接下來,我們實(shí)現(xiàn)全屏功能按鈕。為了讓視頻全屏(包括控制器),我們需要選擇 .video-container 元素,然后詢問瀏覽器去全屏放置它(及其子元素)。

        index.js 文件中選擇按鈕和視頻容器:

              
              // index.js
        const fullscreenButton = document.getElementById('fullscreen-button');
        const videoContainer = document.getElementById('video-container');

        然后創(chuàng)建一個(gè)新的名為 toggleFullScreen 函數(shù):

              
              // index.js
        // toggleFullScreen toggles the full screen state of the video
        // If the browser is currently in fullscreen mode,
        // then it should exit and vice versa.
        function toggleFullScreen() {
        if (document.fullscreenElement) {
        document.exitFullscreen();
        } else if (document.webkitFullscreenElement) {
        // Need this to support Safari
        document.webkitExitFullscreen();
        } else if (videoContainer.webkitRequestFullscreen) {
        // Need this to support Safari
        videoContainer.webkitRequestFullscreen();
        } else {
        videoContainer.requestFullscreen();
        }
        }

        然后,為 fullScreenButton 元素添加一個(gè) click 事件,如下:

              
              // index.js
        fullscreenButton.onclick = toggleFullScreen;

        toggleFullScreen 函數(shù)會(huì)先檢查 document 是否是全屏模式,如果是則退出到瀏覽器模式。否則,則將 videoContainer 元素放置在全屏。

        在該章節(jié),我們還要做的是當(dāng)鼠標(biāo)懸停在按鈕上更新全屏圖片和提示文本。首先,選擇圖標(biāo):

              
              // index.js
        const fullscreenIcons = fullscreenButton.querySelectorAll('use');

        然后創(chuàng)建一個(gè)函數(shù),當(dāng) videoContainer 進(jìn)行全屏或者退出全屏模式時(shí)候更新按鈕:

              
              // index.js
        // updateFullscreenButton changes the icon of the full screen button
        // and tooltip to reflect the current full screen state of the video
        function updateFullscreenButton() {
        fullscreenIcons.forEach(icon => icon.classList.toggle('hidden'));

        if (document.fullscreenElement) {
        fullscreenButton.setAttribute('data-title', 'Exit full screen (f)')
        } else {
        fullscreenButton.setAttribute('data-title', 'Full screen (f)')
        }
        }

        最后,為 videoContainer 元素分配 updateFullscreenButton 函數(shù)到 onfullscreenchange 事件處理器:

              
              // index.js
        videoContainer.addEventListener('fullscreenchange', updateFullscreenButton);

        嗯,它按預(yù)期工作!你可以在自己瀏覽器上測(cè)試或者看下面的 GIF 圖。

        5588b397f6e86aae7efbe8dccd672f2f.webpfull-screen-video.gif

        添加畫中畫支持

        Picture-in-Picture(PiP) API 允許用戶在浮動(dòng)窗口(其中位于其他窗口之上) 中觀看視頻,這樣他們就可以在觀看視頻的同時(shí)將注意力放在其他站點(diǎn)或者應(yīng)用上。

        到目前為止,這個(gè) API 只被少數(shù)瀏覽器支持,所以我們需要對(duì)不支持的瀏覽器隱藏該 PiP 按鈕,以便他們看不到使用不了的功能。

        d547296e8823ade3218c8103aff25c1f.webppicture-in-picture-ability.webp

        請(qǐng)參考 caniuse.com 獲取最新的表格信息。

        下面的代碼能幫我們實(shí)現(xiàn)該功能。在其他事件監(jiān)聽器下添加此代碼。

              
              // index.js
        document.addEventListener('DOMContentLoaded', () => {
        if (!('pictureInPictureEnabled' in document)) {
        pipButton.classList.add('hidden');
        }
        });

        正如本教程我們要做的那樣,我們先需要選中相關(guān)的控制器:

              
              // index.js
        const pipButton = document.getElementById('pip-button')

        然后創(chuàng)建切換 Picture-in-Picture 模式的函數(shù):

              
              // index.js
        // togglePip toggles Picture-in-Picture mode on the video
        async function togglePip() {
        try {
        if (video !== document.pictureInPictureElement) {
        pipButton.disabled = true;
        await video.requestPictureInPicture();
        } else {
        await document.exitPictureInPicture();
        }
        } catch (error) {
        console.error(error)
        } finally {
        pipButton.disabled = false;
        }
        }

        我創(chuàng)建了一個(gè)名為 togglePip 的異步函數(shù),以便我們可以在 requestPictureInPicture() 方法拒絕時(shí)捕獲到錯(cuò)誤,這可能由于多種原因?qū)е隆T谡鎸?shí)的應(yīng)用中,你可能想向用戶展示錯(cuò)誤信息,而不是打印到控制臺(tái)上。

        接著,在 pipButton 元素上添加 click 事件,然后添加 togglePip 函數(shù)到該事件處理器中。

              
              // index.js
        pipButton.addEventListener('click', togglePip);

        現(xiàn)在,添加 pipButton 應(yīng)該進(jìn)入或者退出畫中畫模式。你也可以通過點(diǎn)擊(畫中畫模式)右上角的關(guān)閉按鈕關(guān)閉 PiP 窗口。

        1dd27b9be506089a977a3a4ce05123b5.webpPiP-mode.gif

        切換視頻控件

        視頻控件會(huì)占用一些空間并阻擋用戶查看一些內(nèi)容。當(dāng)它們不被使用的時(shí)候?qū)⑵潆[藏起來比較好,然后當(dāng)鼠標(biāo)移動(dòng)到視頻上方再顯示它們。

        為了實(shí)現(xiàn)這個(gè)目標(biāo),我們編寫兩個(gè)函數(shù),如下:

              
              // index.js
        // hideControls hides the video controls when not in use
        // if the video is paused, the controls must remain visible
        function hideControls() {
        if (video.paused) {
        return;
        }

        videoControls.classList.add('hide');
        }

        // showControls displays the video controls
        function showControls() {
        videoControls.classList.remove('hide');
        }

        這里我們想做的就是,當(dāng)鼠標(biāo)離開視頻上方就隱藏控件。但是當(dāng)視頻停止播放的時(shí)候,我們確??丶偸钦故镜?,所以在 hideControls() 函數(shù)中添加條件判斷。

        為了實(shí)現(xiàn)這個(gè),我們將在 video 元素和 videoControls 元素上使用 onmouseenteronmouseleave 事件處理器,如下:

              
              // index.js
        video.addEventListener('mouseenter', showControls);
        video.addEventListener('mouseleave', hideControls);
        videoControls.addEventListener('mouseenter', showControls);
        videoControls.addEventListener('mouseleave', hideControls);

        添加鍵盤快捷鍵

        我們將添加到播放器的最后一個(gè)特性是使用快捷鍵控制視頻播放。實(shí)際上,就是當(dāng)我們按下特定的鍵時(shí),運(yùn)行我們指定函數(shù)的事情。我們將實(shí)現(xiàn)的快捷鍵如下:

        • k:播放或者暫停視頻
        • m:視頻靜音或者取消靜音
        • f:切換全屏
        • p:切換畫中畫模式

        這里我們要做的就是監(jiān)聽 documentkeyup 事件,檢測(cè)按下的快捷鍵并返回相關(guān)的函數(shù)。

              
              // index.js
        // keyboardShortcuts executes the relevant functions for
        // each supported shortcut key
        function keyboardShortcuts(event) {
        const { key } = event;
        switch(key) {
        case 'k':
        togglePlay();
        animatePlayback();
        if (video.paused) {
        showControls();
        } else {
        setTimeout(() => {
        hideControls();
        }, 2000);
        }
        break;
        case 'm':
        toggleMute();
        break;
        case 'f':
        toggleFullScreen();
        break;
        case 'p':
        togglePip();
        break;
        }
        }

        如上,一個(gè) switch 聲明被用來檢測(cè)哪個(gè)快捷鍵被按下,然后執(zhí)行相關(guān)的代碼。兩秒后調(diào)用 hideControl 函數(shù)的原因是模仿 YouTube 上的行為,當(dāng)使用快捷鍵播放視頻時(shí)候,控件不會(huì)立馬消失,而是有一個(gè)短暫的延時(shí)。

              
              // index.js
        document.addEventListener('keyup', keyboardShortcuts);

        總結(jié)

        改進(jìn)視頻播放器的方法還有很多,但是本教程篇幅已經(jīng)很長(zhǎng)了,所以我不得不在這里停下來。如果你對(duì)額外的功能感興趣,下面是些想法:

        • 添加對(duì)字幕的支持
        • 添加對(duì)播放速度的支持
        • 添加快速前進(jìn)或者倒放視頻的功能
        • 添加選擇視頻分辨率(720p, 480p, 360p, 240p)的功能

        我希望本教程對(duì)你有幫助。相關(guān)代碼 GitHub。

        Thanks for reading, and happy coding!

        參考

        原文地址 - https://freshman.tech/custom-html5-video/


        瀏覽 85
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            狠狠操综合 | 歪歪漫画韩漫 | 自拍超碰在线观看 | 久久夜色网 | 久草福利资源 | 国产麻豆成人A | 岳的乳罩蕾丝内裤 | 小毛片在线播放 | 鸡巴插逼网站 | 男男舌吻呻吟肉 |