<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 21641字,需瀏覽 44分鐘

           ·

          2023-05-09 13:44

          在網(wǎng)頁(yè)中觀看和分享視頻內(nèi)容是一個(gè)很常見(jiàn)的功能,多年來(lái),視頻嵌入網(wǎng)頁(yè)的方式發(fā)生了變化?,F(xiàn)在,我們?cè)诂F(xiàn)代瀏覽器中使用 <video> 標(biāo)簽就可以添加視頻文件到網(wǎng)頁(yè)上,該標(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 來(lái)提升默認(rèn)設(shè)置的體驗(yàn)。

          我們將在本教程中構(gòu)建一個(gè)看起來(lái)像 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)樵诒疚木帉?xiě)時(shí),我們將添加的一些功能(比如畫(huà)中畫(huà)功能)僅適用于谷歌(Webkit 內(nèi)核)瀏覽器。

          開(kāi)始

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

          在終端中運(yùn)行 npm install 來(lái)安裝 browser-sync 作為啟動(dòng) Web 服務(wù)器的開(kāi)發(fā)依賴(lài)項(xiàng),其在任何文件更改時(shí)自動(dòng)刷新頁(yè)面。然后 npm start 啟動(dòng)項(xiàng)目,監(jiān)聽(tīng)瀏覽器 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 類(lèi)型的視頻源文件,因?yàn)樵擃?lèi)型的視頻被所有主流瀏覽器兼容,是一個(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 文件中輸入下面代碼片段來(lái)實(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ǔ)開(kāi)始。我們需要通過(guò)點(diǎn)擊播放按鈕來(lái)播放或者暫停視頻,并且更改應(yīng)該匹配視頻狀態(tài)的圖標(biāo)。我們從獲取視頻和播放按鈕開(kāi)始,代碼在 index.js 頂部,如下:

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

          然后,我們創(chuàng)建一個(gè)函數(shù)來(lái)切換視頻播放狀態(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
          // 如果視頻播放是暫?;蛘咭曨l結(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)聽(tīng)器,當(dāng) playButton 按鈕被點(diǎn)擊后執(zhí)行 togglePlay 方法。

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

          夠簡(jiǎn)單吧?通過(guò)點(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ù),通過(guò)事件監(jiān)聽(tīng)器將其連接起來(lái)。

          我們繼續(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 類(lèi),以便根據(jù)視頻的狀態(tài)展示正確的圖標(biāo)。

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

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

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

                
                // 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)聽(tīng)器:

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

          當(dāng)視頻播放或者暫停時(shí),updatePlayButton 函數(shù)都會(huì)被執(zhí)行,切換每個(gè)按鈕中的 hidden 類(lèi)。因?yàn)闀和0粹o元素默認(rèn)值是 hidden 類(lèi),一旦視頻被播放,這個(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 是我們將在本教程后面添加播放或者暫停視頻的鍵盤(pán)快捷鍵。

          如下,更新 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)過(guò)時(shí)間

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

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

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

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

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

          一旦頁(yè)面加載完成后,我們將使用 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)聽(tīng)器上。當(dāng)元數(shù)據(jù)被加載之后,將會(huì)更新視頻的持續(xù)時(shí)長(zhǎng)。

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

          同理,當(dāng)視頻播放過(guò)程中,我們更新播放經(jīng)過(guò)的時(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)聽(tīng)視頻。無(wú)論什么時(shí)候,視頻的 currentTime 屬性值更新了,事件就會(huì)觸發(fā)。

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

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

          68fb2a6a25cdc5da7a7f819fb369ac04.webpcurrent-time-update.gif

          更新進(jìn)度條

          接下來(lái)我們要做的事情是當(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 類(lèi)型的 input 允許我們快速無(wú)縫瀏覽視頻。兩個(gè)元素我都用同個(gè)樣式修飾,所以它們有一樣的寬高,但是 input 是透明色(除了與進(jìn)度條內(nèi)相同的顏色的指示點(diǎn))。

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

          兩者的 min 屬性被設(shè)置為 0,兩者的 value 屬性指向當(dāng)前時(shí)間值。它們還需要一個(gè) max 屬性,該屬性將設(shè)置為視頻的持續(xù)時(shí)間(以秒為單位),該屬性值來(lái)自 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)聽(tīng)器下,為 video 添加一個(gè)新的名為 timeupdate 事件監(jiān)聽(tīng)器:

                
                // 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ù),用來(lái)當(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 來(lái)查看效果:

                
                // 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 的值并檢查其是否有效。如果有效,我們獲取該值并更新視頻播放過(guò)的時(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)。

          正如你所見(jiàn),音頻的輸入范圍是 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)起來(lái),如下:

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

          到這里,你將意識(shí)到當(dāng)你左滑輸入框時(shí),音量減少,反之音量增加。我們需要添加另一個(gè)函數(shù)來(lái)在音量變化時(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)。

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

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

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

          41e33d50dc10c14c30eda330c383be7b.webpvolume-change.gif

          我們需要添加的另一個(gè)事件是能夠通過(guò)單擊音量圖標(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)聽(tīng) video 上的 click 事件,當(dāng)事件觸發(fā)就運(yùn)行 togglePlay 函數(shù):

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

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

          這是我們動(dòng)畫(huà)的 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 元素通過(guò) opacity 屬性,設(shè)置默認(rèn)值是透明色。為了復(fù)制 YouTube 中的動(dòng)效,我們將會(huì)使用 Web Animations API 來(lái)實(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)畫(huà)時(shí)間等的可選對(duì)象。

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

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

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

          90caebd77c6173317eaf9231335c98b3.webpplay-pause-video.gif

          視頻全屏

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

          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

          添加畫(huà)中畫(huà)支持

          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)聽(tīng)器下添加此代碼。

                
                // 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ū)е?。在真?shí)的應(yīng)用中,你可能想向用戶展示錯(cuò)誤信息,而不是打印到控制臺(tái)上。

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

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

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

          1dd27b9be506089a977a3a4ce05123b5.webpPiP-mode.gif

          切換視頻控件

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

          為了實(shí)現(xiàn)這個(gè)目標(biāo),我們編寫(xiě)兩個(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)離開(kāi)視頻上方就隱藏控件。但是當(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);

          添加鍵盤(pán)快捷鍵

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

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

          這里我們要做的就是監(jiān)聽(tīng) 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 聲明被用來(lái)檢測(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)了,所以我不得不在這里停下來(lái)。如果你對(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)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲成人资源网 | 国产激情综合AV | 在线一区二区三区四区五区 | 无码三级乱伦 | 日韩一级免费播放 |