├── .gitignore ├── IntroMarkDown └── 文字稿.md ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json └── src ├── components └── Course.js ├── containers ├── App.js └── CourseContainer.js ├── index.js └── lib └── VideoPlayer ├── VideoPlayer.js └── videojs-hqcat.css /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /IntroMarkDown/文字稿.md: -------------------------------------------------------------------------------- 1 | # HTML5 视频播放器的自定制 2 | 3 | ### 0-Intro 4 | 5 | **案例目的:** 6 | 7 | 在 React 项目中使用 video.js,实现 HTML5 视频播放器的自定制。 8 | 9 | **关于 [video.js](http://videojs.com/):** 10 | 11 | 引用官方的自我介绍: 12 | 13 | > video.js is a free and open source HTML5 video player. 14 | 15 | 这是个免费且开源的 HTML5 视频播放器。 16 | 17 | 18 | ### 1-着手开发 19 | 20 | video.js 使用了 [Grunt](https://gruntjs.com/) 作为构建工具。所以,在开始写代码之前,请确保开发环境里已经装有 [Node](https://nodejs.org/) 和 [Grunt](https://gruntjs.com/) 21 | 22 | 安装 Grunt: 23 | ``` 24 | npm install -g grunt-cli 25 | ``` 26 | 27 | ### 2-创建一个 React 项目 28 | ``` 29 | create-react-app myVideoPlayer 30 | ``` 31 | 32 | 然后删去对我们没用的代码 33 | 34 | [commit](https://github.com/BeijiYang/VideoJsCustomization/tree/06e79e32b3cbfb8571a788166279083729f40e6c) 35 | 36 | ### 3-React 项目中引入 video.js 37 | 38 | 本节,我们将在 React 项目中引入 video.js ,为单调的页面上添加一个 HTML5 视频播放器。 39 | 40 | #### 安装 video.js 41 | ``` 42 | npm install --save-dev video.js 43 | ``` 44 | 45 | #### 项目中引入 video.js 46 | 47 | 对于如何在 React 项目中使用 video.js ,官方文档就这一篇:[ video.js and ReactJS integration](http://docs.videojs.com/tutorial-React.html) 48 | 49 | 我们参考文档中的基本方法,主要思路就是利用 React 组件的生命周期函数: 50 | * 在 `componentDidMount` 阶段实例化一个 video.js 播放器 51 | * 在 `componentWillUnmount` 阶段将其销毁 52 | 53 | 在我们的项目中,新建文件夹 `src/lib/VideoPlayer`,在其中新建组件 `VideoPlayer.js`: 54 | 55 | 特别注意,需要加入对 css 文件的引用 56 | 57 | **VideoPlayer.js** 58 | ``` 59 | import React from 'React' 60 | import videojs from 'video.js' 61 | import 'video.js/dist/video-js.css' 62 | 63 | export default class VideoPlayer extends React.Component { 64 | componentDidMount () { 65 | // instantiate video.js 66 | this.player = videojs(this.videoNode, this.props, function onPlayerReady () { 67 | console.log('onPlayerReady', this) 68 | }) 69 | } 70 | 71 | // destroy player on unmount 72 | componentWillUnmount () { 73 | if (this.player) { 74 | this.player.dispose() 75 | } 76 | } 77 | 78 | // wrap the player in a div with a `data-vjs-player` attribute 79 | // so videojs won't create additional wrapper in the DOM 80 | // see https://github.com/videojs/ video.js /pull/3856 81 | render () { 82 | return ( 83 |
84 |
86 | ) 87 | } 88 | } 89 | 90 | ``` 91 | 92 | 然后新建课程组件,它将引用 VideoPlayer 组件: 93 | 94 | 根据 [Dan Abramov的思想](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0),组件拆分为展示性组件和容器组件(Presentational and Container Components) 95 | 96 | **src/containers/App.js** 97 | ``` 98 | import React, { Component } from 'React' 99 | import CourseContainer from './CourseContainer' 100 | 101 | class App extends Component { 102 | render () { 103 | return ( 104 |
105 | 106 |
107 | ) 108 | } 109 | } 110 | 111 | export default App 112 | ``` 113 | 114 | **src/containers/CourseContainer.js** 115 | ``` 116 | import React, { Component } from 'React' 117 | import Course from '../components/Course' 118 | 119 | class CourseContainer extends Component { 120 | render () { 121 | // VideoJsOptions for this Course 122 | const CourseVideoJsOptions = { 123 | autoplay: true, 124 | controls: true, 125 | sources: [{ 126 | src: 'http://vjs.zencdn.net/v/oceans.mp4', 127 | type: 'video/mp4' 128 | }] 129 | } 130 | 131 | return ( 132 | 133 | ) 134 | } 135 | } 136 | 137 | export default CourseContainer 138 | ``` 139 | 140 | **src/components/Course.js** 141 | ``` 142 | 143 | import React, { Component } from 'React' 144 | import VideoPlayer from '../lib/VideoPlayer/VideoPlayer' 145 | 146 | class Course extends Component { 147 | 148 | render () { 149 | return ( 150 |
151 |

CourseDemo

152 | 153 |
154 | ) 155 | } 156 | } 157 | 158 | export default Course 159 | ``` 160 | 161 | 至此,在课程页面上就已经有一个播放器了。我们在 React 项目中成功引入了 video.js 。 162 | 163 | 很容易发现,页面加载完成后,播放器会自动播放视频。能否禁止这个默认行为呢? 164 | 165 | 下一节我们就看如何对播放器的功能进行控制和扩展。 166 | 167 | [commit](https://github.com/BeijiYang/VideoJsCustomization/tree/897f3ca7ccce59cb9490011e19bad0a7112088c8) 168 | 169 | 170 | ### 4-用 options 控制功能 171 | 172 | 本节,我们用 options 实现对基本功能的控制。 173 | 174 | video.js 中,可以通过 [options](http://docs.videojs.com/tutorial-options.html) 对播放器实例进行控制,如循环播放、静音、以及宽高样式等方面。 175 | 176 | 上面案例代码中的 `CourseVideoJsOptions` 就是一个例子。定义一个 options 对象,将其作为参数传入 VideoPlayer 组件中: 177 | 178 | **src/components/Course.js** 179 | ``` 180 | ... ... 181 | 182 | ... ... 183 | ``` 184 | 185 | 案例代码中的 options 对象如下: 186 | 187 | ``` 188 | const CourseVideoJsOptions = { 189 | autoplay: true, 190 | controls: true, 191 | sources: [{ 192 | src: 'http://vjs.zencdn.net/v/oceans.mp4', 193 | type: 'video/mp4' 194 | }] 195 | } 196 | ``` 197 | 198 | 其中有三个 key,对照 [options](http://docs.videojs.com/tutorial-options.html) 文档,不难知道 199 | * autoplay 是否自动播放 200 | * controls 是否显示控制条 201 | * sources 规定视频源 202 | 203 | 通过 options,我们可以对功能进行控制与添加: 204 | * playbackRates:倍速播放 205 | * poster: 视频播放前显示的图片 206 | * volumePanel:音量条 207 | * fluid: 播放器自动充满容器 208 | 209 | ``` 210 | const CourseVideoJsOptions = { 211 | autoplay: false, 212 | controls: true, 213 | sources: [{ 214 | src: 'http://vjs.zencdn.net/v/oceans.mp4', 215 | type: 'video/mp4' 216 | }, { 217 | src: 'http://vjs.zencdn.net/v/oceans.webm', 218 | type: 'video/webm' 219 | }], 220 | poster: 'http://videojs.com/img/logo.png', 221 | fluid: 'true', // put the player in the VideoPlayerWrap box 222 | 'playbackRates': [0.75, 1, 1.5, 2], 223 | controlBar: { 224 | volumePanel: { 225 | inline: false // 将音量控制条垂直 226 | } 227 | } 228 | } 229 | ``` 230 | 231 | 注意:这里 `sources` 对应的值是一个视频源对象数组。数组中每个 `src` 都是同一个视频,但格式各异。 232 | 233 | 这样可以解决不同浏览器之间的兼容性问题:Video.js 会检测当前浏览器所支持的视频格式,然后在数组中选择合适的视频源进行播放。 234 | 235 | **src/components/Course.js** 236 | ``` 237 | 238 | import React, { Component } from 'React' 239 | import VideoPlayer from '../lib/VideoPlayer/VideoPlayer' 240 | import styled from 'styled-components' 241 | 242 | const VideoPlayerWrap = styled.div` 243 | margin: 10px; 244 | padding: 10px; 245 | border: 2px solid green; 246 | ` 247 | 248 | class Course extends Component { 249 | 250 | render () { 251 | return ( 252 |
253 |

CourseDemo

254 | 255 | 256 | 257 |
258 | ) 259 | } 260 | } 261 | 262 | export default Course 263 | ``` 264 | 265 | 注:这里使用了 [styled-component](https://www.styled-components.com/)。 266 | 267 | 在播放器外套了一层 VideoPlayerWrap(其实就是 div ),这么做的好处在于: 268 | * 由于 VideoPlayer options 中打开了 `fluid`,播放器可以自适应 VideoPlayerWrap 容器。如此,options 就可以专注于对功能进行控制。 269 | * VideoPlayerWrap 的样式代码,同时也规定了播放器的样式。将样式代码集中写到展示性组件中,也符合 Dan Abramov 的思想 270 | 271 | 本节,我们用 video.js 的 options 机制,实现了播放器的倍速播放、添加 poster、样式控制等功能。 272 | 273 | 以上都是对现有功能进行控制。下一节,我们来看看如何按照我们的想法,对播放器的功能进行扩展。 274 | 275 | 276 | 277 | 278 | ### 5-写插件(Plugin)扩展功能 279 | 280 | 本节,我们的目的是,扩展空格键控制播放/暂停的功能。 281 | 282 | video.js 的默认动作是,仅仅在 control bar 的播放键被鼠标选中时,才能用空格/回车键控制播放/暂停。不太方便。 283 | 我们要将其改进为:点开视频后,就可以通过空格键控制视频的播放/暂停。 284 | 285 | #### 引入插件 286 | 287 | 我们可以通过写自己的[插件](http://docs.videojs.com/docs/guides/plugins.html)来实现额外的功能。参考这篇文档,尝试如下: 288 | 289 | **VideoPlayer.js** 290 | 291 | ``` 292 | ··· ··· 293 | render () { 294 | // 写插件:当监听到播放器实例的播放(play)事件,就输出一条语句 295 | function examplePlugin(options) { 296 | this.on('play', function(e) { 297 | console.log('playback has started!'); 298 | }); 299 | }; 300 | 301 | // 注册该插件 302 | videojs.registerPlugin('examplePlugin', examplePlugin) 303 | 304 | return ( 305 | ··· ··· 306 | ``` 307 | 308 | 目前插件已经存在,可以根据需要打开或关闭它 309 | 310 | 下面尝试使用 311 | 312 | **CourseContainer.js** 313 | ``` 314 | ··· ··· 315 | const CourseVideoJsOptions = { 316 | autoplay: false, 317 | controls: true, 318 | 319 | ... ... 320 | 321 | // 使用该插件 322 | plugins: { 323 | setStateandFocusPlugin: true 324 | } 325 | } 326 | ... ... 327 | ``` 328 | 329 | 完成以上步骤后,再次点开视频,发现控制台输出了 `playback has started!` ,说明插件应用成功。 330 | 331 | 关于代码中的事件监听,参考文档 [Event Target](http://docs.videojs.com/tutorial-event-target.html) 332 | 333 | #### 设置播放器实例的状态 334 | 335 | 在插件代码中加一条 console 语句如下。 336 | 337 | 如此,在控制台中,就可以在点开视频时看到,这句代码输出了该播放器的实例对象 338 | 339 | **VideoPlayer.js** 340 | ``` 341 | ··· ··· 342 | function examplePlugin(options) { 343 | this.on('play', function(e) { 344 | console.log(this) 345 | console.log('playback has started!') 346 | }); 347 | }; 348 | ... ... 349 | ``` 350 | 351 | 仔细查看该对象的属性,发现有 `setState` 和 `state` 两条。 352 | 据此,我们尝试根据播放器的播放状态来设置 `state` 值 353 | 354 | 注意,这和 React 组件的 state 是两回事。 355 | 356 | **VideoPlayer.js** 357 | ``` 358 | ··· ··· 359 | this.on('play', function (e) { 360 | console.log(this); 361 | console.log('playback has started!') 362 | this.setState({ 363 | state: 'playing' 364 | }) 365 | console.log(this.state.state) 366 | }) 367 | 368 | this.on('pause', function (e) { 369 | console.log('playback has paused') 370 | this.setState({ 371 | state: 'pause' 372 | }) 373 | console.log(this.state.state) 374 | }) 375 | ... ... 376 | ``` 377 | 378 | 此时,再让视频播放/暂停,都会看到控制台输出的状态,说明设置成功。 379 | 380 | #### 初步改进空格键的功能 381 | 382 | 空格键的默认动作是: 383 | * 当鼠标选中 control bar 的播放键时,空格键可以切换播放/暂停; 384 | * 当鼠标选中全屏键时,空格键可以切换全屏; 385 | * 当鼠标选中静音键时,空格键可以切换静音; 386 | * 当鼠标什么也没选中时,空格键无法控制播放器的任何功能。 387 | 388 | 这样既麻烦又不实用,因为只有“播放/暂停”功能才是使用频率最高的功能。让空格键在任何情况下都能直接控制暂停功能,可以明显提升用户体验。 389 | 390 | 接下来,我们需要监听“按下空格键”这个事件。这个需求可以分为两步: 391 | * 监听键盘事件 392 | * 判断是否为空格键 393 | 394 | 对于前者,我们可以使用 [onKeyDown 事件](http://www.w3school.com.cn/jsref/event_onkeydown.asp),它会在用户按下一个键盘按键时发生。 395 | 396 | 对于后者,涉及到 [keyCode/键码-文档链接待补充]()的知识。具体到这个案例,空格键的键码是32。 397 | 398 | 推荐[这个网站](http://keycode.info),它可以很方便地查询键盘各个按键的键码,十分好用。 399 | 400 | 通过 onKeyDown 事件的文档可以看到,