视频播放器仓库

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. /**
  2. * This is a HoC that finds a single
  3. * <video> in a component and makes
  4. * all its PROPERTIES available as props.
  5. */
  6. import React, { Component } from 'react';
  7. import { findDOMNode } from 'react-dom';
  8. import {
  9. EVENTS,
  10. PROPERTIES,
  11. TRACKEVENTS
  12. } from './constants';
  13. const defaultMapStateToProps = (state = {}) => Object.assign({
  14. video: {
  15. ...state
  16. }
  17. });
  18. const defaultMapVideoElToProps = (videoEl) => ({
  19. videoEl
  20. });
  21. const defaultMergeProps = (
  22. stateProps = {},
  23. videoElProps = {},
  24. ownProps = {}
  25. ) => Object.assign({}, stateProps, videoElProps, ownProps);
  26. export default (
  27. BaseComponent,
  28. mapStateToProps = defaultMapStateToProps,
  29. mapVideoElToProps = defaultMapVideoElToProps,
  30. mergeProps = defaultMergeProps
  31. ) => class Video extends Component {
  32. constructor(props) {
  33. super(props);
  34. this.updateState = this.updateState.bind(this);
  35. this.state = {};
  36. }
  37. updateState() {
  38. this.setState(
  39. PROPERTIES.reduce((p, c) => {
  40. p[c] = this.videoEl && this.videoEl[c];
  41. if (c == 'playbackrates' && this.videoEl) {
  42. if (this.videoEl.dataset && this.videoEl.dataset[c]) {
  43. p[c] = JSON.parse(this.videoEl.dataset[c]);
  44. } else {
  45. p[c] = JSON.parse(this.videoEl.getAttribute("data-" + c));
  46. }
  47. }
  48. return p;
  49. }, {})
  50. );
  51. }
  52. bindEventsToUpdateState() {
  53. EVENTS.forEach(event => {
  54. if (this.videoEl.addEventListener) {
  55. this.videoEl.addEventListener(event.toLowerCase(), this.updateState);
  56. } else {
  57. this.videoEl.attachEvent("on" + event.toLowerCase(), this.updateState);
  58. }
  59. });
  60. TRACKEVENTS.forEach(event => {
  61. // TODO: JSDom does not have this method on
  62. // `textTracks`. Investigate so we can test this without this check.
  63. this.videoEl.textTracks && this.videoEl.textTracks.addEventListener
  64. && this.videoEl.textTracks.addEventListener(event.toLowerCase(), this.updateState);
  65. });
  66. // If <source> elements are used instead of a src attribute then
  67. // errors for unsupported format do not bubble up to the <video>.
  68. // Do this manually by listening to the last <source> error event
  69. // to force an update.
  70. // https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Using_HTML5_audio_and_video
  71. const sources = this.videoEl.getElementsByTagName('source');
  72. if (sources.length) {
  73. const lastSource = sources[sources.length - 1];
  74. lastSource.addEventListener ? lastSource.addEventListener('error', this.updateState) : lastSource.attachEvent('error', this.updateState);
  75. }
  76. }
  77. unbindEvents() {
  78. EVENTS.forEach(event => {
  79. this.videoEl.removeEventListener ? this.videoEl.removeEventListener(event.toLowerCase(), this.updateState) :
  80. this.videoEl.detachEvent("on" + event.toLowerCase(), this.updateState);
  81. });
  82. TRACKEVENTS.forEach(event => {
  83. // TODO: JSDom does not have this method on
  84. // `textTracks`. Investigate so we can test this without this check.
  85. this.videoEl.textTracks && this.videoEl.textTracks.removeEventListener
  86. && this.videoEl.textTracks.removeEventListener(event.toLowerCase(), this.updateState);
  87. });
  88. const sources = this.videoEl.getElementsByTagName('source');
  89. if (sources.length) {
  90. const lastSource = sources[sources.length - 1];
  91. lastSource.removeEventListener ? lastSource.removeEventListener('error', this.updateState) :
  92. lastSource.detachEvent('onerror', this.updateState);
  93. }
  94. }
  95. componentWillUnmount() {
  96. this.unbindEvents();
  97. }
  98. // Stop `this.el` from being null briefly on every render,
  99. // see: https://github.com/mderrick/react-html5video/pull/65
  100. setRef(el) {
  101. this.el = findDOMNode(el);
  102. }
  103. componentDidMount() {
  104. this.videoEl = this.el.getElementsByTagName('video')[0];
  105. this.bindEventsToUpdateState();
  106. }
  107. render() {
  108. const stateProps = mapStateToProps(
  109. this.state,
  110. this.props
  111. );
  112. const videoElProps = mapVideoElToProps(
  113. this.videoEl,
  114. this.state,
  115. this.props
  116. );
  117. return (
  118. <div ref={this.setRef.bind(this)} style={{ height: '100%' }}>
  119. <BaseComponent
  120. {...mergeProps(
  121. stateProps,
  122. videoElProps,
  123. this.props)} />
  124. </div>
  125. );
  126. }
  127. }