proxy.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. import EventEmitter from 'event-emitter'
  2. import util from './utils/util'
  3. import Errors from './error'
  4. class Proxy {
  5. constructor (options) {
  6. this.logParams = {
  7. bc: 0,
  8. bu_acu_t: 0,
  9. played: []
  10. }
  11. this._hasStart = false
  12. this.videoConfig = {
  13. controls: !!options.isShowControl,
  14. autoplay: options.autoplay,
  15. playsinline: options.playsinline,
  16. 'webkit-playsinline': options.playsinline,
  17. 'x5-playsinline': options.playsinline,
  18. 'x5-video-player-type': options['x5-video-player-type'] || options['x5VideoPlayerType'],
  19. 'x5-video-player-fullscreen': options['x5-video-player-fullscreen'] || options['x5VideoPlayerFullscreen'],
  20. 'x5-video-orientation': options['x5-video-orientation'] || options['x5VideoOrientation'],
  21. airplay: options['airplay'],
  22. 'webkit-airplay': options['airplay'],
  23. tabindex: 2,
  24. mediaType: options.mediaType || 'video'
  25. }
  26. if (options.muted) {
  27. this.videoConfig.muted = 'muted'
  28. }
  29. if (options.loop) {
  30. this.videoConfig.loop = 'loop'
  31. }
  32. let textTrackDom = ''
  33. this.textTrackShowDefault = true
  34. if (options.textTrack && Array.isArray(options.textTrack)) {
  35. if (options.textTrack.length > 0 && !options.textTrack.some(track => { return track.default })) {
  36. options.textTrack[0].default = true
  37. this.textTrackShowDefault = false
  38. }
  39. options.textTrack.some(track => {
  40. if (track.src && track.label && track.default) {
  41. textTrackDom += `<track src="${track.src}" `
  42. if (track.kind) {
  43. textTrackDom += `kind="${track.kind}" `
  44. }
  45. textTrackDom += `label="${track.label}" `
  46. if (track.srclang) {
  47. textTrackDom += `srclang="${track.srclang}" `
  48. }
  49. textTrackDom += `${track.default ? 'default' : ''}>`
  50. return true
  51. }
  52. })
  53. this.videoConfig.crossorigin = 'anonymous'
  54. }
  55. if (options.textTrackStyle) {
  56. let style = document.createElement('style')
  57. this.textTrackStyle = style
  58. document.head.appendChild(style)
  59. let styleStr = ''
  60. for (let index in options.textTrackStyle) {
  61. styleStr += `${index}: ${options.textTrackStyle[index]};`
  62. }
  63. let wrap = options.id ? `#${options.id}` : (options.el.id ? `#${options.el.id}` : `.${options.el.className}`)
  64. if (style.sheet.insertRule) {
  65. style.sheet.insertRule(`${wrap} video::cue { ${styleStr} }`, 0)
  66. } else if (style.sheet.addRule) {
  67. style.sheet.addRule(`${wrap} video::cue`, styleStr)
  68. }
  69. }
  70. this.video = util.createDom(this.videoConfig.mediaType, textTrackDom, this.videoConfig, '')
  71. if (!this.textTrackShowDefault && textTrackDom) {
  72. let trackDoms = this.video.getElementsByTagName('Track')
  73. trackDoms[0].track.mode = 'hidden'
  74. }
  75. if (options.autoplay) {
  76. this.video.autoplay = true
  77. if (options.autoplayMuted) {
  78. this.video.muted = true
  79. }
  80. }
  81. this.ev = ['play', 'playing', 'pause', 'ended', 'error', 'seeking', 'seeked',
  82. 'timeupdate', 'waiting', 'canplay', 'canplaythrough', 'durationchange', 'volumechange', 'loadeddata', 'loadstart'
  83. ].map((item) => {
  84. return {
  85. [item]: `on${item.charAt(0).toUpperCase()}${item.slice(1)}`
  86. }
  87. })
  88. EventEmitter(this)
  89. this._interval = {}
  90. let lastBuffer = '0,0'
  91. let self = this
  92. this.ev.forEach(item => {
  93. self.evItem = Object.keys(item)[0]
  94. let name = Object.keys(item)[0]
  95. self.video.addEventListener(Object.keys(item)[0], function () {
  96. // fix when video destroy called and video reload
  97. if (!self || !self.logParams) {
  98. return
  99. }
  100. if (name === 'play') {
  101. self.hasStart = true
  102. } else if (name === 'canplay') {
  103. util.removeClass(self.root, 'xgplayer-is-enter')
  104. } else if (name === 'waiting') {
  105. self.logParams.bc++
  106. self.inWaitingStart = new Date().getTime()
  107. } else if (name === 'playing') {
  108. util.removeClass(self.root, 'xgplayer-is-enter')
  109. if (self.inWaitingStart) {
  110. self.logParams.bu_acu_t += new Date().getTime() - self.inWaitingStart
  111. self.inWaitingStart = undefined
  112. }
  113. } else if (name === 'loadeddata') {
  114. self.logParams.played.push({
  115. begin: 0,
  116. end: -1
  117. })
  118. } else if (name === 'seeking') {
  119. self.logParams.played.push({
  120. begin: self.video.currentTime,
  121. end: -1
  122. })
  123. } else if (self && self.logParams && self.logParams.played && name === 'timeupdate') {
  124. if (self.logParams.played.length < 1) {
  125. self.logParams.played.push({
  126. begin: self.video.currentTime,
  127. end: -1
  128. })
  129. }
  130. self.logParams.played[self.logParams.played.length - 1].end = self.video.currentTime
  131. }
  132. if (name === 'error') {
  133. // process the error
  134. self._onError(name)
  135. } else {
  136. self.emit(name, self)
  137. }
  138. if (self.hasOwnProperty('_interval')) {
  139. if (['ended', 'error', 'timeupdate'].indexOf(name) < 0) {
  140. clearInterval(self._interval['bufferedChange'])
  141. util.setInterval(self, 'bufferedChange', function () {
  142. if (self.video && self.video.buffered) {
  143. let curBuffer = []
  144. for (let i = 0, len = self.video.buffered.length; i < len; i++) {
  145. curBuffer.push([self.video.buffered.start(i), self.video.buffered.end(i)])
  146. }
  147. if (curBuffer.toString() !== lastBuffer) {
  148. lastBuffer = curBuffer.toString()
  149. self.emit('bufferedChange', curBuffer)
  150. }
  151. }
  152. }, 200)
  153. } else {
  154. if (name !== 'timeupdate') {
  155. util.clearInterval(self, 'bufferedChange')
  156. }
  157. }
  158. }
  159. }, false)
  160. })
  161. }
  162. /**
  163. * 错误监听处理逻辑抽离
  164. */
  165. _onError (name) {
  166. if (this.video && this.video.error) {
  167. this.emit(name, new Errors('other', this.currentTime, this.duration, this.networkState, this.readyState, this.currentSrc, this.src,
  168. this.ended, {
  169. line: 162,
  170. msg: this.error,
  171. handle: 'Constructor'
  172. }, this.video.error.code, this.video.error))
  173. }
  174. }
  175. get hasStart () {
  176. return this._hasStart
  177. }
  178. set hasStart (bool) {
  179. if (typeof bool === 'boolean' && bool === true && !this._hasStart) {
  180. this._hasStart = true
  181. this.emit('hasstart')
  182. }
  183. }
  184. destroy () {
  185. if (this.textTrackStyle) {
  186. this.textTrackStyle.parentNode.removeChild(this.textTrackStyle)
  187. }
  188. }
  189. play () {
  190. return this.video.play()
  191. }
  192. pause () {
  193. this.video.pause()
  194. }
  195. canPlayType (type) {
  196. return this.video.canPlayType(type)
  197. }
  198. getBufferedRange () {
  199. let range = [0, 0]
  200. let video = this.video
  201. let buffered = video.buffered
  202. let currentTime = video.currentTime
  203. if (buffered) {
  204. for (let i = 0, len = buffered.length; i < len; i++) {
  205. range[0] = buffered.start(i)
  206. range[1] = buffered.end(i)
  207. if (range[0] <= currentTime && currentTime <= range[1]) {
  208. break
  209. }
  210. }
  211. }
  212. if (range[0] - currentTime <= 0 && currentTime - range[1] <= 0) {
  213. return range
  214. } else {
  215. return [0, 0]
  216. }
  217. }
  218. set autoplay (isTrue) {
  219. this.video.autoplay = isTrue
  220. }
  221. get autoplay () {
  222. return this.video.autoplay
  223. }
  224. get buffered () {
  225. return this.video.buffered
  226. }
  227. get crossOrigin () {
  228. return this.video.crossOrigin
  229. }
  230. set crossOrigin (isTrue) {
  231. this.video.crossOrigin = isTrue
  232. }
  233. get currentSrc () {
  234. return this.video.currentSrc
  235. }
  236. get currentTime () {
  237. if(this.video) {
  238. return this.video.currentTime || 0
  239. } else {
  240. return 0
  241. }
  242. }
  243. set currentTime (time) {
  244. if (typeof isFinite === 'function' && !isFinite(time)) return
  245. if (util.hasClass(this.root, 'xgplayer-ended')) {
  246. this.once('playing', () => { this.video.currentTime = time })
  247. this.replay()
  248. } else {
  249. this.video.currentTime = time
  250. }
  251. this.emit('currentTimeChange')
  252. }
  253. get defaultMuted () {
  254. return this.video.defaultMuted
  255. }
  256. set defaultMuted (isTrue) {
  257. this.video.defaultMuted = isTrue
  258. }
  259. get duration () {
  260. return this.video.duration
  261. }
  262. get ended () {
  263. if(this.video) {
  264. return this.video.ended || false
  265. } else {
  266. return true
  267. }
  268. }
  269. get error () {
  270. let err = this.video.error
  271. if (!err) {
  272. return null
  273. }
  274. let status = [{
  275. en: 'MEDIA_ERR_ABORTED',
  276. cn: '取回过程被用户中止'
  277. }, {
  278. en: 'MEDIA_ERR_NETWORK',
  279. cn: '当下载时发生错误'
  280. }, {
  281. en: 'MEDIA_ERR_DECODE',
  282. cn: '当解码时发生错误'
  283. }, {
  284. en: 'MEDIA_ERR_SRC_NOT_SUPPORTED',
  285. cn: '不支持音频/视频'
  286. }]
  287. return this.lang ? this.lang[status[err.code - 1].en] : status[err.code - 1].en
  288. }
  289. get loop () {
  290. return this.video.loop
  291. }
  292. set loop (isTrue) {
  293. this.video.loop = isTrue
  294. }
  295. get muted () {
  296. return this.video.muted
  297. }
  298. set muted (isTrue) {
  299. this.video.muted = isTrue
  300. }
  301. get networkState () {
  302. let status = [{
  303. en: 'NETWORK_EMPTY',
  304. cn: '音频/视频尚未初始化'
  305. }, {
  306. en: 'NETWORK_IDLE',
  307. cn: '音频/视频是活动的且已选取资源,但并未使用网络'
  308. }, {
  309. en: 'NETWORK_LOADING',
  310. cn: '浏览器正在下载数据'
  311. }, {
  312. en: 'NETWORK_NO_SOURCE',
  313. cn: '未找到音频/视频来源'
  314. }]
  315. return this.lang ? this.lang[status[this.video.networkState].en] : status[this.video.networkState].en
  316. }
  317. get paused () {
  318. return this.video.paused
  319. }
  320. get playbackRate () {
  321. return this.video.playbackRate
  322. }
  323. set playbackRate (rate) {
  324. this.video.playbackRate = rate
  325. }
  326. get played () {
  327. return this.video.played
  328. }
  329. get preload () {
  330. return this.video.preload
  331. }
  332. set preload (isTrue) {
  333. this.video.preload = isTrue
  334. }
  335. get readyState () {
  336. let status = [{
  337. en: 'HAVE_NOTHING',
  338. cn: '没有关于音频/视频是否就绪的信息'
  339. }, {
  340. en: 'HAVE_METADATA',
  341. cn: '关于音频/视频就绪的元数据'
  342. }, {
  343. en: 'HAVE_CURRENT_DATA',
  344. cn: '关于当前播放位置的数据是可用的,但没有足够的数据来播放下一帧/毫秒'
  345. }, {
  346. en: 'HAVE_FUTURE_DATA',
  347. cn: '当前及至少下一帧的数据是可用的'
  348. }, {
  349. en: 'HAVE_ENOUGH_DATA',
  350. cn: '可用数据足以开始播放'
  351. }]
  352. return this.lang ? this.lang[status[this.video.readyState].en] : status[this.video.readyState]
  353. }
  354. get seekable () {
  355. return this.video.seekable
  356. }
  357. get seeking () {
  358. return this.video.seeking
  359. }
  360. get src () {
  361. return this.video.src
  362. }
  363. set src (url) {
  364. let self = this
  365. if (!util.hasClass(this.root, 'xgplayer-ended')) {
  366. this.emit('urlchange', JSON.parse(JSON.stringify(self.logParams)))
  367. }
  368. this.logParams = {
  369. bc: 0,
  370. bu_acu_t: 0,
  371. played: [],
  372. pt: new Date().getTime(),
  373. vt: new Date().getTime(),
  374. vd: 0
  375. }
  376. this.video.pause()
  377. this.video.src = url
  378. this.emit('srcChange')
  379. this.logParams.playSrc = url
  380. this.logParams.pt = new Date().getTime()
  381. this.logParams.vt = this.logParams.pt
  382. function ldFunc () {
  383. self.logParams.vt = new Date().getTime()
  384. if (self.logParams.pt > self.logParams.vt) {
  385. self.logParams.pt = self.logParams.vt
  386. }
  387. self.logParams.vd = self.video.duration
  388. self.off('loadeddata', ldFunc)
  389. }
  390. this.once('loadeddata', ldFunc)
  391. }
  392. set poster (posterUrl) {
  393. let poster = util.findDom(this.root, '.xgplayer-poster')
  394. if (poster) {
  395. poster.style.backgroundImage = `url(${posterUrl})`
  396. }
  397. }
  398. get volume () {
  399. return this.video.volume
  400. }
  401. set volume (vol) {
  402. this.video.volume = vol
  403. }
  404. get fullscreen () {
  405. return util.hasClass(this.root, 'xgplayer-is-fullscreen') || util.hasClass(this.root, 'xgplayer-fullscreen-active')
  406. }
  407. get bullet () {
  408. return util.findDom(this.root, 'xg-danmu') ? util.hasClass(util.findDom(this.root, 'xg-danmu'), 'xgplayer-has-danmu') : false
  409. }
  410. get textTrack () {
  411. return util.hasClass(this.root, 'xgplayer-is-textTrack')
  412. }
  413. get pip () {
  414. return util.hasClass(this.root, 'xgplayer-pip-active')
  415. }
  416. }
  417. export default Proxy