Source: Player.js

  1. /**
  2. * Copyright (C) 2019.
  3. * All Rights Reserved.
  4. * @file Player.js
  5. * @desc
  6. * the player main stream
  7. * load data by url -> get the data -> ts demux -> get h265 data -> Webassmebly decode -> YUV data -> draw yuv to Canvas -> image and audio synv -> audio stream -> AudioContext decode and play
  8. * @author Jarry
  9. */
  10. import getEvents from './toolkit/Events.js'
  11. import Element from './toolkit/Element.js'
  12. import AlertError from './error/AlertError'
  13. import { throwError } from './error/ThrowError'
  14. import BaseClass from './base/BaseClass.js'
  15. import LoaderController from './loader/LoaderController'
  16. import DataController from './data/DataController'
  17. import Action from './action/Action.js'
  18. import AudioPlayer from './audio/AudioPlayer.js'
  19. import { Config, READY } from './config/Config'
  20. import PlayerUtil from './utils/PlayerUtil'
  21. import ComponentsController from './components/ComponentsController'
  22. import ControlBarController from './control-bar/ControlBarController'
  23. import DataProcessorController from './data-processor/dataProcessorController'
  24. import StreamController from './action/StreamController.js'
  25. import ImagePlayer from './ImagePlayer/ImagePlayer.js'
  26. import webworkify from 'webworkify-webpack'
  27. import Events from './config/EventsConfig'
  28. class Player extends BaseClass {
  29. mode = Config.mode
  30. $container = null
  31. componentsController = null
  32. controlBarController = null
  33. controlBarHeight = 50
  34. alertError = null
  35. dataController = null
  36. demuxer = null
  37. decoder = null
  38. preload = true
  39. startTime = 0
  40. screenWidth = null
  41. screenHeight = null
  42. options = {}
  43. #libPath = Config.libPath
  44. readyStatus = { dataProcessor: false, firstData: false, audioPlayer: false }
  45. reseting = false
  46. #currentTime = 0
  47. #seek = false
  48. #playbackRate = 1
  49. #muted = false
  50. maxBufferLength = READY.MAXBUFFERLENGTH
  51. seekSegmentNo = -1
  52. status = 'init'
  53. loader = null
  54. currentIndex = null
  55. startIndex = 1
  56. loadData = null
  57. paused = false
  58. autoPlay = true
  59. duration = 0
  60. tsNumber = 0
  61. /**
  62. * @property {string} sourceURL - The url of the video to play
  63. * @property {string} source - The url of the video to play
  64. * @property {Object} defaultRate - The default value of playing rate
  65. * @property {string} type - The type of the video, such as HLS
  66. * @property {Object[]} rateList - The rate list of the player
  67. * @example <caption>Example of rateList.</caption>
  68. * // let options = {
  69. * rateList:[
  70. {"url":"http://localhost/20190902/01/a5/029f8fad8868a7116f20d8ae5b075996.m3u8","id":51,"name":"720P","value":"720"},
  71. {"url":"http://localhost/20190902/f0/db/ee545466ced38973a9d60fe7f24ed409.m3u8","id":51,"name":"高清","value":"600"},
  72. {"url":"http://localhost/20190902/78/6c/5a5a99476f4f792e2e7a701ba1f6d5ad.m3u8","id":264,"name":"极速","value":"jisu"},
  73. {"url":"http://localhost/20190902/54/05/7e714a321d9e7d92c937582b2e439833.m3u8","id":265,"name":"流畅","value":"300"}]
  74. }
  75. * @property {Function} processURL - process the url of video source
  76. * @property {Number} maxBufferLength - The maximum value of the buffer, its default value is 5000(ms)
  77. * @property {Boolean} autoPlay - If auto play after initializing the Player
  78. * @property {string} libPath - The path of decoder
  79. * @property {Boolean} preload - If pre load video before playing
  80. * @property {Number} startTime - Start time to play the video
  81. * @property {string} playbackRate - Playback speed
  82. * @property {Number} controlBarHeight - The height of the control bar
  83. * @property {AlertError} alertError - The alert info when error happens
  84. * @property {Worker} httpWorker - set User's web worker
  85. * @property {Function} afterLoadPlaylist - To handle operations after playlist is loaded
  86. */
  87. constructor (el, options = {}) {
  88. super()
  89. if (!el) {
  90. this.logger.error('Please pass in a dom object as the display container')
  91. }
  92. this.el = el
  93. Object.assign(this.options, options)
  94. this.options.sourceURL = this.options.sourceURL || this.options.source
  95. this.options.streamList = this.options.streamList || []
  96. this.options.events = getEvents()
  97. this.maxBufferLength = options.maxBufferLength !== undefined ? options.maxBufferLength : this.maxBufferLength
  98. this.autoPlay = options.autoPlay !== undefined ? options.autoPlay : this.autoPlay
  99. this.#libPath =
  100. options.libPath !== undefined ? options.libPath : this.#libPath
  101. this.preload =
  102. options.preload === undefined ? this.preload : options.preload
  103. this.startTime = options.startTime === undefined ? this.startTime : options.startTime
  104. this.originStartTime = this.startTime
  105. this.playbackRate = options.playbackRate === undefined ? this.playbackRate : options.playbackRate
  106. }
  107. setAlertError () {
  108. this.options.alertError = this.alertError = AlertError.getInstance({
  109. player: this,
  110. component: this.componentsController.alertBox,
  111. events: this.options.events
  112. })
  113. }
  114. setDataController () {
  115. this.dataController = DataController.getInstance({
  116. player: this,
  117. events: this.options.events
  118. })
  119. }
  120. setLoadData () {
  121. this.dataController.setLoadData({
  122. player: this,
  123. events: this.options.events
  124. })
  125. this.loadData = this.dataController.loadData
  126. }
  127. setComponentsController () {
  128. this.componentsController = ComponentsController.getInstance({
  129. $container: this.$container,
  130. $screenContainer: this.$screenContainer,
  131. $canvas: this.$canvas,
  132. $audioContainer: this.$audioContainer,
  133. $audio: this.$audio,
  134. loadData: this.loadData,
  135. bigPlayButtonColor: this.bigPlayButtonColor,
  136. player: this,
  137. events: this.options.events
  138. })
  139. }
  140. setControlBarController () {
  141. const options = Object.assign({}, this.options, {
  142. $container: this.$container,
  143. $screenContainer: this.$screenContainer,
  144. controlBarAutoHide: this.controlBarAutoHide,
  145. player: this
  146. })
  147. this.controlBarController = ControlBarController.getInstance(options)
  148. }
  149. setLoadController () {
  150. this.loaderController = LoaderController.getInstance(this.options.type, {
  151. player: this,
  152. events: this.options.events
  153. })
  154. }
  155. setProcessorController() {
  156. this.processController = new DataProcessorController({
  157. type: 'ts',
  158. libPath: this.#libPath,
  159. events: this.options.events,
  160. player: this
  161. })
  162. }
  163. setStreamController () {
  164. this.streamController = new StreamController({
  165. events: this.options.events,
  166. loadData: this.loadData,
  167. imagePlayer: this.imagePlayer,
  168. audioPlayer: this.audioPlayer,
  169. player: this
  170. })
  171. }
  172. setImagerPlayer () {
  173. this.imagePlayer = new ImagePlayer({
  174. events: this.options.events,
  175. canvas: this.$canvas,
  176. maxBufferLength: this.maxBufferLength,
  177. player: this
  178. })
  179. }
  180. setAudioPlayer () {
  181. this.audioPlayer = new AudioPlayer({
  182. player: this,
  183. events: this.options.events,
  184. audioNode: this.$audio
  185. })
  186. }
  187. setAction () {
  188. this.action = new Action({
  189. player: this,
  190. screen: this.screen,
  191. imagePlayer: this.imagePlayer,
  192. loadData: this.loadData,
  193. audioPlayer: this.audioPlayer,
  194. events: this.options.events
  195. })
  196. }
  197. init () {
  198. this.controlBarHeight = this.options.controlBarHeight || this.controlBarHeight
  199. this.options.httpWorker = webworkify(require.resolve('./toolkit/HTTP.js'), {
  200. name: 'httpWorker'
  201. })
  202. this.currentTime = this.startTime
  203. this.addEl()
  204. this.setDataController()
  205. this.setLoadData()
  206. this.setComponentsController()
  207. this.setLoadController()
  208. this.setControlBarController()
  209. this.componentsController.setControlBarController(this.controlBarController)
  210. this.setAlertError()
  211. this.setProcessorController()
  212. this.setImagerPlayer()
  213. this.setAudioPlayer()
  214. this.setAction()
  215. this.setStreamController()
  216. this.componentsController.drawPoster()
  217. if (this.preload) {
  218. this.run()
  219. }
  220. this.bindEvent()
  221. }
  222. bindEvent () {
  223. this.events.on(Events.PlayerOnPlay, () => {
  224. this.play()
  225. })
  226. this.events.on(Events.PlayerOnPause, () => {
  227. this.pause()
  228. })
  229. this.events.on(Events.PlayerOnVolume, (value) => {
  230. this.volume = value
  231. })
  232. this.events.on(Events.DataProcessorReady, () => {
  233. this.logger.info('bindEvent', 'decoder ready')
  234. this.checkReady('dataProcessor')
  235. })
  236. this.events.on(Events.AudioPlayerReady, () => {
  237. this.logger.info('bindEvent', 'audioPlayer ready')
  238. if (!this.seeking) {
  239. this.checkReady('audioPlayer')
  240. }
  241. })
  242. this.events.on(Events.PlayerReady, () => {
  243. this.logger.info('bindEvent', 'player ready')
  244. this.onReady()
  245. })
  246. this.events.on(Events.PlayerSpeedTo, (data) => {
  247. this.changeSpeed(data)
  248. })
  249. this.events.on(Events.PlayerChangeRate, (data) => {
  250. this.changeRate(data)
  251. })
  252. this.events.on(Events.PlayerWait, () => {
  253. this.onWait()
  254. })
  255. this.events.on(Events.PlayerPlay, () => {
  256. this.onPlay()
  257. })
  258. this.events.on(Events.LoaderPlayListLoaded, data => {
  259. if (typeof this.options.afterLoadPlaylist == 'function') {
  260. this.options.afterLoadPlaylist(this.laodData.sourceData)
  261. }
  262. let sourceData = data.loadData.sourceData
  263. this.duration = sourceData.duration
  264. this.tsNumber = sourceData.length
  265. this.streamController.setBaseInfo({
  266. duration: this.duration,
  267. tsNumber: this.tsNumber
  268. })
  269. this.dataController.startLoad(this.startTime)
  270. this.setStartTime(this.originStartTime)
  271. })
  272. this.events.on(Events.LoadDataFirstLoaded, buffer => {
  273. this.logger.info('bindEvent', 'first data ready')
  274. this.startIndex = buffer.no
  275. this.streamController.currentIndex = buffer.no
  276. this.checkReady('firstData')
  277. })
  278. this.events.on(Events.StreamDataReady, () => {
  279. this.logger.info('bindEvent', 'dataReady')
  280. this.onDataReady()
  281. })
  282. this.events.on(Events.PlayerLoadedMetaData, (width, height) => {
  283. this.setCanvas()
  284. this.resizeScreen(width, height)
  285. })
  286. this.events.on(Events.PlayerEnd, () => {
  287. this.status = 'end'
  288. })
  289. this.events.on(Events.ImagePlayerBuffeUpdate, () => {
  290. let buffer = this.buffer()
  291. this.events.emit(Events.PlayerbufferUpdate, buffer)
  292. })
  293. this.events.on(Events.PlayerOnSeek, (time) => {
  294. this.seek(Math.floor(time))
  295. })
  296. this.events.on(Events.PlayerAlert, (content) => {
  297. this.alertError.show(content)
  298. })
  299. this.events.on(Events.PlayerThrowError, (errors) => {
  300. throwError.apply(this, errors)
  301. })
  302. }
  303. reset (value) {
  304. if (this.action) {
  305. this.action.reset(value)
  306. }
  307. }
  308. switchPlaylist (url, callback) {
  309. this.loaderController.switchPlaylist(url, callback)
  310. }
  311. /**
  312. * @method
  313. * @name changeSpeed
  314. * @param {Object} data - data.value, The value of playback speed
  315. * @description Change the playback speed, such as 1, 0.5, 1.5, 2...*/
  316. changeSpeed (data = {}) {
  317. this.playbackRate = data.value || 1
  318. }
  319. setStartTime (time) {
  320. this.startTime = time
  321. }
  322. /**
  323. * @method
  324. * @name changeRate
  325. * @param {Object} data - The url of the video source
  326. * @param {function} callback - Function to handle after changing the video source
  327. * @description Change the rate of the video source, such as 720P, HD...*/
  328. changeRate (data) {
  329. this.pause()
  330. this.events.emit(Events.ControlBarPauseLoading, this)
  331. this.setStartTime((this.currentTime - 5000) / 1000)
  332. this.changing = true
  333. this.seeking = false
  334. this.imagePlayer.firstRender = false
  335. this.readyStatus = { dataProcessor: false, firstData: false, audioPlayer: false }
  336. this.reset(true)
  337. this.switchPlaylist(data.url)
  338. }
  339. /**
  340. * @method
  341. * @name changeSrc
  342. * @param {string} url - The url of the video source
  343. * @param {function} callback - Function to handle after changing the video source
  344. * @description Change the source of the video to play*/
  345. changeSrc (url, callback) {
  346. this.pause()
  347. this.events.emit(Events.ControlBarPauseLoading, this)
  348. this.events.emit(Events.PlayerChangeSrc, this)
  349. this.changing = true
  350. this.seeking = false
  351. this.imagePlayer.firstRender = false
  352. this.readyStatus = { dataProcessor: false, firstData: false, audioPlayer: false }
  353. this.currentTime = this.startTime
  354. this.imagePlayer.clear()
  355. this.reset(true)
  356. this.switchPlaylist(url, callback)
  357. }
  358. setCanvas () {
  359. const $canvas = PlayerUtil.createCanvas(this)
  360. if (Element.isElement(this.$canvas)) {
  361. if (this.$canvas.width && this.$canvas.height) {
  362. this.imagePlayer.setScreenRender($canvas)
  363. this.$canvas.replaceWith($canvas)
  364. this.$canvas = $canvas
  365. return
  366. }
  367. } else {
  368. this.$screen.appendChild($canvas)
  369. this.$canvas = $canvas
  370. }
  371. }
  372. /**
  373. * @method
  374. * @name destroy
  375. * @description destroy the instance of Class Player*/
  376. destroy () {
  377. if (this.controlBarController) {
  378. this.controlBarController.destroy()
  379. delete this.controlBarController
  380. }
  381. if (this.$container) {
  382. this.el.removeChild(this.$container)
  383. delete this.$container
  384. }
  385. this.decodeController.destroy()
  386. this.demuxController.destroy()
  387. this.loaderController.destroy()
  388. }
  389. run () {
  390. this.loaderController.run()
  391. }
  392. checkReady (type) {
  393. let readyStatus = this.readyStatus
  394. if (type && typeof type === 'string') {
  395. readyStatus[type] = true
  396. let keys = Object.keys(readyStatus)
  397. for (let i = 0; i < keys.length; i++) {
  398. if (!readyStatus[keys[i]]) {
  399. return false
  400. }
  401. }
  402. this.logger.info('checkReady', 'player ready')
  403. this.events.emit(Events.PlayerReady)
  404. return true
  405. } else {
  406. this.logger.error('checkReady', 'check ready', 'type is no correct, type:', type)
  407. return false
  408. }
  409. }
  410. addEl () {
  411. let $container, $screen, $audioContainer, $audio
  412. if (!this.el) {
  413. this.logger.error('addEl', 'not found el.', 'el:', this.options.el)
  414. return
  415. }
  416. $container = PlayerUtil.createContainer(this)
  417. this.el.appendChild($container)
  418. this.$container = $container
  419. $screen = PlayerUtil.createScreenContainer(this)
  420. this.$screen = $screen
  421. $container.appendChild($screen)
  422. this.$screenContainer = $screen
  423. this.setCanvas()
  424. $audio = PlayerUtil.createAudio(this)
  425. this.$audio = $audio
  426. $audioContainer = PlayerUtil.createAudioContainer(this)
  427. $audioContainer.appendChild($audio)
  428. this.$audioContainer = $audioContainer
  429. $container.appendChild($audioContainer)
  430. }
  431. onWait () {
  432. this.logger.info('onWait', 'wait,wait,wait')
  433. }
  434. onPlay () {
  435. this.logger.info('onPlay', 'play, play, play')
  436. }
  437. onReady () {
  438. if (!this.changing) {
  439. this.componentsController.run()
  440. }
  441. if (this.changing) {
  442. this.logger.info('onReady', 'change ready', 'startIndex:', this.startIndex)
  443. }
  444. this.streamController.startLoad(this.startIndex)
  445. }
  446. onDataReady () {
  447. if (this.changing) {
  448. this.changing = false
  449. this.play()
  450. }
  451. if (this.seeking || (this.autoPlay && !this.paused)) {
  452. this.play()
  453. }
  454. }
  455. buffer () {
  456. let videoBuffered = this.imagePlayer.buffer()
  457. let audioPlayerBuffered = this.audioPlayer.buffer()
  458. let sTime = Math.max(videoBuffered.start, audioPlayerBuffered.start)
  459. let eTime = Math.min(videoBuffered.end, audioPlayerBuffered.end)
  460. if (sTime < eTime) {
  461. return [sTime, eTime]
  462. }
  463. return [0, 0]
  464. }
  465. /**
  466. * @method
  467. * @name play
  468. * @description play the video*/
  469. play() {
  470. this.logger.info('play', this.status)
  471. if (this.status !== 'playing') {
  472. this.logger.info('start play')
  473. this.status = 'playing'
  474. this.paused = false
  475. this.action.play(this.currentTime)
  476. this.events.emit(Events.PlayerPlay, this)
  477. }
  478. }
  479. on (name, callback) {
  480. this.events.on('Player.' + name, callback)
  481. }
  482. off (name, callback) {
  483. this.events.off('Player.' + name, callback)
  484. }
  485. once (name, callback) {
  486. this.events.once('Player.' + name, callback)
  487. }
  488. /**
  489. * @method
  490. * @name pause
  491. * @description pause the video*/
  492. pause () {
  493. if (this.status != 'pause') {
  494. this.logger.info('pause')
  495. this.status = 'pause'
  496. this.action.pause()
  497. this.paused = true
  498. this.events.emit(Events.PlayerPause, this)
  499. }
  500. }
  501. /**
  502. * @method
  503. * @name seek
  504. * @param {number} time - the duration of seeking
  505. * @description seek time to play*/
  506. seek (time) {
  507. if (time < this.startTime) {
  508. return
  509. }
  510. if (time >= this.duration * 1000) {
  511. this.logger.info('seek', 'seek to time:', time)
  512. return
  513. }
  514. this.seekTime = Date.now()
  515. this.seeking = true
  516. this.currentTime = time
  517. this.action.seek(time)
  518. }
  519. get muted () {
  520. return this.#muted
  521. }
  522. set muted (value) {
  523. this.#muted = !!value
  524. }
  525. set playbackRate (value) {
  526. this.#playbackRate = value
  527. }
  528. get playbackRate () {
  529. return this.#playbackRate
  530. }
  531. set seeking (value) {
  532. this.#seek = value
  533. if (value) {
  534. this.events.emit(Events.PlayerSeeking)
  535. } else {
  536. this.events.emit(Events.PlayerSeekEnd)
  537. }
  538. }
  539. get seeking () {
  540. return this.#seek
  541. }
  542. get currentTime () {
  543. return this.#currentTime
  544. }
  545. set currentTime (time) {
  546. this.#currentTime = time
  547. }
  548. get volume () {
  549. return this.audioPlayer.volume
  550. }
  551. set volume (value) {
  552. this.audioPlayer.volume = value
  553. }
  554. resizeScreen (width, height) {
  555. Element.adaptSizeElement(width, height, this.$screenContainer, this.$canvas)
  556. }
  557. }
  558. export default Player