├── .babelrc ├── .gitignore ├── .npmignore ├── README.md ├── example └── src │ ├── app.js │ └── index.html ├── package.json ├── src ├── index.css └── index.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-proposal-class-properties", 8 | ] 9 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | example/dist -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # .npmignore 2 | src 3 | examples 4 | .babelrc 5 | .gitignore 6 | webpack.config.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub stars](https://img.shields.io/github/stars/feawesome/react-awesome-player.svg?style=flat-square)](https://github.com/feawesome/react-awesome-player/stargazers) 2 | [![GitHub issues](https://img.shields.io/github/issues/feawesome/react-awesome-player.svg?style=flat-square)](https://github.com/feawesome/react-awesome-player/issues) 3 | [![GitHub forks](https://img.shields.io/github/forks/feawesome/react-awesome-player.svg?style=flat-square)](https://github.com/feawesome/react-awesome-player/network) 4 | [![GitHub last commit](https://img.shields.io/github/last-commit/google/skia.svg?style=flat-square)](https://github.com/feawesome/react-awesome-player) 5 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg?style=flat-square)](https://github.com/feawesome/react-awesome-player) 6 | [![Twitter](https://img.shields.io/twitter/url/https/github.com/feawesome/react-awesome-player.svg?style=flat-square)](https://twitter.com/intent/tweet?url=https://github.com/feawesome/react-awesome-player) 7 | 8 | [![NPM](https://nodei.co/npm/react-awesome-player.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/react-awesome-player/) 9 | [![NPM](https://nodei.co/npm-dl/react-awesome-player.png?months=9&height=3)](https://nodei.co/npm/react-awesome-player/) 10 | 11 | 12 | ## react-awesome-player 13 | 14 | [video.js](https://github.com/videojs/video.js) player component for React. 15 | 16 | Secondary development based on video.js, perfectly compatible with React , support subtitle display and the live stream of HLS. It is an awesome plugin for playing video on webpage. 17 | If anything goes wrong during using, please submit issues in this repository, or send email to author: **returnzp@gmail.com** 18 | 19 | ### Example 20 | 21 | * [Demo Page](https://feawesome.github.io/react-awesome-player) 22 | * Demo Code 23 | ```jsx 24 | import React from 'react' 25 | import { render } from 'react-dom' 26 | import ReactAwesomePlayer from 'react-awesome-player' 27 | 28 | class App extends React.Component { 29 | state = { 30 | options: { 31 | poster: "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=854361313,3188166359&fm=26&gp=0.jpg", 32 | sources: [{ 33 | type: "video/mp4", 34 | src: "https://cdn.theguardian.tv/webM/2015/07/20/150716YesMen_synd_768k_vp8.webm" 35 | }], 36 | subtitles: [{ 37 | language: 'zh', 38 | url: "https://feawesome.github.io/react-awesome-player/zh.vtt", 39 | label: "中文" 40 | }, 41 | { 42 | language: 'en', 43 | url: "https://feawesome.github.io/react-awesome-player/en.vtt", 44 | label: "EN" 45 | }], 46 | defaultSubtitle: 'en' 47 | } 48 | } 49 | loadeddata() { 50 | console.log('loadeddata') 51 | } 52 | canplay() { 53 | console.log('canplay') 54 | } 55 | canplaythrough() { 56 | console.log('canplaythrough') 57 | } 58 | play() { 59 | console.log('play') 60 | } 61 | pause() { 62 | console.log('pause') 63 | } 64 | waiting() { 65 | console.log('waiting') 66 | } 67 | playing() { 68 | console.log('playing') 69 | } 70 | ended() { 71 | console.log('ended') 72 | } 73 | error() { 74 | console.log('error') 75 | } 76 | 77 | render () { 78 | const { options } = this.state 79 | return
80 | { console.log(video) }} 82 | options={options} 83 | loadeddata={this.loadeddata} 84 | canplay={this.canplay} 85 | canplaythrough={this.canplaythrough} 86 | play={this.play} 87 | pause={this.pause} 88 | waiting={this.waiting} 89 | playing={this.playing} 90 | ended={this.ended} 91 | error={this.error} 92 | /> 93 |
94 | } 95 | } 96 | 97 | render(, document.getElementById('root')) 98 | 99 | ``` 100 | 101 | 102 | ### Install 103 | #### NPM 104 | 105 | ``` bash 106 | npm install react-awesome-player --save 107 | ``` 108 | 109 | 110 | ### API 111 | - component api: 112 | 113 | | API | description | type | default | 114 | | - | - | - | - | 115 | | events | custom videojs event to component | Array | [] | 116 | | playsInline | set player not full-screen in mobile device | Boolean | true | 117 | | crossOrigin | set crossOrigin to video | String | '' | 118 | 119 | - video.js api 120 | * [video.js options](http://docs.videojs.com/tutorial-options.html) 121 | * [video.js docs](http://docs.videojs.com/) 122 | 123 | ### videojs plugins 124 | 125 | - [videojs-resolution-switcher](https://github.com/kmoskwiak/videojs-resolution-switcher) 126 | - [videojs-contrib-hls](https://github.com/videojs/videojs-contrib-hls) 127 | - [videojs-youtube](https://github.com/videojs/videojs-youtube) 128 | - [videojs-vimeo](https://github.com/videojs/videojs-vimeo) 129 | - [videojs-hotkeys](https://github.com/ctd1500/videojs-hotkeys) 130 | - [videojs-flash](https://github.com/videojs/videojs-flash) 131 | - [videojs-contrib-ads](https://github.com/videojs/videojs-contrib-ads) 132 | - [more plugins...](https://github.com/search?o=desc&q=videojs+plugin&s=stars&type=Repositories&utf8=%E2%9C%93) 133 | 134 | 135 | ### Author 136 | **Peng Zhang** 137 | **returnzp@gmail.com** 138 | -------------------------------------------------------------------------------- /example/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import ReactAwesomePlayer from 'react-awesome-player' 4 | 5 | class App extends React.Component { 6 | state = { 7 | options: { 8 | poster: "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=854361313,3188166359&fm=26&gp=0.jpg", 9 | sources: [{ 10 | type: "video/mp4", 11 | src: "https://cdn.theguardian.tv/webM/2015/07/20/150716YesMen_synd_768k_vp8.webm" 12 | }], 13 | subtitles: [{ 14 | language: 'zh', 15 | url: "https://feawesome.github.io/react-awesome-player/zh.vtt", 16 | label: "中文" 17 | }, 18 | { 19 | language: 'en', 20 | url: "https://feawesome.github.io/react-awesome-player/en.vtt", 21 | label: "EN" 22 | }], 23 | defaultSubtitle: 'en' 24 | } 25 | } 26 | loadeddata() { 27 | console.log('loadeddata') 28 | } 29 | canplay() { 30 | console.log('canplay') 31 | } 32 | canplaythrough() { 33 | console.log('canplaythrough') 34 | } 35 | play() { 36 | console.log('play') 37 | } 38 | pause() { 39 | console.log('pause') 40 | } 41 | waiting() { 42 | console.log('waiting') 43 | } 44 | playing() { 45 | console.log('playing') 46 | } 47 | ended() { 48 | console.log('ended') 49 | } 50 | error() { 51 | console.log('error') 52 | } 53 | 54 | render () { 55 | const { options } = this.state 56 | return
57 | { console.log(video) }} 59 | options={options} 60 | loadeddata={this.loadeddata} 61 | canplay={this.canplay} 62 | canplaythrough={this.canplaythrough} 63 | play={this.play} 64 | pause={this.pause} 65 | waiting={this.waiting} 66 | playing={this.playing} 67 | ended={this.ended} 68 | error={this.error} 69 | /> 70 |
71 | } 72 | } 73 | 74 | render(, document.getElementById('root')) 75 | -------------------------------------------------------------------------------- /example/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | React Awesome Player 5 | 6 | 7 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-awesome-player", 3 | "version": "1.1.1", 4 | "description": "video.js component for React", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "webpack-dev-server --open development", 9 | "build": "webpack --mode production", 10 | "deploy": "gh-pages -d example/dist", 11 | "publish-demo": "npm run build && npm run deploy && npx babel src --out-dir lib" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/feawesome/react-awesome-player.git" 16 | }, 17 | "keywords": [ 18 | "react-video-player", 19 | "react-awesome-player", 20 | "react video player", 21 | "video player", 22 | "react player", 23 | "react video" 24 | ], 25 | "author": { 26 | "name": "Peng Zhang", 27 | "email": "returnzp@gmail.com" 28 | }, 29 | "license": "MIT", 30 | "devDependencies": { 31 | "@babel/cli": "^7.2.3", 32 | "@babel/core": "^7.2.2", 33 | "@babel/preset-env": "^7.3.1", 34 | "@babel/preset-react": "^7.0.0", 35 | "babel-loader": "^8.0.5", 36 | "gh-pages": "^2.0.1", 37 | "html-webpack-plugin": "^3.2.0", 38 | "react": "^16.7.0", 39 | "react-dom": "^16.7.0", 40 | "webpack": "^4.29.0", 41 | "webpack-cli": "^3.2.1", 42 | "webpack-dev-server": "^3.1.14" 43 | }, 44 | "dependencies": { 45 | "@babel/plugin-proposal-class-properties": "^7.5.0", 46 | "css-loader": "^3.0.0", 47 | "object-path": "^0.11.4", 48 | "react-awesome-player": "^1.1.0", 49 | "style-loader": "^0.23.1", 50 | "video.js": "^7.6.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | .react-awesome-player * { 2 | outline: 0; 3 | } 4 | .video-js { 5 | height: auto; 6 | } 7 | .react-awesome-player-container { 8 | /* position: relative; */ 9 | } 10 | .vjs-time-tooltip { 11 | min-width: 40px; 12 | } 13 | .react-awesome-player-container button { 14 | outline: none; 15 | } 16 | 17 | .vjs-button > .vjs-icon-placeholder:before { 18 | /* line-height: 2; */ 19 | } 20 | 21 | /* 声音 */ 22 | .react-awesome-player-container .video-js .vjs-volume-panel-horizontal { 23 | display: none !important; 24 | } 25 | 26 | /* 显示时间 */ 27 | .react-awesome-player-container .video-js .vjs-control-bar .vjs-current-time { 28 | padding: 0; 29 | display: block !important; 30 | } 31 | .vjs-picture-in-picture-control { 32 | display: none !important; 33 | } 34 | /* 显示时间 */ 35 | .react-awesome-player-container .video-js .vjs-time-divider { 36 | padding: 0 2px; 37 | min-width: 0em; 38 | text-align: center; 39 | display: block !important; 40 | } 41 | 42 | /* 显示时间 */ 43 | .react-awesome-player-container .video-js .vjs-control-bar .vjs-duration { 44 | padding: 0; 45 | display: block !important; 46 | } 47 | 48 | .video-js .vjs-big-play-button .vjs-icon-placeholder:before, .vjs-button > .vjs-icon-placeholder:before, .video-js .vjs-modal-dialog, .vjs-modal-dialog .vjs-modal-dialog-content { 49 | position: unset; 50 | } 51 | 52 | .react-awesome-player-container video { 53 | object-fit: fill; 54 | } 55 | 56 | .react-awesome-player-container .vjs-fullscreen video { 57 | object-fit: contain; 58 | } 59 | 60 | .react-awesome-player-container .video-layer { 61 | } 62 | 63 | .react-awesome-player-container .video-layer section { 64 | width: 100%; 65 | height: 100%; 66 | display: flex; 67 | align-items: center; 68 | justify-content: center; 69 | text-align: center; 70 | background: #080915; 71 | box-shadow: 0px 0px 30px 1px #103136 inset; 72 | } 73 | 74 | .react-awesome-player-container .video-layer .loader { 75 | position: relative; 76 | width: 40px; 77 | height: 40px; 78 | border-radius: 50%; 79 | } 80 | 81 | .react-awesome-player-container .video-layer .loader-1 .loader-outter { 82 | position: absolute; 83 | border: 4px solid #b50136; 84 | border-left-color: transparent; 85 | border-bottom: 0; 86 | width: 100%; 87 | height: 100%; 88 | border-radius: 50%; 89 | -webkit-animation: loader-1-outter 1s cubic-bezier(0.42, 0.61, 0.58, 0.41) 90 | infinite; 91 | animation: loader-1-outter 1s cubic-bezier(0.42, 0.61, 0.58, 0.41) infinite; 92 | } 93 | 94 | .react-awesome-player-container .video-layer .loader-1 .loader-inner { 95 | position: absolute; 96 | border: 4px solid #b50136; 97 | border-radius: 50%; 98 | width: 18px; 99 | height: 18px; 100 | left: calc(50% - 22px); 101 | top: calc(50% - 24px); 102 | border-right: 0; 103 | border-top-color: transparent; 104 | -webkit-animation: loader-1-inner 1s cubic-bezier(0.42, 0.61, 0.58, 0.41) 105 | infinite; 106 | animation: loader-1-inner 1s cubic-bezier(0.42, 0.61, 0.58, 0.41) infinite; 107 | } 108 | 109 | @keyframes loader-1-outter { 110 | 0% { 111 | -webkit-transform: rotate(0deg); 112 | transform: rotate(0deg); 113 | } 114 | 100% { 115 | -webkit-transform: rotate(360deg); 116 | transform: rotate(360deg); 117 | } 118 | } 119 | 120 | @-webkit-keyframes loader-1-inner { 121 | 0% { 122 | -webkit-transform: rotate(0deg); 123 | transform: rotate(0deg); 124 | } 125 | 100% { 126 | -webkit-transform: rotate(-360deg); 127 | transform: rotate(-360deg); 128 | } 129 | } 130 | 131 | @keyframes loader-1-inner { 132 | 0% { 133 | -webkit-transform: rotate(0deg); 134 | transform: rotate(0deg); 135 | } 136 | 100% { 137 | -webkit-transform: rotate(-360deg); 138 | transform: rotate(-360deg); 139 | } 140 | } 141 | 142 | .no-video { 143 | position: absolute; 144 | width: 100%; 145 | height: 100%; 146 | left: 0; 147 | top: 0; 148 | z-index: 9; 149 | } 150 | 151 | .definition-container { 152 | width: 30px; 153 | background: red; 154 | height: 30px; 155 | position: relative; 156 | } 157 | 158 | .definition-container .definition-btn-container { 159 | background: #ddd; 160 | bottom: 30px; 161 | position: absolute; 162 | display: none; 163 | } 164 | 165 | .vjs-audio-button { 166 | /* display: none; */ 167 | } 168 | 169 | .vjs-texttrack-settings { 170 | /* display: none; */ 171 | } 172 | 173 | .vjs-menu .vjs-menu-content { 174 | overflow: initial; 175 | } 176 | 177 | .vjs-menu .vjs-menu-content li:first-child{ 178 | display: none; 179 | } 180 | 181 | .vjs-menu-content { 182 | overflow-y: hidden; 183 | } 184 | 185 | .video-js 186 | .vjs-subs-caps-button 187 | + .vjs-menu 188 | .vjs-captions-menu-item 189 | .vjs-menu-item-text 190 | .vjs-icon-placeholder:before { 191 | font-family: VideoJS !important; 192 | content: '\F10D' !important; 193 | } 194 | 195 | /* .vjs-icon-subtitles:before, 196 | .video-js .vjs-subtitles-button .vjs-icon-placeholder:before, 197 | .video-js .vjs-subs-caps-button .vjs-icon-placeholder:before, 198 | .video-js.video-js:lang(en-GB) 199 | .vjs-subs-caps-button 200 | .vjs-icon-placeholder:before, 201 | .video-js.video-js:lang(en-IE) 202 | .vjs-subs-caps-button 203 | .vjs-icon-placeholder:before, 204 | .video-js.video-js:lang(en-AU) 205 | .vjs-subs-caps-button 206 | .vjs-icon-placeholder:before, 207 | .video-js.video-js:lang(en-NZ) 208 | .vjs-subs-caps-button 209 | .vjs-icon-placeholder:before { 210 | content: '\e645' !important; 211 | font-size: 14px; 212 | } */ 213 | 214 | /* .vjs-icon-play, 215 | .video-js .vjs-big-play-button .vjs-icon-placeholder:before, 216 | .video-js .vjs-play-control .vjs-icon-placeholder { 217 | font-family: 'iconfont'; 218 | } */ 219 | 220 | /* .vjs-icon-play:before, 221 | .video-js .vjs-big-play-button .vjs-icon-placeholder:before, 222 | .video-js .vjs-play-control .vjs-icon-placeholder:before { 223 | font-size: 14px; 224 | content: '\e644' !important; 225 | } */ 226 | 227 | /* .vjs-icon-fullscreen-enter:before, 228 | .video-js .vjs-fullscreen-control .vjs-icon-placeholder:before { 229 | font-size: 14px; 230 | content: '\e646' !important; 231 | } */ 232 | 233 | /* .react-awesome-player-container .vjs-icon-play:before, 234 | .react-awesome-player-container .video-js .vjs-big-play-button .vjs-icon-placeholder:before, 235 | .react-awesome-player-container .video-js .vjs-play-control .vjs-icon-placeholder:before { 236 | font-size: 14px; 237 | content: '\e643' !important; 238 | } */ 239 | 240 | 241 | /* .vjs-button > .vjs-icon-placeholder:before { 242 | line-height: 42px; 243 | font-size: 20px; 244 | } */ 245 | 246 | /* .vjs-icon-pause:before, 247 | .video-js .vjs-play-control.vjs-playing .vjs-icon-placeholder:before { 248 | font-size: 14px; 249 | content: '\e643' !important; 250 | } */ 251 | 252 | .vjs-custom-skin > .video-js .vjs-menu-button-popup .vjs-menu { 253 | width: 90px; 254 | left: -30px; 255 | } 256 | 257 | .vjs-custom-skin > .video-js .vjs-control { 258 | width: 30px; 259 | } 260 | 261 | .vjs-custom-skin > .video-js .vjs-play-progress, 262 | .vjs-custom-skin > .video-js .vjs-volume-level { 263 | background-color: #d32f2f; 264 | } 265 | 266 | .vjs-custom-skin > .video-js .vjs-loading-spinner { 267 | border-color: #d32f2f; 268 | } 269 | 270 | .vjs-custom-skin > .video-js .vjs-control-bar .vjs-time-control { 271 | min-width: 6px; 272 | } 273 | 274 | .vjs-custom-skin > .video-js .vjs-control-bar .vjs-time-control div { 275 | height: 42px; 276 | font-size: 12px; 277 | line-height: 42px; 278 | } 279 | 280 | .vjs-custom-skin > .video-js .vjs-control-bar .vjs-time-control div span { 281 | line-height: 39px; 282 | } 283 | 284 | .vjs-custom-skin > .video-js .vjs-time-control { 285 | width: auto; 286 | } 287 | 288 | .vjs-custom-skin > .video-js .vjs-time-control .vjs-current-time-display { 289 | width: auto; 290 | } 291 | 292 | .vjs-custom-skin .video-js .vjs-big-play-button { 293 | margin: 0 !important; 294 | height: 54px !important; 295 | } 296 | 297 | .video-js { 298 | background-color: #212121; 299 | font-family: VideoJS !important; 300 | outline: 0; 301 | } 302 | 303 | .video-js .vjs-volume-panel { 304 | /* display: none !important; */ 305 | } 306 | 307 | .video-js .vjs-big-play-button { 308 | top: 50%; 309 | left: 50%; 310 | position: absolute; 311 | -webkit-transform: translate(-50%, -50%); 312 | transform: translate(-50%, -50%); 313 | width: 54px; 314 | height: 54px !important; 315 | border-radius: 50%; 316 | border: 0; 317 | line-height: 54px !important; 318 | background-color: rgba(0, 0, 0, 0.6); 319 | text-align: center; 320 | line-height: 54px; 321 | margin: 0 !important; 322 | } 323 | 324 | .video-js .vjs-custom-skin > .video-js .vjs-big-play-button { 325 | margin: 0 !important; 326 | } 327 | 328 | /* .video-js .vjs-icon-placeholder { 329 | line-height: 54px !important; 330 | font-family: 'iconfont' !important; 331 | } */ 332 | 333 | .video-js .vjs-play-progress { 334 | font-family: VideoJS !important; 335 | } 336 | 337 | .video-js .vjs-poster { 338 | background-size: cover; 339 | } 340 | 341 | .video-js:hover .vjs-big-play-button { 342 | background-color: rgba(0, 0, 0, 0.45); 343 | } 344 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * React-Awesome-Player 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | import PropTypes from 'prop-types'; 9 | import ObjectPath from 'object-path' 10 | 11 | import videojs from 'video.js' 12 | import 'video.js/dist/video-js.css'; 13 | import './index.css' 14 | 15 | const DEFAULT_STATE = { 16 | options: { 17 | controls: true, // 是否显示控制条 18 | controlBar: { // 显示控制条内容 19 | timeDivider: true, // 时间分割线 20 | durationDisplay: true, // 显示时间 21 | remainingTimeDisplay: false, // 剩余时间显示 22 | fullscreenToggle: true, // 切换全屏 23 | subtitlesButton: true, // 字幕按钮 24 | }, 25 | techOrder: [ 26 | 'html5', 27 | ], 28 | autoplay: false, // 自动播放 29 | muted: false, 30 | loop: false, // 循环播放 31 | preload: 'none', // 预加载 32 | // language: 'zh', // 展示语言 33 | aspectRatio: '16:9', // 比例 34 | fluid: true, 35 | poster: '', // 封面图 36 | sources: [], 37 | subtitles: [], // 字幕 38 | defaultSubtitle: '' // 默认字幕 39 | // width: document.documentElement.clientWidth, 40 | }, 41 | playInline: true, 42 | crossOrigin: '' 43 | } 44 | 45 | const DEFAULT_EVENTS = [ 46 | 'loadeddata', 47 | 'canplay', 48 | 'canplaythrough', 49 | 'play', 50 | 'pause', 51 | 'waiting', 52 | 'playing', 53 | 'ended', 54 | 'error', 55 | ]; 56 | 57 | class ReactAwesomePlayer extends React.Component { 58 | static defaultProps = { 59 | options: {} 60 | } 61 | 62 | video = null 63 | state = DEFAULT_STATE 64 | 65 | componentDidMount() { 66 | const sources = ObjectPath(this.props).get('options.sources') || [] 67 | const isSource = sources.length 68 | 69 | if (!this.player && isSource) { 70 | this.initialize(); 71 | } 72 | } 73 | 74 | componentDidUpdate(prevProps) { 75 | if (this.player && this.props.options !== prevProps.options) { 76 | this.resetUrl(); 77 | } 78 | } 79 | 80 | componentWillUnmount() { 81 | if (this.player) { 82 | this.player.dispose(); 83 | } 84 | } 85 | 86 | resetUrl() { 87 | const sources = ObjectPath(this.props).get('options.sources') || [] 88 | 89 | sources[0] && this.player.src(sources[0].src); 90 | this.player.poster(this.props.options.poster); 91 | } 92 | 93 | initialize() { 94 | // 添加行内播放 95 | if (this.state.playInline) { 96 | this.video.setAttribute('webkit-playsinline', true); 97 | this.video.setAttribute('playsInline', true); 98 | this.video.setAttribute('x5-playsinline', true); 99 | this.video.setAttribute('x5-video-player-type', 'h5'); 100 | this.video.setAttribute('x5-video-player-fullscreen', false); 101 | } 102 | 103 | // 跨域视频请求 104 | if (this.state.crossOrigin !== '') { 105 | this.video.crossOrigin = this.state.crossOrigin; 106 | this.video.setAttribute('crossOrigin', this.state.crossOrigin); 107 | } 108 | 109 | // 防止出现报错: "VIDEOJS: ERROR: Unable to find plugin: __ob__" 110 | if (this.state.options.plugins) { 111 | this.setState((prevState) => { 112 | delete prevState.plugins.__ob__; 113 | return prevState; 114 | }) 115 | } 116 | 117 | this.setState({ 118 | options: { 119 | ...this.state.options, 120 | ...this.props.options, 121 | }, 122 | }, () => { 123 | const context = this; 124 | 125 | this.player = videojs(this.video, this.state.options, function () { 126 | 127 | // events 128 | const events = DEFAULT_EVENTS.concat(context.props.events) 129 | 130 | // 监听事件 131 | const onEdEvents = {}; 132 | for (let i = 0; i < events.length; i += 1) { 133 | if (typeof events[i] === 'string' && onEdEvents[events[i]] === undefined) { 134 | (event => { 135 | onEdEvents[event] = null; 136 | this.on(event, () => { 137 | if (typeof context.props[event] === 'function') context.props[event](true); 138 | }); 139 | })(events[i]); 140 | } 141 | } 142 | 143 | // 监听事件更新事件 144 | this.on('timeupdate', function () { 145 | if (typeof context.props.timeupdate === 'function') context.props.timeupdate(this.currentTime()); 146 | }); 147 | }); 148 | }); 149 | } 150 | 151 | render() { 152 | const { subtitles, defaultSubtitle } = this.state.options; 153 | const { onRef } = this.props; 154 | 155 | return ( 156 |
157 | 174 |
175 | ) 176 | } 177 | } 178 | 179 | ReactAwesomePlayer.propTypes = { 180 | options: PropTypes.object, 181 | defaultSubtitle: PropTypes.string, 182 | events: PropTypes.array, 183 | onRef: PropTypes.func, 184 | 185 | // events props 186 | loadeddata: PropTypes.func, 187 | canplay: PropTypes.func, 188 | canplaythrough: PropTypes.func, 189 | play: PropTypes.func, 190 | pause: PropTypes.func, 191 | waiting: PropTypes.func, 192 | playing: PropTypes.func, 193 | ended: PropTypes.func, 194 | error: PropTypes.func, 195 | timeupdate: PropTypes.func, 196 | }; 197 | 198 | export default ReactAwesomePlayer; 199 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | const htmlWebpackPlugin = new HtmlWebpackPlugin({ 4 | template: path.join(__dirname, "./example/src/index.html"), 5 | filename: "./index.html" 6 | }); 7 | 8 | module.exports = { 9 | entry: path.join(__dirname, "./example/src/app.js"), 10 | output: { 11 | path: path.join(__dirname, "example/dist"), 12 | filename: "bundle.js" 13 | }, 14 | module: { 15 | rules: [{ 16 | test: /\.(js|jsx)$/, 17 | use: "babel-loader", 18 | exclude: /node_modules/ 19 | }, 20 | { 21 | // Preprocess our own .css files 22 | // This is the place to add your own loaders (e.g. sass/less etc.) 23 | // for a list of loaders, see https://webpack.js.org/loaders/#styling 24 | test: /\.css$/, 25 | use: ['style-loader', 'css-loader'], 26 | }, 27 | ] 28 | }, 29 | plugins: [htmlWebpackPlugin], 30 | resolve: { 31 | extensions: [".js", ".jsx"] 32 | }, 33 | devServer: { 34 | port: 3001 35 | } 36 | }; 37 | 38 | --------------------------------------------------------------------------------