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:
parent
03b4a32dd7
commit
1e84f993b4
|
@ -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;
|
|
@ -3,6 +3,8 @@ import { connect } from 'react-redux';
|
||||||
|
|
||||||
import AbstractVideoTrack from '../AbstractVideoTrack';
|
import AbstractVideoTrack from '../AbstractVideoTrack';
|
||||||
|
|
||||||
|
import Video from './Video';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that renders a video element for a passed in video track.
|
* Component that renders a video element for a passed in video track.
|
||||||
*
|
*
|
||||||
|
@ -42,82 +44,6 @@ class VideoTrack extends AbstractVideoTrack {
|
||||||
id: React.PropTypes.string
|
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.
|
* Renders the video element.
|
||||||
*
|
*
|
||||||
|
@ -125,78 +51,15 @@ class VideoTrack extends AbstractVideoTrack {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
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 (
|
return (
|
||||||
<div>
|
<Video
|
||||||
<video
|
|
||||||
autoPlay = { true }
|
autoPlay = { true }
|
||||||
className = { this.props.className }
|
className = { this.props.className }
|
||||||
id = { this.props.id }
|
id = { this.props.id }
|
||||||
ref = { this._setVideoElement } />
|
onVideoPlaying = { this._onVideoPlaying }
|
||||||
</div>
|
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);
|
export default connect()(VideoTrack);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import { VideoTrack } from '../../base/media';
|
import { Video } from '../../base/media';
|
||||||
|
|
||||||
const VIDEO_ERROR_CLASS = 'video-preview-has-error';
|
const VIDEO_ERROR_CLASS = 'video-preview-has-error';
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ class VideoInputPreview extends Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className = { className }>
|
<div className = { className }>
|
||||||
<VideoTrack
|
<Video
|
||||||
className = 'video-input-preview-display flipVideoX'
|
className = 'video-input-preview-display flipVideoX'
|
||||||
triggerOnPlayingUpdate = { false }
|
triggerOnPlayingUpdate = { false }
|
||||||
videoTrack = {{ jitsiTrack: this.props.track }} />
|
videoTrack = {{ jitsiTrack: this.props.track }} />
|
||||||
|
|
Loading…
Reference in New Issue