这篇翻译不完整。请帮忙从英语翻译这篇文章

HTML5提供了用于在文档中嵌入富媒体的元素 — <video><audio> — 这些元素通过自带的API来控制视频或音频的播放,搜索等。本文将向你展示如何执行一些常见的任务,如创建自定义播放控件。

前提: JavaScript基础(见JavaScript第一步创建JavaScript代码块JavaScript对象入门),Web API简介
目标: 学习如何通过浏览器API来控制视频和音频的播放。

HTML5视频和音频

<video><audio>元素允许我们把视频和音频嵌入到网页当中。就像我们在音频和视频内容文中展示的一样,一个典型的实现如下所示:

<video controls>
  <source src="rabbit320.mp4" type="video/mp4">
  <source src="rabbit320.webm" type="video/webm">
  <p>Your browser doesn't support HTML5 video. Here is a <a href="rabbit320.mp4">link to the video</a> instead.</p>
</video>

上述代码将会在浏览器内部创建一个如下图所示的视频播放器:

你可以点击上面的文章链接来查看相关HTML元素的所有特性;但在这篇文章中,主要目的是学习我们最感兴趣的controls属性,它会启用默认的播放设置。如果没有指定该属性,则播放器中不会显示相关控件:

至此,你可能会觉得这个属性作用不大,但是它确实很有优势。使用原生浏览器控件的一个很大的问题在于,它们在各个浏览器中都不相同 — 对于跨浏览器的支持并不是很好!另一个问题是,在大多数浏览器中原生控件难以通过键盘来操作。

你可以通过隐藏本地控件(通过删除controls属性),然后使用HTML,CSS和JavaScript编写自己的代码来解决这两个问题。 在下一节中,我们将看到如何通过一些可用的工具来实现。

HTMLMediaElement API

作为HTML5规范的一部分,HTMLMediaElement API提供允许你以编程方式来控制视频和音频播放的功能—例如 HTMLMediaElement.play(), HTMLMediaElement.pause(),等。该接口对<audio><video>两个元素都是可用的,因为在这两个元素中要实现的功能几乎是相同的。让我们通过一个例子来一步步演示一些功能。

我们最终的示例(和功能)将会如下所示:

着手开始

想要使用这个示例的代码,请下载media-player-start.zip 并解压到相应目录下。如果想要下载examples repo,它位于javascript/apis/video-audio/start/ 路径下。

下载并解压之后,加载HTML代码,你将会看到一个通过浏览器原生播放控件渲染的HTML5视频播放器。

探索 HTML

打开HTML index文件。你将看到一些功能;HTML由视频播放器和它的控件所控制:

<div class="player">
  <video controls>
    <source src="video/sintel-short.mp4" type="video/mp4">
    <source src="video/sintel-short.mp4" type="video/webm">
    <!-- fallback content here -->
  </video>
  <div class="controls">
    <button class="play" data-icon="P" aria-label="play pause toggle"></button>
    <button class="stop" data-icon="S" aria-label="stop"></button>
    <div class="timer">
      <div></div>
      <span aria-label="timer">00:00</span>
    </div>
    <button class="rwd" data-icon="B" aria-label="rewind"></button>
    <button class="fwd" data-icon="F" aria-label="fast forward"></button>
  </div>
</div>
  • 播放器被嵌入在<div>元素之中,所以如果有需要的话,可以把它作为一个单元整体来设置其样式。
  • <video>元素层包含两个<source>元素,这样可以根据浏览器来加载其所支持的视频格式。
  • 这些控制的 HTML 大概是最有趣的:
    • 我们有四个 <button> — play/pause, stop, rewind, and fast forward.
    • 每个<button> 都有一个class , 一个data-icon 属性来决定在每个按钮上显示什么 (在下一节讲述他是如何工作的), 和一个aria-label 属性为每一个按钮提供容易理解的描述, 即使我们没有在tags内提供可读的标签。当用户关注这些元素时含有aria-label 属性的内容也会被讲述人读出来。
    • 有一个设定的计时器/进度条 <div>用来记录已经播放的时长。为了展示效果, 提供了两个记录的装置 — 一个 <span> 包含了分钟和秒,和一个额外的<div> 用来创建一个垂直的随着时间增加而增长的进度条。 完成版本看上去是这样的, 点击查看完成版本.

探索 CSS

现在打开CSS文件来查看里面的内容。例子中的CSS样式并不是很复杂, 我们突出了最主要的一部分。首先注意.controls 的样式:

.controls {
  visibility: hidden;
  opacity: 0.5;
  width: 400px;
  border-radius: 10px;
  position: absolute;
  bottom: 20px;
  left: 50%;
  margin-left: -200px;
  background-color: black;
  box-shadow: 3px 3px 5px black;
  transition: 1s all;
  display: flex;
}

.player:hover .controls, player:focus .controls {
  opacity: 1;
}
  • 我们从设置为hidden的自定义控件visibility开始。稍后在我们的JavaScript中, 我们将控件设置为 visible, 并且从<video> 元素中移除controls 属性。这是因为, 如果JavaScript由于某种原因没有加载, 用户依然可以使用原生的控件播放视频。
  • 默认情况下,我们将控件的opacity设置为0.5 opacity,这样当您尝试观看视频时,它们就不会分散注意力。 只有当您将鼠标悬停/聚焦在播放器上时,控件才会完全不透明。
  • 我们使用Flexbox(display: flex)布置控制栏内的按钮,以简化操作。

接下来,让我们看看我们的按钮图标:

@font-face {
   font-family: 'HeydingsControlsRegular';
   src: url('fonts/heydings_controls-webfont.eot');
   src: url('fonts/heydings_controls-webfont.eot?#iefix') format('embedded-opentype'),
        url('fonts/heydings_controls-webfont.woff') format('woff'),
        url('fonts/heydings_controls-webfont.ttf') format('truetype');
   font-weight: normal;
   font-style: normal;
}

button:before {
  font-family: HeydingsControlsRegular;
  font-size: 20px;
  position: relative;
  content: attr(data-icon);
  color: #aaa;
  text-shadow: 1px 1px 0px black;
}

首先在CSS的最上方我们使用 @font-face 块来导入自定义Web字体。这是一种图标字体 —— 字母表中的所有字符都是各种常用图标,你可以尝试在程序中调用。

接下来,我们使用这些内容来显示每个按钮上的图标:

  • 我们使用 ::before 选择器在每个 <button> 元素之前显示内容。
  • 我们使用 content 属性将各情况下要显示的内容设置为 data-icon 属性的内容。例如在播放按钮的情况下,data-icon 包含大写的“P”。
  • 我们使用 font-family 将自定义Web字体应用于我们的按钮上。在该字体中“P”对应的是“播放”图标,因此播放按钮上显示“播放”图标。

图标字体非常酷有很多原因 —— 减少HTTP请求,因为您不需要将这些图标作为图像文件下载。同时具有出色的可扩展性,以及您可以使用文本属性来设置它们的样式 —— 例如 colortext-shadow

最后让我们来看看进度条的 CSS:

.timer {
  line-height: 38px;
  font-size: 10px;
  font-family: monospace;
  text-shadow: 1px 1px 0px black;
  color: white;
  flex: 5;
  position: relative;
}

.timer div {
  position: absolute;
  background-color: rgba(255,255,255,0.2);
  left: 0;
  top: 0;
  width: 0;
  height: 38px;
  z-index: 2;
}

.timer span {
  position: absolute;
  z-index: 3;
  left: 19px;
}
  • 我们将外部 .timer  <div> 设为flex:5,这样它占据了控件栏的大部分宽度。 我们还设置 position: relative,这样我们就可以根据它的边界方便地定位元素,而不是<body> 元素的边界。
  • 内部 <div>  position:absolute 绝对定位于外部 <div> 的顶部。 它的初始宽度为0,因此根本无法看到它。随着视频的播放,JavaScript将动态的增加其宽度。
  • <span> 也绝对位于计时器/进度条 timer 栏的左侧附近。
  • 我们还对内部 <div><span> 定义适当数值的 z-index ,以便进度条显示在最上层,内部 <div> 显示在下层。 这样,我们确保我们可以看到所有信息 —— 一个box不会遮挡另一个。

实现 JavaScript

 

我们已经有了一个相当完整的 HTML 和CSS 接口;现在我们只需要调通所有按钮以使控件正常工作。

  1. 在与 index.html 文件相同的目录下创建新的JavaScript文件。命名为 custom-player.js

  2. 在此文件的顶部,插入以下代码:

    var media = document.querySelector('video');
    var controls = document.querySelector('.controls');
    
    var play = document.querySelector('.play');
    var stop = document.querySelector('.stop');
    var rwd = document.querySelector('.rwd');
    var fwd = document.querySelector('.fwd');
    
    var timerWrapper = document.querySelector('.timer');
    var timer = document.querySelector('.timer span');
    var timerBar = document.querySelector('.timer div');
    

    这里我们创建变量来保存对我们想要操作的所有对象的引用。有如下三组:

    •  <video> 元素,和控制栏。
    • 播放/暂停,停止,快退,和快进按钮。
    • 进度条外面的 <div>,数字计时器的 <span>,以及内部的 <div> 会随着视频播放逐渐变宽。
  3. 接下来,在代码的底部插入以下内容:

    media.removeAttribute('controls');
    controls.style.visibility = 'visible';

    这两行从视频中删除默认浏览器控件,并使自定义控件可见。

播放和暂停视频

让我们实现或许是最重要的控制——播放/暂停按钮。

  1. 首先,将以下内容添加到您代码的底部,以便于在单击播放按钮时调用 playPauseMedia()函数:

    play.addEventListener('click', playPauseMedia);
    
  2. 现在定义 playPauseMedia() 函数——再次添加以下内容到您代码底部:

    function playPauseMedia() {
      if(media.paused) {
        play.setAttribute('data-icon','u');
        media.play();
      } else {
        play.setAttribute('data-icon','P');
        media.pause();
      }
    }

    我们使用if语句来检查视频是否暂停。如果视频已暂停,HTMLMediaElement.paused 属性将返回 true,任何视频没有播放的时间,包括第一次加载时处于0的时间段都是视频暂停状态。如果已暂停,我们把play按钮的 data-icon 属性值设置成"u", 用以表示 "暂停" 按钮图标,并且调用HTMLMediaElement.play() 函数播放视频。

    点击第二次,按钮将会切换回去——"播放"按钮图标将会再次显示,并且视频将会被HTMLMediaElement.paused() 函数暂停。

暂停视频

  1. 接下来,让我们添加处理视频停止的方法。添加以下的 addEventListener() 行在你之前添加的内容的下面:

    stop.addEventListener('click', stopMedia);
    media.addEventListener('ended', stopMedia);
    

    click 事件很明显——我们想要在点击停止按钮的时候停止视频通过运行我们的 stopMedia() 函数。然而我们也希望停止视频当视频播放完成时——由ended 事件标记,所以我们也会设置一个监听器在此事件触发时运行函数。

  2. 接下来,让我们定义 stopMedia()—— 在 playPauseMedia() 后面添加以下函数:

    function stopMedia() {
      media.pause();
      media.currentTime = 0;
      play.setAttribute('data-icon','P');
    }
    

    在 HTMLMediaElement API 中没有 stop() 方法——等效的办法是先用 pause() 暂停视频,然后设置currentTime 属性为0。设置 currentTime 的值(单位:秒)将会立刻使视频跳到该位置。

    之后要做的事是把显示的图标设置成“播放”图标。无论视频使暂停还是正在播放,您都希望它随后可以播放。

Seeking back and forth

There are many ways that you can implement rewind and fast forward functionality; here we are showing you a relatively complex way of doing it, which doesn't break when the different buttons are pressed in an unexpected order.

  1. First of all, add the following two addEventListener() lines below the previous ones:

    rwd.addEventListener('click', mediaBackward);
    fwd.addEventListener('click', mediaForward);
    
  2. Now on to the event handler functions — add the following code below your previous functions to define mediaBackward() and mediaForward():

    var intervalFwd;
    var intervalRwd;
    
    function mediaBackward() {
      clearInterval(intervalFwd);
      fwd.classList.remove('active');
    
      if(rwd.classList.contains('active')) {
        rwd.classList.remove('active');
        clearInterval(intervalRwd);
        media.play();
      } else {
        rwd.classList.add('active');
        media.pause();
        intervalRwd = setInterval(windBackward, 200);
      }
    }
    
    function mediaForward() {
      clearInterval(intervalRwd);
      rwd.classList.remove('active');
    
      if(fwd.classList.contains('active')) {
        fwd.classList.remove('active');
        clearInterval(intervalFwd);
        media.play();
      } else {
        fwd.classList.add('active');
        media.pause();
        intervalFwd = setInterval(windForward, 200);
      }
    }
    

    You'll notice that first we initialize two variables — intervalFwd and intervalRwd — you'll find out what they are for later on.

    Let's step through mediaBackward() (the functionality for mediaForward() is exactly the same, but in reverse):

    1. We clear any classes and intervals that are set on the fast forward functionality — we do this because if we press the rwd button after pressing the fwd button, we want to cancel any fast forward functionality and replace it with the rewind functionality. If we tried to do both at one, the player would break.
    2. We use an if statement to check whether the active class has been set on the rwd button, indicating that it has already been pressed. The classList is a rather handy property that exists on every element — it contains a list of all the classes set on the element, as well as methods for adding/removing classes, etc. We use the classList.contains() method to check whether the list contains the active class. This returns a boolean true/false result.
    3. If active has been set on the rwd button, we remove it using classList.remove(), clear the interval that has been set when the button was first pressed (see below for more explanation), and use HTMLMediaElement.play() to cancel the rewind and start the video playing normally.
    4. If it hasn't yet been set, we add the active class to the rwd button using classList.add(), pause the video using HTMLMediaElement.pause(), then set the intervalRwd variable to equal a setInterval() call. When invoked, setInterval() creates an active interval, meaning that it runs the function given as the first parameter every x milliseconds, where x is the value of the 2nd parameter. So here we are running the windBackward() function every 200 milliseconds — we'll use this function to wind the video backwards constantly. To stop a setInterval() running, you have to call clearInterval(), giving it the identifying name of the interval to clear, which in this case is the variable name intervalRwd (see the clearInterval() call earlier on in the function).
  3. last of all for this section, we need to define the windBackward() and windForward() functions invoked in the setInterval() calls. Add the following below your two previous functions:

    function windBackward() {
      if(media.currentTime <= 3) {
        rwd.classList.remove('active');
        clearInterval(intervalRwd);
        stopMedia();
      } else {
        media.currentTime -= 3;
      }
    }
    
    function windForward() {
      if(media.currentTime >= media.duration - 3) {
        fwd.classList.remove('active');
        clearInterval(intervalFwd);
        stopMedia();
      } else {
        media.currentTime += 3;
      }
    }

    Again, we'll just run through the first one of these functions as they work almost identically, but in reverse to one another. In windBackward() we do the following — bear in mind that when the interval is active, this function is being run once every 200 milliseconds.

    1. We start off with an if statement that checks to see whether the current time is less than 3 seconds, i.e., if rewinding by another three seconds would take it back past the start of the video. This would cause strange behaviour, so if this is the case we stop the video playing by calling stopMedia(), remove the active class from the rewind button, and clear the intervalRwd interval to stop the rewind functionality. If we didn't do this last step, the video would just keep rewinding forever.
    2. If the current time is not within 3 seconds of the start of the video, we simply remove three seconds from the current time by executing media.currentTime -= 3. So in effect, we are rewinding the video by 3 seconds, once every 200 milliseconds.

Updating the elapsed time

The very last piece of our media player to implement is the time elapsed displays. To do this we'll run a function to update the time displays every time the timeupdate event is fired on the <video> element. This frequency with which this event fires depends on your browser, CPU power, etc (see this stackoverflow post).

Add the following addEventListener() line just below the others:

media.addEventListener('timeupdate', setTime);

Now to define the setTime() function. Add the following at the bottom of your file:

function setTime() {
  var minutes = Math.floor(media.currentTime / 60);
  var seconds = Math.floor(media.currentTime - minutes * 60);
  var minuteValue;
  var secondValue;

  if (minutes < 10) {
    minuteValue = '0' + minutes;
  } else {
    minuteValue = minutes;
  }

  if (seconds < 10) {
    secondValue = '0' + seconds;
  } else {
    secondValue = seconds;
  }

  var mediaTime = minuteValue + ':' + secondValue;
  timer.textContent = mediaTime;

  var barLength = timerWrapper.clientWidth * (media.currentTime/media.duration);
  timerBar.style.width = barLength + 'px';
}

This is a fairly long function, so let's go through it step by step:

  1. First of all, we work out the number of minutes and seconds in the HTMLMediaElement.currentTime value.
  2. Then we initialize two more variables — minuteValue and secondValue.
  3. The two if statements work out whether the number of minutes and seconds are less than 10. If so, they add a leading zero to the values, in the same way that a digital clock display works.
  4. The actual time value to display is set as minuteValue plus a colon character plus secondValue.
  5. The Node.textContent value of the timer is set to the time value, so it displays in the UI.
  6. The length we should set the inner <div> to is worked out by first working out the width of the outer <div> (any element's clientWidth property will contain its length), and then multiplying it by the HTMLMediaElement.currentTime divided by the total HTMLMediaElement.duration of the media.
  7. We set the width of the inner <div> to equal the calculated bar length, plus "px", so it will be set to that number of pixels.

Fixing play and pause

There is one problem left to fix. If the play/pause or stop buttons are pressed while the rewind or fast forward functionality is active, they just don't work. How can we fix it so that they cancel the rwd/fwd button functionality and play/stop the video as you'd expect? This is fairly easy to fix.

First of all, add the following lines inside the stopMedia() function — anywhere will do:

rwd.classList.remove('active');
fwd.classList.remove('active');
clearInterval(intervalRwd);
clearInterval(intervalFwd);

Now add the same lines again, at the very start of the playPauseMedia() function (just before the start of the if statement).

At this point, you could delete the equivalent lines from the windBackward() and windForward() functions, as that functionality has been implemented in the stopMedia() function instead.

Note: You could also further improve the efficiency of the code by creating a separate function that runs these lines, then calling that anywhere it is needed, rather than repeating the lines multiple times in the code. But we'll leave that one up to you.

小结

我想我们已经在这篇文章中教过你足够多了。使用HTMLMediaElement API可以为创建简单的视频和音频播放器提供丰富的可用功能,然而这只是冰山一角。 有关更复杂和有趣功能的链接,请参阅下面的“另请参阅”部分。

以下是一些有关如何增强我们构建的现有示例的建议:

  1. 如果视频是一小时或更长时间(嗯,它不会显示小时;只有几分钟和几秒),当前显示时间会中断。 你能弄清楚如何更改示例以使其显示小时数吗?
  2. 由于 <audio> 元素具有相同的HTMLMediaElement功能,因此您可以轻松地将此播放器用于 <audio> 元素。 试着这样做。

  3. 你能找到一种方法将计时器内部的 <div> 元素转换为真正的搜索条/ 滑动条- 也就是说,当你点击条形图上的某个位置时,它会跳转到视频播放中的相对位置吗? 作为提示,您可以通过getBoundingClientRect() 方法找出元素左/右和上/下侧的X和Y值 , 而且你可以通过 Document 对象调用的click事件的事件对象找到鼠标单击的坐标。举个栗子:

    document.onclick = function(e) {
      console.log(e.x) + ',' + console.log(e.y)
    }

另请参阅

 

本章目录

文档标签和贡献者

最后编辑者: yinpeng123,