preface

In recent weeks, WE have finished the revision of the live broadcast page and video playing page of Kukule University. Some discussions and reflections in the process are worth recording. This paper will introduce the process of implementing a Web video player.

Tip 1: This article will not focus on the implementation details of the Web video player. Tip 2: This article will talk about the Web video player, does not involve the underlying implementation, or about the video format, bit rate and other knowledge, is only a pure HTML5 Web side video component. Tip 3: the first time to write, some words may be inappropriate expression, welcome to point out.

The video player plug-in used by the old video player page of Kuga is Video.js, which has a large number of users and has very good scalability. Video.js also provides support for Flash player, which can switch between Flash and HTML5 video player according to the browser version. Making under the low version of the browser can also obtain consistent play experience and advanced browser, and it’s plug-in is very much, can say what you want function can be solved by using a plug-in to commonly, but because of its function is very powerful, it became the reason why I don’t use it, on the one hand, video, js, though strong, In addition, in order to satisfy your designer friends, you have to modify its style, and the style of video.js is not that flexible. On the other hand, as a front-end with pursuit, why not develop a video player by myself?

Two years ago, I would never have said that I was going to write a Web video player from scratch for two reasons. One is that two years ago, you had to use Flash in a production environment to accommodate older versions of Internet Explorer. Another reason is that two years ago I didn’t know anything about front-end. This joke may be cold, but the real point is that it’s not that hard to develop a pure HTML5 Web video player in a modern browser. And our business just needs to be compatible with IE11 above the modern browser, so we can not consider Flash, save a lot of things!

In addition, in recent days, if you visit several famous video websites in China, you may find that in addition to the Flash version of the browser, they will also provide pure HTML5 version of the video player, such as Tencent Video, Youku and Bilibili, etc. In particular, Bilibili also open source flv.js. A JavaScript library that uses Media Source Extensions to play FLV videos in HTML5 video, making FLV videos available to play on native HTML5 video players. I used this library when I did my Kool live page. Looking at the DOM structure of THE HTML5 video player of the above websites, it will be found that in addition to the video element itself is provided by the browser native, other essential function modules on the control bar of the player are realized by DOM simulation, such as progress bar, volume selection, etc. All of these features are already available in native video players, but they vary from browser to browser. Look like this :(chrome)

Like this: (edge)

And this: (Firefox)

In general, native video players are not used in production environments for a consistent user experience and functionality extension, so we use DOM to simulate these functionality modules.

To sum up: What we need to do to achieve a video player is to use the native video element as the playback carrier, and then write a set of control UI for it to control the state of the video element. Let’s use a complete player example to illustrate this process.

Begin to implement

Let’s take a look at the final result:

I’m calling it KuPlayer. As mentioned earlier, KuPlayer uses the native video element like some common HTML5 players, but without controls, which would cause the browser to simply provide a video player without a control bar. We then used DOM to simulate the player control bar. The KuPlayer control bar consists of the following features:

  1. Play/Pause
  2. Video duration display
  3. The volume control
  4. Progress bar part – play loading display and progress display
  5. Resolution selection
  6. Full screen control

First, take a look at the rough DOM structure:

Refer to the DOM structure above for a brief introduction to these DOM elements and their capabilities.

  1. Kp-media is the video element itself,
  2. kp-controlsIs the parent element of the control bar for which the child element corresponds to the function of the control bar.
  • Kp-play and KP-Pause are buttons that play and pause video, respectively.
  • Kp-progress indicates a progress bar
  • Kp-time Specifies the kp-time
  • Kp-volume controls the video volume
  • Kp-resolution switches for resolution
  • How the DOM structure of KP-Full and KP-CancelFull is organized depends entirely on the functionality required by the business. This is just a basic example.

I’ll start with the basics and show you how some of the features are implemented, such as how to control play and pause, how to change the volume of a video, how to achieve full screen, and how to display the correct progress bar (both play and buffer). Some of the other functions are implemented in much the same way and will not be described in detail.

Let’s talk about basic styles first

For the video element, its raw size is the size of the video it is playing. Its visual image is always scaled to the size of the video it is playing, similar to setting the background image of an element to background-size: Cover, so if the video element is set to width:100% and height:100%, it will not be stretched as expected. Instead, it will behave in its original proportion. Set width:100%, Height :100% width:100%, height:auto

As you can see, even if set to Width :100%, height:100%, it still shows the scale of the original video, and the browser automatically gives the invisible area of the video a pure black background, instead of being overly stretched as I had suspected. Looking back at the DOM structure, you can see that there are only three elements in the player DOM: Div, Button, and Input. Div is the wrapper around modules, Button is the play and pause Button, and Input is what?

Everyone knows that as an on-demand player, there should be a progress bar that shows and selects progress, so there should be an element that can be dragged and then bound to events that control the progress and state of the player. The typical progress bar that designers throw at us is something like this:

A tracklike bar with a slider that you can drag and change the current playback progress. I’m lazy, so I can’t even write a drag widget. Is there a native DOM element I can use? The answer is yes, just set the Input element to (type=’range’). Let’s take a look at what the Input Range element looks like in a browser. Let’s use Chrome as an example. (All subsequent examples are Chrome.) An all-natural Input Range element would look like this in a browser:

Apart from being a little ugly, it seems to meet my requirements, so is there any way to change the style? After some searching and trying, it was transformed into this in my hands! The slider that turns blue at the bottom is the hover state.

The method is simple, in fact, to modify its track and thumb respectively, using the pseudo-class element is

:-webkit-slider-runnable-track and :-webkit-slider-thumb are similar to modifying the browser’s native scroll bar style, but surprisingly, browsers are pretty compatible with these attributes. However, you may have to write similar pseudo-classes for each browser. If you use SCSS or less, mixin is convenient. As shown below:

Note -webkit-appearance: None is an important statement that will cause the browser to abandon its native style. This article explains it in detail. There are some weird differences between browsers, like chrome’s slider-thumb margin-top must be set to -4px. Otherwise, the slider will always be offset by 4px. This is probably a browser joke, but it often annoys front-end engineers. There are many articles on the web that modify the style of the Input Range elements, which I won’t list here. So a rumbling down, style is beautiful, but looks like there is no direct div was used to simulate a sliding control to simple, actually otherwise, using pseudo class element adapted to modify the browser is really tedious, but it’s native browser offers developers, use native natural many manual emulation to god’s truth than himself, It’s just that there’s still a lot of variability in browsers, but it’s a vision that one day, if we don’t have to write so many pseudo-classes for each browser, we can spend our energy on better things to do…

How to write, some of the other elements of the CSS is varied from visual draft, if as a standalone player component, of course, some good behavior in the process of development can make follow-up up more convenient, such as the class name of the reasonable, concise DOM level, reasonable DOM elements, combined with the necessary semantic and so on, Of course, there are still some aspects of KuPlayer that need to be improved in this respect, please give your suggestions.

Let’s talk about functional implementation

First of all, open MDN and search the video element. You will see that there are so many attributes and events that belong to the video element alone. There are as many as 23 events! For example, abort, canplay, ended, error, and Loadedmetadata. These events are internal events of the video element, and all its objects refer to the video itself that it is playing. Front-end engineers who want to control its playback and pause or other states cannot directly call its internal events but can listen for them to achieve special effects, such as error and passable events that occur and then tell users: “Sorry, your network condition is not good, please refresh and try again!” .

As a front-end looking to save a lot of trouble, the best implementation I can think of is to take advantage of these events, and that’s really all you can do.

Start by creating a class for KuPlayer

class KuPlayer {}
Copy the code

How to play and pause video

The Vide1o element provides a number of methods for developers to control its state. For example, the Play and pause methods control the playback and pause of the video. If these two methods are called, the play and pause events within the video element will also be triggered. The playback state of the video also changes, for example, when pause() is called, the paused property of the video element fetched at that time becomes true. So we can write this (code for reference only)

Class KuPlayer {constructor(){// First get the media object that refers to the video element itself. this.media = document.querySelector('.J_kpMedia'); } // Then the play and pause methods. Play (){// implement the native play() method this.media.play(); UI} pause(){// Executes the native pause() method this.media.pause(); // Display player pause state UI}}Copy the code

Just as I was testing these two seemingly simple features, Chrome surprised me with this error in Console.

I discovered this during a quick test of the player’s play and pause, followed by the url it suggested, and found that Chrome had already told us why it appeared.

This.media.play () returns a Promise! If the Promise function is successfully executed, the video will play and the video element will execute the playing event. The reason for this error is that The this.media.pause() method will be called when the Promise returned by this.media.play() is resolved and rejected, and the video itself will not be played. So prompted the mistakes in the console, and, in fact, call pause () method is not the only way to end this process, as is to change the current broadcast schedule or replace the video SRC wait time will trigger, so in the case of poor network or do something, such as users switch between play and pause frequently, The cue is constantly triggered.

Although this error is more like a warning and will not be perceived by the user and will not affect the entire play experience, it is unacceptable for the front end to keep reporting errors in the console, so we must add an error handler to the Promise returned by the this.media.play() method. If it’s easy to do this, at least the console won’t report any more errors.

Play () {// Return a promise const playPromise = this.media.play(); if (playPromise ! == undefined) { playPromise.catch(() => { // }); }}Copy the code

However, in playpromise.catch (), the most appropriate action is to call this.pause() directly, which will continue to display the pause state if the play() event fails. Due to our business requirements, the video automatically plays when the user opens the Play page. However, when I tested the Video in Safari, I found another surprise: The video does not automatically play in Safari. Could it be that Apple has banned it? Auto-play Policy Changes for macOS, Safari 11 has banned auto-play, requiring users to manually click to allow auto-play. And according to reliable sources, apple for not one size fits all, had 11 such as tencent video, youku video website joined the white list, allowing their web page directly play video, but for other sites, do not have such good luck, but apple also close to tell the developer, how to deal with this situation:

Developers can actively call the this.media.Play () event, but since safari disables it, This.media. Play () returns a Promise from the rejected/this.media. Play () function, which calls the stop-play () method, and displays the suspended UI. It just doesn’t give Safari users a playable experience when they open the page.

How to change video volume

The native video element volume property is an accessor property: both a getter and a setter. You can set or return the current volume of the video, and it ranges from 0.0 to 1.0 (the default is 1.0), so call directly

This. Media. Volume = 0.5;Copy the code

The current video volume can be set to 0.5, which is to control the volume of the video itself, but in fact, the excessive range and interval of the volume can be determined by the developer. For example, the Tencent Video player sets the volume range from 0 to 100, which is actually just a ratio replacement. Change the volume size of the video element volume itself.

The video can be muted by setting the volume and, naturally, by setting the current volume value to 0.0, or by setting the video element’s fraternal attribute to true (a Boolean attribute that fuels and returns whether the video has been muted). Once changed the video volume will trigger method [volumechange] (https://developer.mozilla.org/en-US/docs/Web/Events/volumechange).

The volumechange method fires when the audio volume changes (either a volume or a fraternal attribute).

How to change the current video progress.

Like the volume property mentioned above, currentTime is also an accessor property, It is both [getter] (https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/get) and [setter ] (https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/set), currentTime can set or return the audio/video playback of the current position, So simply assigning a value to this.media.currentTime will change the progress of the video, but with some UI and interaction changes. How to change the progress bar will be discussed below. Once curentTime is actively set, the video element’s own seeking method and seeked method are triggered.

The seeking and seeked methods indicate that a jump operation is in progress (changing the playback point) and that the jump operation is over, respectively.

How to achieve full screen video

The full screen video playback can be divided into two to achieve pseudo full screen and native full screen, the pseudo full-screen mode of implementation is directly to the current element to the window size to full screen, iQIYI provides full screen video playback when page features, but it will only fill the current browser window size, cannot fill the entire screen window size, in terms of my personal experience, Filling the entire screen is a much better immersive viewing experience than filling the browser window, so KuPlayer only provides the native full screen, and the final result looks like this: The video fills the screen, and there’s a native prompt “Press ESC to exit full-screen mode,” which is essentially the same as pressing F11 under Window.

Standard method of call full-screen is Element requestFullscreen, this method is used to send asynchronous requests make elements into full screen mode, but considering the browser compatibility, need according to its, moz and ms were prefix browser compatibility. So the code to request full screen could be written like this (again, for reference only) :

requestFullscreen(elem) { if (elem.requestFullscreen) { elem.requestFullscreen(); } else if (elem.msRequestFullscreen) { elem.msRequestFullscreen(); } else if (elem.mozRequestFullScreen) { elem.mozRequestFullScreen(); } else if (elem.webkitRequestFullscreen) { elem.webkitRequestFullscreen(); }}Copy the code

The way to get rid of full screen is document.exitFullscreen, but interestingly, exitFullscreen is a W3C standard way to get rid of full screen, but in Chrome and Firefox, Instead, it has changed to webkitCancelFullScreen and Mozilla CancelFullscreen respectively, probably because Chrome decided to use Cancel rather than Exit. So the code to exit the full screen would look like this:

cancelFullscreen(){ if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.msExitFullscreen) { document.msExitFullscreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if(document.oRequestFullscreen){ document.oCancelFullScreen(); }else if (document.webkitExitFullscreen){ document.webkitExitFullscreen(); }}Copy the code

RequestFullscreen (); document.querySelector (‘.kp-video).requestFullScreen (); Exiting full-screen is triggered directly on document, because if the requestFullscreen method of an element is called, the element is expanded to full-screen size, including its children. And at the same time, will only have a full screen of the element, so cancel the direct call document. CancelFullScreen is ok.

So we’re going to have to customize some of the effects depending on whether it’s fullscreen or not, and then we’re going to listen for the fullScreenchange event on the Document object, and either we’re going to request fullscreen or we’re going to cancel fullscreen, and then the fullScreenchange event is going to fire, Then we within this method using document. FullscreenElement is equal to null to judge whether the current state is already full screen, and then you can customize the style of the full screen mode. RequestFullscreen () is an asynchronous request that will not trigger the fullscreen state immediately after it is issued, and if it succeeds, it will fail. A failed requestFullscreen method raises the FullScreenError event.

In addition, the realization of full screen can only be manually triggered by the user, that is, triggered by events, and cannot be directly called in the code, so it cannot achieve the effect of opening the page that is full screen.

How do I display the progress bar

A video player should let the user know the current video playback and buffering progress. For playback progress, it is simply to get currentTime and duration, and then display the ratio of the two using elements and styles.

use

const {currentTime, duration } = this.media
Copy the code

CurrentTime and Duration of the video can be retrieved, and a simple percentage calculation can be performed:

const percent = `${currentTime/ duration * 100}%`
Copy the code

Then use elements and styles to simulate the progress bar. You can simply use div and set its width, or you can use the native Progress element. Here again, you need to modify the style of the progress element. As with the Input Range element, you need to write different styles for different browsers. There is a detailed introduction in this article, which will not be repeated here.

Now that we know how to change the playback progress, how to make the progress bar behave smoothly and naturally? Again, take advantage of some of the events provided by the native Video element: TimeUpdate is triggered when currentTime is updated. This is exactly what we need to show the progress bar, so just listen for the timeUpdate event and perform the above operation to get the length and calculate the ratio. However, the performance of the progress is not only when the video is playing normally, but also when the user performs seek operation, the current playing progress should be changed accordingly. Therefore, the user should monitor the seeking event of the video and continue to call the function to change the playing progress. It can also be manually called when the currentTime of the video is changed.

The buffer schedule is a bit more difficult to implement because the property describing the current video load is Buffered, which tells us which part of the video has been downloaded. It returns a TimeRanges object that indicates which blocks of the video have been downloaded. A TimeRanges object contains the following:

The length property gets the number of buffered ranges in the video. Call the start(index) method to get the start of a buffered range. Call the end(index) method to get the end of a buffered range.

If the playback process is not interrupted from beginning to end, i.e. no additional operations, then there is usually only one buffer range, so the following lines of code can be used directly between the buffers:

const { buffered } = this.media;
const start = buffered.start(0);
const end = buffered.end(0);
Copy the code

The difference between start and end is the buffer, but since this is assumed to be the case with no additional operations, start is approximately zero, so the length of the interval we use is the actual value of end. Then, just like a progress bar, use the element to simulate the change in progress. Similarly, we can listen for progress and Playing events on native video elements to change the buffer state for us,

Playing is triggered when a video starts to play (whether it is played for the first time, resumed after a pause, or restarted after it has finished). Progress is triggered periodically to inform the media of the download progress of the relevant section. Information about the total number of media currently downloaded is available in the buffered property of the element.

However, seek operation is unavoidable during video playback. At this time, TimeRanges length is no longer 1. Each jump to an unbuffered video area will generate a new buffer, and this buffer is arranged in ascending order. To illustrate this process, borrow a schematic from MDN:

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | = = = = = = = = = = = = = | | = = = = = = = = = = = | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 0 5 15 19 to 21Copy the code

This represents two buffer time ranges — the first spanning 0 to 5 seconds and the second 15 to 19 seconds. If currentTime is set to 10 at this time, a buffer between 10 seconds will be created, whether to 13 seconds or 14 seconds is unknown. Will merge the 10-15 second buffer with the 15-19 second buffer). Buffered. Start (index); buffered. Start (index); start(index); Buffered. End (index) is used as the cut-off point of the buffer, and the difference between the two is used as the current buffer.

Some tips

Through the implementation of the above specific functions, it can be found that many properties provided by native video are accessor properties. In order to directly access and expose these properties in the instantiated object, we can write the following code in the class.

class KuPlayer { constructor(){} ... get volume() { return this.media.volume; } set volume(value) { this.media.volume = value; } get muted() { return this.media.muted; } set muted(muted) { this.media.muted = muted; } get currentTime() { return this.media.currentTime; }... }Copy the code

In addition, whether it’s playing and pausing, or changing volume and progress, or something like Loadedmetadata in the native video itself, Now all attributes contain valid information they should have), error (triggered when an error occurs), ended (triggered at the end of the video playback) and other events. After all kinds of events are emitted, it is necessary to listen for these events in the method of instantiating the object, for which we introduce the Events module. Call on and emit methods to listen for internal events on the video element.

import EventEmitter from 'events'; . Class KuPlayer {constructor(){// First get the media object, which refers to the video element itself. this.media = document.querySelector('.J_kpMedia'); // Create an instance of EventEmitter. this.emiter = new EventEmitter(); On (event, listener) {this.emitter. on(event, listener); } / / methods: remove off an event (event listener) {this. Emiter. RemoveListener (event listener); } // Emit an event emit(event, data) {this.emitter. emit(event, data); } // Method: play video (){... // Emit the emiter play event this.emit('play'); } // Class method: pause video pause(){... // Emit the emiter pause event this.emit('pause'); }... }Copy the code

When instantiated, you can use the on method directly to listen for events inside the player:

const kuPlayer = new KuPlayer(); Kuplayer. on('play', CurrentTime console.log(kuplayer.currentTime)...Copy the code

summary

This article may only introduce the tip of the iceberg of video elements, and there are many native events and attributes of video elements that are not involved in business development, so we have not done much research. If you find any mistakes in this article, please contact [email protected].

I hope this article can inspire students who want to develop Web video players to make more amazing and practical video players. In addition, in the development process of player and the writing process of this paper, we refer to many excellent open source projects and articles, including: video.js, plyr.js and MDN.

Last but not least, is there anyone who would like to work for Kroca? As the front end only needs to be compatible with IE 11 above oh! We welcome excellent you!





Creative Commons Attribution – Non-commercial Use – Same way Share 4.0 International License