fix(device-selection): use non-connect video preview

Filmstrip only mode displays a device selection dialog that
does not have access to the redux/connect. However, the current
VideoTrack extends from AbstractVideoTrack, which assumes a
redux connection. The fix is to move video display logic into
a separate component and have device selection use that, while
the existing VideoTrack remains connected to redux but
uses the new video display component.
This commit is contained in:
Leonard Kim 2017-08-30 16:58:19 -07:00 committed by hristoterezov
parent 03b4a32dd7
commit 1e84f993b4
3 changed files with 228 additions and 147 deletions

View File

@ -0,0 +1,218 @@
import React, { Component } from 'react';
/**
* Component that renders a video element for a passed in video track.
*
* @extends Component
*/
class Video extends Component {
/**
* Default values for {@code Video} component's properties.
*
* @static
*/
static defaultProps = {
className: '',
id: ''
};
/**
* {@code Video} component's property types.
*
* @static
*/
static propTypes = {
/**
* CSS classes to add to the video element.
*/
className: React.PropTypes.string,
/**
* The value of the id attribute of the video. Used by the torture tests
* to locate video elements.
*/
id: React.PropTypes.string,
/**
* Optional callback to invoke once the video starts playing.
*/
onVideoPlaying: React.PropTypes.func,
/**
* The JitsiLocalTrack to display.
*/
videoTrack: React.PropTypes.object
};
/**
* Initializes a new {@code Video} instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props) {
super(props);
/**
* The internal reference to the DOM/HTML element intended for
* displaying a video. This element may be an HTML video element or a
* temasys video object.
*
* @private
* @type {HTMLVideoElement|Object}
*/
this._videoElement = null;
// Bind event handlers so they are only bound once for every instance.
this._onVideoPlaying = this._onVideoPlaying.bind(this);
this._setVideoElement = this._setVideoElement.bind(this);
}
/**
* Invokes the library for rendering the video on initial display. Sets the
* volume level to zero to ensure no sound plays.
*
* @inheritdoc
* @returns {void}
*/
componentDidMount() {
// Add these attributes directly onto the video element so temasys can
// use them when converting the video to an object.
this._videoElement.volume = 0;
this._videoElement.onplaying = this._onVideoPlaying;
this._attachTrack(this.props.videoTrack);
}
/**
* Remove any existing associations between the current video track and the
* component's video element.
*
* @inheritdoc
* @returns {void}
*/
componentWillUnmount() {
this._detachTrack(this.props.videoTrack);
}
/**
* Updates the video display only if a new track is added. This component's
* updating is blackboxed from React to prevent re-rendering of video
* element, as the lib uses track.attach(videoElement) instead. Also,
* re-rendering cannot be used with temasys, which replaces video elements
* with an object.
*
* @inheritdoc
* @returns {boolean} - False is always returned to blackbox this component.
* from React.
*/
shouldComponentUpdate(nextProps) {
const currentJitsiTrack = this.props.videoTrack
&& this.props.videoTrack.jitsiTrack;
const nextJitsiTrack = nextProps.videoTrack
&& nextProps.videoTrack.jitsiTrack;
if (currentJitsiTrack !== nextJitsiTrack) {
this._detachTrack(this.props.videoTrack);
this._attachTrack(nextProps.videoTrack);
}
return false;
}
/**
* Renders the video element.
*
* @override
* @returns {ReactElement}
*/
render() {
// The wrapping div is necessary because temasys will replace the video
// with an object but react will keep expecting the video element. The
// div gives a constant element for react to keep track of.
return (
<div>
<video
autoPlay = { true }
className = { this.props.className }
id = { this.props.id }
ref = { this._setVideoElement } />
</div>
);
}
/**
* Calls into the passed in track to associate the track with the
* component's video element and render video.
*
* @param {Object} videoTrack - The redux representation of the
* {@code JitsiLocalTrack}.
* @private
* @returns {void}
*/
_attachTrack(videoTrack) {
if (!videoTrack || !videoTrack.jitsiTrack) {
return;
}
const updatedVideoElement
= videoTrack.jitsiTrack.attach(this._videoElement);
// Sets the instance variable for the video element again as the element
// maybe have been replaced with a new object by temasys.
this._setVideoElement(updatedVideoElement);
}
/**
* Removes the association to the component's video element from the passed
* in redux representation of jitsi video track to stop the track from
* rendering. With temasys, the video element must still be visible for
* detaching to complete.
*
* @param {Object} videoTrack - The redux representation of the
* {@code JitsiLocalTrack}.
* @private
* @returns {void}
*/
_detachTrack(videoTrack) {
// Detach the video element from the track only if it has already
// been attached. This accounts for a special case with temasys
// where if detach is being called before attach, the video
// element is converted to Object without updating this
// component's reference to the video element.
if (this._videoElement
&& videoTrack
&& videoTrack.jitsiTrack
&& videoTrack.jitsiTrack.containers.includes(this._videoElement)) {
videoTrack.jitsiTrack.detach(this._videoElement);
}
}
/**
* Invokes the onvideoplaying callback if defined.
*
* @private
* @returns {void}
*/
_onVideoPlaying() {
if (this.props.onVideoPlaying) {
this.props.onVideoPlaying();
}
}
/**
* Sets an instance variable for the component's video element so it can be
* referenced later for attaching and detaching a JitsiLocalTrack.
*
* @param {Object} element - DOM element for the component's video display.
* @private
* @returns {void}
*/
_setVideoElement(element) {
this._videoElement = element;
}
}
export default Video;

View File

@ -3,6 +3,8 @@ import { connect } from 'react-redux';
import AbstractVideoTrack from '../AbstractVideoTrack';
import Video from './Video';
/**
* Component that renders a video element for a passed in video track.
*
@ -42,82 +44,6 @@ class VideoTrack extends AbstractVideoTrack {
id: React.PropTypes.string
};
/**
* Initializes a new VideoTrack instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props) {
super(props);
/**
* The internal reference to the DOM/HTML element intended for
* displaying a video. This element may be an HTML video element or a
* temasys video object.
*
* @private
* @type {HTMLVideoElement|Object}
*/
this._videoElement = null;
// Bind event handlers so they are only bound once for every instance.
this._setVideoElement = this._setVideoElement.bind(this);
}
/**
* Invokes the library for rendering the video on initial display. Sets the
* volume level to zero to ensure no sound plays.
*
* @inheritdoc
* @returns {void}
*/
componentDidMount() {
// Add these attributes directly onto the video element so temasys can
// use them when converting the video to an object.
this._videoElement.volume = 0;
this._videoElement.onplaying = this._onVideoPlaying;
this._attachTrack(this.props.videoTrack);
}
/**
* Remove any existing associations between the current video track and the
* component's video element.
*
* @inheritdoc
* @returns {void}
*/
componentWillUnmount() {
this._detachTrack(this.props.videoTrack);
}
/**
* Updates the video display only if a new track is added. This component's
* updating is blackboxed from React to prevent re-rendering of video
* element, as the lib uses track.attach(videoElement) instead. Also,
* re-rendering cannot be used with temasys, which replaces video elements
* with an object.
*
* @inheritdoc
* @returns {boolean} - False is always returned to blackbox this component.
* from React.
*/
shouldComponentUpdate(nextProps) {
const currentJitsiTrack = this.props.videoTrack
&& this.props.videoTrack.jitsiTrack;
const nextJitsiTrack = nextProps.videoTrack
&& nextProps.videoTrack.jitsiTrack;
if (currentJitsiTrack !== nextJitsiTrack) {
this._detachTrack(this.props.videoTrack);
this._attachTrack(nextProps.videoTrack);
}
return false;
}
/**
* Renders the video element.
*
@ -125,78 +51,15 @@ class VideoTrack extends AbstractVideoTrack {
* @returns {ReactElement}
*/
render() {
// The wrapping div is necessary because temasys will replace the video
// with an object but react will keep expecting the video element. The
// div gives a constant element for react to keep track of.
return (
<div>
<video
<Video
autoPlay = { true }
className = { this.props.className }
id = { this.props.id }
ref = { this._setVideoElement } />
</div>
onVideoPlaying = { this._onVideoPlaying }
videoTrack = { this.props.videoTrack } />
);
}
/**
* Calls into the passed in track to associate the track with the
* component's video element and render video.
*
* @param {Object} videoTrack - The redux representation of the
* {@code JitsiLocalTrack}.
* @private
* @returns {void}
*/
_attachTrack(videoTrack) {
if (!videoTrack || !videoTrack.jitsiTrack) {
return;
}
const updatedVideoElement
= videoTrack.jitsiTrack.attach(this._videoElement);
// Sets the instance variable for the video element again as the element
// maybe have been replaced with a new object by temasys.
this._setVideoElement(updatedVideoElement);
}
/**
* Removes the association to the component's video element from the passed
* in redux representation of jitsi video track to stop the track from
* rendering. With temasys, the video element must still be visible for
* detaching to complete.
*
* @param {Object} videoTrack - The redux representation of the
* {@code JitsiLocalTrack}.
* @private
* @returns {void}
*/
_detachTrack(videoTrack) {
// Detach the video element from the track only if it has already
// been attached. This accounts for a special case with temasys
// where if detach is being called before attach, the video
// element is converted to Object without updating this
// component's reference to the video element.
if (this._videoElement
&& videoTrack
&& videoTrack.jitsiTrack
&& videoTrack.jitsiTrack.containers.includes(this._videoElement)) {
videoTrack.jitsiTrack.detach(this._videoElement);
}
}
/**
* Sets an instance variable for the component's video element so it can be
* referenced later for attaching and detaching a JitsiLocalTrack.
*
* @param {Object} element - DOM element for the component's video display.
* @private
* @returns {void}
*/
_setVideoElement(element) {
this._videoElement = element;
}
}
export default connect()(VideoTrack);

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { VideoTrack } from '../../base/media';
import { Video } from '../../base/media';
const VIDEO_ERROR_CLASS = 'video-preview-has-error';
@ -42,7 +42,7 @@ class VideoInputPreview extends Component {
return (
<div className = { className }>
<VideoTrack
<Video
className = 'video-input-preview-display flipVideoX'
triggerOnPlayingUpdate = { false }
videoTrack = {{ jitsiTrack: this.props.track }} />