index.jsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import React, { Component, Fragment } from 'react';
  2. import PropTypes from 'prop-types';
  3. import classnames from 'unique-classnames';
  4. import './index.css';
  5. function isIOS() {
  6. return /.*iphone.*/i.test(navigator.userAgent);
  7. }
  8. export default class MediaPlayer extends Component {
  9. static propTypes = {
  10. className: PropTypes.string,
  11. style: PropTypes.object,
  12. stream: PropTypes.object,
  13. client: PropTypes.object,
  14. onClick: PropTypes.func,
  15. };
  16. static defaultProps = {
  17. className: '',
  18. style: {},
  19. stream: {},
  20. client: null,
  21. onClick: () => { },
  22. };
  23. constructor(props) {
  24. super(props);
  25. this.state = {
  26. volume: 0,
  27. stats: {
  28. audioLost: 0,
  29. biggestAudioLost: 0,
  30. videoLost: 0,
  31. biggestVideoLost: 0,
  32. rtt: 0,
  33. biggestRTT: 0
  34. },
  35. }
  36. this.volumeTimer = 0;
  37. this.stateTimer = 0;
  38. this.videoElem = React.createRef();
  39. this.isIOS = isIOS();
  40. }
  41. componentDidMount() {
  42. this.isComponentMounted = true;
  43. const { stream } = this.props;
  44. if (stream.mediaStream) {
  45. this.play(stream.mediaStream);
  46. }
  47. }
  48. componentWillReceiveProps(nextProps) {
  49. if (!nextProps.stream.mediaStream) {
  50. this.stop();
  51. } else if (nextProps.stream.mediaStream !== this.props.stream.mediaStream) {
  52. this.play(nextProps.stream.mediaStream);
  53. }
  54. }
  55. componentWillUnmount() {
  56. this.stop();
  57. this.isComponentMounted = false;
  58. }
  59. play(mediaStream) {
  60. this.videoElem.current.srcObject = mediaStream;
  61. this.startGetVolume();
  62. this.startGetState();
  63. }
  64. stop() {
  65. this.stopGetVolume();
  66. this.stopGetState();
  67. this.videoElem.current.srcObject = null;
  68. }
  69. startGetVolume() {
  70. const { client, stream } = this.props;
  71. if (!client || !stream || !stream.audio) {
  72. return;
  73. }
  74. if (this.volumeTimer) {
  75. clearInterval(this.volumeTimer);
  76. }
  77. this.volumeTimer = setInterval(() => {
  78. const vol = client.getAudioVolume(stream.sid);
  79. this.setState({ volume: vol })
  80. }, 1000);
  81. }
  82. stopGetVolume() {
  83. clearInterval(this.volumeTimer);
  84. }
  85. startGetState() {
  86. const { client, stream } = this.props;
  87. if (!client || !stream || !stream.video) {
  88. return;
  89. }
  90. if (this.stateTimer) {
  91. clearInterval(this.stateTimer);
  92. }
  93. this.stateTimer = setInterval(() => {
  94. client.getAudioStats(stream.sid, (_stats) => {
  95. if (!this.isComponentMounted) return;
  96. const { stats } = this.state;
  97. stats.audioLost = _stats.lostpre;
  98. if (stats.biggestAudioLost < _stats.lostpre) {
  99. stats.biggestAudioLost = _stats.lostpre;
  100. }
  101. this.setState({ stats });
  102. }, (e) => {
  103. console.error('get video stats ', stream.sid);
  104. });
  105. client.getVideoStats(stream.sid, (_stats) => {
  106. if (!this.isComponentMounted) return;
  107. const { stats } = this.state;
  108. stats.videoLost = _stats.lostpre;
  109. if (stats.biggestVideoLost < _stats.lostpre) {
  110. stats.biggestVideoLost = _stats.lostpre;
  111. }
  112. this.setState({ stats });
  113. }, (e) => {
  114. console.error('get video stats ', stream.sid);
  115. });
  116. client.getNetworkStats(stream.sid, (_stats) => {
  117. if (!this.isComponentMounted) return;
  118. const { stats } = this.state;
  119. stats.rtt = _stats.rtt;
  120. if (stats.biggestRTT < _stats.rtt) {
  121. stats.biggestRTT = _stats.rtt;
  122. }
  123. this.setState({ stats });
  124. }, (e) => {
  125. console.error('get network stats ', stream.sid);
  126. });
  127. }, 1000);
  128. }
  129. stopGetState() {
  130. clearInterval(this.stateTimer);
  131. }
  132. handleClick = () => {
  133. const { stream, onClick } = this.props;
  134. onClick && onClick(stream);
  135. }
  136. renderStats = () => {
  137. const { stream } = this.props;
  138. const { volume, stats } = this.state;
  139. return stream.mediaStream
  140. ? <Fragment>
  141. <div style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>音量: {volume} % &nbsp;&nbsp;&nbsp;&nbsp;音频丢包率: {stats.audioLost} %</div>
  142. <div style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>视频丢包率: {stats.videoLost} % &nbsp;&nbsp;&nbsp;&nbsp;网络延时: {stats.rtt} ms</div>
  143. </Fragment>
  144. : null;
  145. }
  146. handleUnmute = () => {
  147. this.videoElem.current.muted = false;
  148. }
  149. renderVideoMask = () => {
  150. if (this.isIOS && this.videoElem.current && this.videoElem.current.muted) {
  151. return (
  152. <div className="muted-mask">
  153. <div className="mask-content">
  154. <div className="hint">由于iOS系统限制,视频自动播放时需要静音,需要您点击下面按钮来取消静音</div>
  155. <button onClick={this.handleUnmute}>取消静音</button>
  156. </div>
  157. </div>
  158. )
  159. }
  160. }
  161. render() {
  162. const { stream, className, style } = this.props;
  163. const classes = classnames('media-player', className);
  164. const hasMediaStream = !!stream.mediaStream;
  165. return (
  166. <div className={classes} style={style} onClick={this.handleClick}>
  167. <div style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>用户ID: {stream.uid}</div>
  168. <div style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>流ID: {stream.sid}</div>
  169. { this.renderStats() }
  170. <div className="video-container" style={{ display: hasMediaStream ? 'block' : 'none' }}>
  171. <video
  172. ref={this.videoElem}
  173. webkit-playsinline="true"
  174. autoPlay
  175. playsInline
  176. muted={this.isIOS}>
  177. </video>
  178. { this.renderVideoMask() }
  179. </div>
  180. <p style={{ display: hasMediaStream ? 'none' : 'block' }}> unsubscribe </p>
  181. </div>
  182. )
  183. }
  184. }