您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

AbstractVideoManager.js 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. /* @flow */
  2. /* eslint-disable no-invalid-this */
  3. import throttle from 'lodash/throttle';
  4. import { Component } from 'react';
  5. import { sendAnalytics, createSharedVideoEvent as createEvent } from '../../../analytics';
  6. import { getCurrentConference } from '../../../base/conference';
  7. import { MEDIA_TYPE } from '../../../base/media';
  8. import { getLocalParticipant } from '../../../base/participants';
  9. import { isLocalTrackMuted } from '../../../base/tracks';
  10. import { dockToolbox } from '../../../toolbox/actions.web';
  11. import { muteLocal } from '../../../video-menu/actions.any';
  12. import { setSharedVideoStatus } from '../../actions.any';
  13. export const PLAYBACK_STATES = {
  14. PLAYING: 'playing',
  15. PAUSED: 'pause',
  16. STOPPED: 'stop'
  17. };
  18. /**
  19. * Return true if the diffenrece between the two timees is larger than 5.
  20. *
  21. * @param {number} newTime - The current time.
  22. * @param {number} previousTime - The previous time.
  23. * @private
  24. * @returns {boolean}
  25. */
  26. function shouldSeekToPosition(newTime, previousTime) {
  27. return Math.abs(newTime - previousTime) > 5;
  28. }
  29. /**
  30. * The type of the React {@link Component} props of {@link YoutubeLargeVideo}.
  31. */
  32. export type Props = {
  33. /**
  34. * The current coference
  35. */
  36. _conference: Object,
  37. /**
  38. * Docks the toolbox
  39. */
  40. _dockToolbox: Function,
  41. /**
  42. * Indicates whether the local audio is muted
  43. */
  44. _isLocalAudioMuted: boolean,
  45. /**
  46. * Is the video shared by the local user.
  47. *
  48. * @private
  49. */
  50. _isOwner: boolean,
  51. /**
  52. * Store flag for muted state
  53. */
  54. _muted: boolean,
  55. /**
  56. * Mutes local audio track
  57. */
  58. _muteLocal: Function,
  59. /**
  60. * The shared video owner id
  61. */
  62. _ownerId: string,
  63. /**
  64. * Updates the shared video status
  65. */
  66. _setSharedVideoStatus: Function,
  67. /**
  68. * The shared video status
  69. */
  70. _status: string,
  71. /**
  72. * Seek time in seconds.
  73. *
  74. */
  75. _time: number,
  76. /**
  77. * The video url
  78. */
  79. _videoUrl: string,
  80. /**
  81. * The video id
  82. */
  83. videoId: string
  84. }
  85. /**
  86. * Manager of shared video.
  87. */
  88. class AbstractVideoManager extends Component<Props> {
  89. throttledFireUpdateSharedVideoEvent: Function;
  90. /**
  91. * Initializes a new instance of AbstractVideoManager.
  92. *
  93. * @returns {void}
  94. */
  95. constructor() {
  96. super();
  97. this.throttledFireUpdateSharedVideoEvent = throttle(this.fireUpdateSharedVideoEvent.bind(this), 5000);
  98. // selenium tests handler
  99. window._sharedVideoPlayer = this;
  100. }
  101. /**
  102. * Implements React Component's componentDidMount.
  103. *
  104. * @inheritdoc
  105. */
  106. componentDidMount() {
  107. this.props._dockToolbox(true);
  108. this.processUpdatedProps();
  109. }
  110. /**
  111. * Implements React Component's componentDidUpdate.
  112. *
  113. * @inheritdoc
  114. */
  115. componentDidUpdate(prevProps: Props) {
  116. const { _videoUrl } = this.props;
  117. if (prevProps._videoUrl !== _videoUrl) {
  118. sendAnalytics(createEvent('started'));
  119. }
  120. this.processUpdatedProps();
  121. }
  122. /**
  123. * Implements React Component's componentWillUnmount.
  124. *
  125. * @inheritdoc
  126. */
  127. componentWillUnmount() {
  128. sendAnalytics(createEvent('stopped'));
  129. if (this.dispose) {
  130. this.dispose();
  131. }
  132. this.props._dockToolbox(false);
  133. }
  134. /**
  135. * Processes new properties.
  136. *
  137. * @returns {void}
  138. */
  139. processUpdatedProps() {
  140. const { _status, _time, _isOwner, _muted } = this.props;
  141. if (_isOwner) {
  142. return;
  143. }
  144. const playerTime = this.getTime();
  145. if (shouldSeekToPosition(_time, playerTime)) {
  146. this.seek(_time);
  147. }
  148. if (this.getPlaybackState() !== _status) {
  149. if (_status === PLAYBACK_STATES.PLAYING) {
  150. this.play();
  151. }
  152. if (_status === PLAYBACK_STATES.PAUSED) {
  153. this.pause();
  154. }
  155. }
  156. if (this.isMuted() !== _muted) {
  157. if (_muted) {
  158. this.mute();
  159. } else {
  160. this.unMute();
  161. }
  162. }
  163. }
  164. /**
  165. * Handle video playing.
  166. *
  167. * @returns {void}
  168. */
  169. onPlay() {
  170. this.smartAudioMute();
  171. sendAnalytics(createEvent('play'));
  172. this.fireUpdateSharedVideoEvent();
  173. }
  174. /**
  175. * Handle video paused.
  176. *
  177. * @returns {void}
  178. */
  179. onPause() {
  180. sendAnalytics(createEvent('paused'));
  181. this.fireUpdateSharedVideoEvent();
  182. }
  183. /**
  184. * Handle volume changed.
  185. *
  186. * @returns {void}
  187. */
  188. onVolumeChange() {
  189. const volume = this.getVolume();
  190. const muted = this.isMuted();
  191. if (volume > 0 && !muted) {
  192. this.smartAudioMute();
  193. }
  194. sendAnalytics(createEvent(
  195. 'volume.changed',
  196. {
  197. volume,
  198. muted
  199. }));
  200. this.fireUpdatePlayingVideoEvent();
  201. }
  202. /**
  203. * Handle changes to the shared playing video.
  204. *
  205. * @returns {void}
  206. */
  207. fireUpdatePlayingVideoEvent() {
  208. if (this.getPlaybackState() === PLAYBACK_STATES.PLAYING) {
  209. this.fireUpdateSharedVideoEvent();
  210. }
  211. }
  212. /**
  213. * Dispatches an update action for the shared video.
  214. *
  215. * @returns {void}
  216. */
  217. fireUpdateSharedVideoEvent() {
  218. const { _isOwner } = this.props;
  219. if (!_isOwner) {
  220. return;
  221. }
  222. const status = this.getPlaybackState();
  223. if (!Object.values(PLAYBACK_STATES).includes(status)) {
  224. return;
  225. }
  226. const {
  227. _ownerId,
  228. _setSharedVideoStatus,
  229. _videoUrl
  230. } = this.props;
  231. _setSharedVideoStatus({
  232. videoUrl: _videoUrl,
  233. status,
  234. time: this.getTime(),
  235. ownerId: _ownerId,
  236. muted: this.isMuted()
  237. });
  238. }
  239. /**
  240. * Indicates if the player volume is currently on. This will return true if
  241. * we have an available player, which is currently in a PLAYING state,
  242. * which isn't muted and has it's volume greater than 0.
  243. *
  244. * @returns {boolean} Indicating if the volume of the shared video is
  245. * currently on.
  246. */
  247. isSharedVideoVolumeOn() {
  248. return this.getPlaybackState() === PLAYBACK_STATES.PLAYING
  249. && !this.isMuted()
  250. && this.getVolume() > 0;
  251. }
  252. /**
  253. * Smart mike mute. If the mike isn't currently muted and the shared video
  254. * volume is on we mute the mike.
  255. *
  256. * @returns {void}
  257. */
  258. smartAudioMute() {
  259. const { _isLocalAudioMuted, _muteLocal } = this.props;
  260. if (!_isLocalAudioMuted
  261. && this.isSharedVideoVolumeOn()) {
  262. sendAnalytics(createEvent('audio.muted'));
  263. _muteLocal(true);
  264. }
  265. }
  266. /**
  267. * Seeks video to provided time
  268. * @param {number} time
  269. */
  270. seek: (time: number) => void;
  271. /**
  272. * Indicates the playback state of the video
  273. */
  274. getPlaybackState: () => boolean;
  275. /**
  276. * Indicates whether the video is muted
  277. */
  278. isMuted: () => boolean;
  279. /**
  280. * Retrieves current volume
  281. */
  282. getVolume: () => number;
  283. /**
  284. * Sets current volume
  285. */
  286. setVolume: (value: number) => void;
  287. /**
  288. * Plays video
  289. */
  290. play: () => void;
  291. /**
  292. * Pauses video
  293. */
  294. pause: () => void;
  295. /**
  296. * Mutes video
  297. */
  298. mute: () => void;
  299. /**
  300. * Unmutes video
  301. */
  302. unMute: () => void;
  303. /**
  304. * Retrieves current time
  305. */
  306. getTime: () => number;
  307. /**
  308. * Disposes current video player
  309. */
  310. dispose: () => void;
  311. }
  312. export default AbstractVideoManager;
  313. /**
  314. * Maps part of the Redux store to the props of this component.
  315. *
  316. * @param {Object} state - The Redux state.
  317. * @returns {Props}
  318. */
  319. export function _mapStateToProps(state: Object): $Shape<Props> {
  320. const { ownerId, status, time, videoUrl, muted } = state['features/shared-video'];
  321. const localParticipant = getLocalParticipant(state);
  322. const _isLocalAudioMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.AUDIO);
  323. return {
  324. _conference: getCurrentConference(state),
  325. _isLocalAudioMuted,
  326. _isOwner: ownerId === localParticipant.id,
  327. _muted: muted,
  328. _ownerId: ownerId,
  329. _status: status,
  330. _time: time,
  331. _videoUrl: videoUrl
  332. };
  333. }
  334. /**
  335. * Maps part of the props of this component to Redux actions.
  336. *
  337. * @param {Function} dispatch - The Redux dispatch function.
  338. * @returns {Props}
  339. */
  340. export function _mapDispatchToProps(dispatch: Function): $Shape<Props> {
  341. return {
  342. _dockToolbox: value => {
  343. dispatch(dockToolbox(value));
  344. },
  345. _muteLocal: value => {
  346. dispatch(muteLocal(value, MEDIA_TYPE.AUDIO));
  347. },
  348. _setSharedVideoStatus: ({ videoUrl, status, time, ownerId, muted }) => {
  349. dispatch(setSharedVideoStatus({
  350. videoUrl,
  351. status,
  352. time,
  353. ownerId,
  354. muted
  355. }));
  356. }
  357. };
  358. }