翻译正在进行中。

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';

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

播放和暂停视频

Let's implement probably the most important control — the play/pause button.

  1. First of all, add the following to the bottom of your code, so that the playPauseMedia() function is invoked when the play button is clicked:

    play.addEventListener('click', playPauseMedia);
    
  2. Now to define playPauseMedia() — add the following, again at the bottom of your code:

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

    He we use an if statement to check whether the video is paused. The HTMLMediaElement.paused property returns true if the media is paused, which is any time the video is not playing, including when it is sat at 0 duration after it first loads. If it is paused, we set the data-icon attribute value on the play button to "u", which is a "paused" icon, and invoke the HTMLMediaElement.play() method to play the media.

    On the second click, the button will be toggled back again — the "play" icon will be shown again, and the video will be paused with HTMLMediaElement.paused().

Stopping the video

  1. Next, let's add functionality to handle stopping the video. Add the following addEventListener() lines below the previous one you added:

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

    The click event is obvious — we want to stop the video by running our stopMedia() function when the stop button is clicked. We do however also want to stop the video when it finishes playing — this is marked by the ended event firing, so we also set up a listener to run the function on that event firing too.

  2. Next, let's define stopMedia() — add the following function below playPauseMedia():

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

    there is no stop() method on the HTMLMediaElement API — the equivalent is to pause() the video, and set its currentTime property to 0. Setting currentTime to a value (in seconds) immediately jumps the media to that position.

    All there is left to do after that is to set the displayed icon to the "play" icon. Regardless of whether the video was paused or playing when the stop button is pressed, you want it to be ready to play afterwards.

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.

Summary

I think we've taught you enough in this article. The HTMLMediaElement API makes a wealth of functionality available for creating simple video and audio players, and that's only the tip of the iceberg. See the "See also" section below for links to more complex and interesting functionality.

Here are some suggestions for ways you could enhance the existing example we've built up:

  1. The time display currently breaks if the video is an hour long or more (well, it won't display hours; just minutes and seconds). Can you figure out how to change the example to make it display hours?

  2. Because <audio> elements have the same HTMLMediaElement functionality available to them, you could easily get this player to work for an <audio> element too. Try doing so.

  3. Can you work out a way to turn the timer inner <div> element into a true seek bar/scrobbler — i.e., when you click somewhere on the bar, it jumps to that relative position in the video playback? As a hint, you can find out the X and Y values of the element's left/right and top/bottom sides via the getBoundingClientRect() method, and you can find the coordinates of a mouse click via the event object of the click event, called on the Document object. For example:

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

See also

 

In this module

文档标签和贡献者

此页面的贡献者: kuldahar, FEpangxing, peanutguo, CaoBingX
最后编辑者: kuldahar,