feat(local-video): convert to react (#1705)
* feat(local-video): convert to react - Create a VideoTrack component for displaying a video element. This mirrors native also having a VideoTrack component. - The VideoTrack component does not let React update it to prevent the video element from re-rendering, which could cause flickers and would not work with temasys's overriding of the video element. - VideoTrack extends AbstractVideoTrack to mirror native implementation and to get the dispatch of the onplaying event. - Remove the onclick handler on the video element. Honestly, I didn't get it to work, and did not try, but it is also unnecessary because another handler already exists on the video wrapper. * ref(device-selection): VideoInputPreview uses VideoTrack to show video * squash into conversion: change css selectors * squash into conversion: mix in abstract props * squash into conversion: change shouldComponentUpdate check * squash: update comment about why triggerOnPlayingUpdate is used
This commit is contained in:
parent
37328b3995
commit
244de8096f
|
@ -157,8 +157,8 @@
|
||||||
-o-transform: scale(-1, 1);
|
-o-transform: scale(-1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#localVideoWrapper>video,
|
#localVideoWrapper video,
|
||||||
#localVideoWrapper>object {
|
#localVideoWrapper object {
|
||||||
border-radius: $borderRadius !important;
|
border-radius: $borderRadius !important;
|
||||||
cursor: hand;
|
cursor: hand;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
@ -183,8 +183,8 @@
|
||||||
|
|
||||||
#sharedVideo,
|
#sharedVideo,
|
||||||
#etherpad,
|
#etherpad,
|
||||||
#localVideoWrapper>video,
|
#localVideoWrapper video,
|
||||||
#localVideoWrapper>object,
|
#localVideoWrapper object,
|
||||||
#localVideoWrapper,
|
#localVideoWrapper,
|
||||||
#largeVideoWrapper,
|
#largeVideoWrapper,
|
||||||
#largeVideoWrapper>video,
|
#largeVideoWrapper>video,
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
/* global $, config, interfaceConfig, APP, JitsiMeetJS */
|
/* global $, config, interfaceConfig, APP, JitsiMeetJS */
|
||||||
|
|
||||||
|
/* eslint-disable no-unused-vars */
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
|
import { VideoTrack } from '../../../react/features/base/media';
|
||||||
|
/* eslint-enable no-unused-vars */
|
||||||
|
|
||||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||||
|
|
||||||
import UIUtil from "../util/UIUtil";
|
|
||||||
import UIEvents from "../../../service/UI/UIEvents";
|
import UIEvents from "../../../service/UI/UIEvents";
|
||||||
import SmallVideo from "./SmallVideo";
|
import SmallVideo from "./SmallVideo";
|
||||||
|
|
||||||
const RTCUIUtils = JitsiMeetJS.util.RTCUIHelper;
|
|
||||||
const TrackEvents = JitsiMeetJS.events.track;
|
const TrackEvents = JitsiMeetJS.events.track;
|
||||||
|
|
||||||
function LocalVideo(VideoLayout, emitter) {
|
function LocalVideo(VideoLayout, emitter) {
|
||||||
|
@ -85,31 +92,34 @@ LocalVideo.prototype.changeVideo = function (stream) {
|
||||||
localVideoContainerSelector.off('click');
|
localVideoContainerSelector.off('click');
|
||||||
localVideoContainerSelector.on('click', localVideoClick);
|
localVideoContainerSelector.on('click', localVideoClick);
|
||||||
|
|
||||||
let localVideo = document.createElement('video');
|
this.localVideoId = 'localVideo_' + stream.getId();
|
||||||
localVideo.id = this.localVideoId = 'localVideo_' + stream.getId();
|
|
||||||
|
|
||||||
RTCUIUtils.setAutoPlay(localVideo, true);
|
|
||||||
RTCUIUtils.setVolume(localVideo, 0);
|
|
||||||
|
|
||||||
var localVideoContainer = document.getElementById('localVideoWrapper');
|
var localVideoContainer = document.getElementById('localVideoWrapper');
|
||||||
// Put the new video always in front
|
|
||||||
UIUtil.prependChild(localVideoContainer, localVideo);
|
|
||||||
|
|
||||||
// Add click handler to both video and video wrapper elements in case
|
/* jshint ignore:start */
|
||||||
// there's no video.
|
ReactDOM.render(
|
||||||
|
<Provider store = { APP.store }>
|
||||||
// onclick has to be used with Temasys plugin
|
<VideoTrack
|
||||||
localVideo.onclick = localVideoClick;
|
id = { this.localVideoId }
|
||||||
|
videoTrack = {{ jitsiTrack: stream }} />
|
||||||
|
</Provider>,
|
||||||
|
localVideoContainer
|
||||||
|
);
|
||||||
|
/* jshint ignore:end */
|
||||||
|
|
||||||
let isVideo = stream.videoType != "desktop";
|
let isVideo = stream.videoType != "desktop";
|
||||||
this._enableDisableContextMenu(isVideo);
|
this._enableDisableContextMenu(isVideo);
|
||||||
this.setFlipX(isVideo? APP.settings.getLocalFlipX() : false);
|
this.setFlipX(isVideo? APP.settings.getLocalFlipX() : false);
|
||||||
|
|
||||||
// Attach WebRTC stream
|
|
||||||
localVideo = stream.attach(localVideo);
|
|
||||||
|
|
||||||
let endedHandler = () => {
|
let endedHandler = () => {
|
||||||
localVideoContainer.removeChild(localVideo);
|
// Only remove if there is no video and not a transition state.
|
||||||
|
// Previous non-react logic created a new video element with each track
|
||||||
|
// removal whereas react reuses the video component so it could be the
|
||||||
|
// stream ended but a new one is being used.
|
||||||
|
if (this.videoStream.isEnded()) {
|
||||||
|
ReactDOM.unmountComponentAtNode(localVideoContainer);
|
||||||
|
}
|
||||||
|
|
||||||
// when removing only the video element and we are on stage
|
// when removing only the video element and we are on stage
|
||||||
// update the stage
|
// update the stage
|
||||||
if (this.isCurrentlyOnLargeVideo()) {
|
if (this.isCurrentlyOnLargeVideo()) {
|
||||||
|
|
|
@ -12,6 +12,18 @@ import { Video } from './_';
|
||||||
* @abstract
|
* @abstract
|
||||||
*/
|
*/
|
||||||
export default class AbstractVideoTrack extends Component {
|
export default class AbstractVideoTrack extends Component {
|
||||||
|
/**
|
||||||
|
* Default values for AbstractVideoTrack component's properties.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static defaultProps = {
|
||||||
|
/**
|
||||||
|
* Dispatch an action when the video starts playing.
|
||||||
|
*/
|
||||||
|
triggerOnPlayingUpdate: true
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AbstractVideoTrack component's property types.
|
* AbstractVideoTrack component's property types.
|
||||||
*
|
*
|
||||||
|
@ -19,7 +31,18 @@ export default class AbstractVideoTrack extends Component {
|
||||||
*/
|
*/
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
dispatch: React.PropTypes.func,
|
dispatch: React.PropTypes.func,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the store should be updated about the playing status
|
||||||
|
* of the video. Defaults to true. One use case for setting this prop
|
||||||
|
* to false is using multiple locals streams from the same video source,
|
||||||
|
* such as when previewing video. In those cases, the store may have no
|
||||||
|
* need to be updated about the existence or state of the stream.
|
||||||
|
*/
|
||||||
|
triggerOnPlayingUpdate: React.PropTypes.bool,
|
||||||
|
|
||||||
videoTrack: React.PropTypes.object,
|
videoTrack: React.PropTypes.object,
|
||||||
|
|
||||||
waitForVideoStarted: React.PropTypes.bool,
|
waitForVideoStarted: React.PropTypes.bool,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -117,7 +140,9 @@ export default class AbstractVideoTrack extends Component {
|
||||||
_onVideoPlaying() {
|
_onVideoPlaying() {
|
||||||
const videoTrack = this.props.videoTrack;
|
const videoTrack = this.props.videoTrack;
|
||||||
|
|
||||||
if (videoTrack && !videoTrack.videoStarted) {
|
if (this.props.triggerOnPlayingUpdate
|
||||||
|
&& videoTrack
|
||||||
|
&& !videoTrack.videoStarted) {
|
||||||
this.props.dispatch(trackVideoStarted(videoTrack.jitsiTrack));
|
this.props.dispatch(trackVideoStarted(videoTrack.jitsiTrack));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import AbstractVideoTrack from '../AbstractVideoTrack';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that renders a video element for a passed in video track.
|
||||||
|
*
|
||||||
|
* @extends AbstractVideoTrack
|
||||||
|
*/
|
||||||
|
class VideoTrack extends AbstractVideoTrack {
|
||||||
|
/**
|
||||||
|
* Default values for {@code VideoTrack} component's properties.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static defaultProps = {
|
||||||
|
...AbstractVideoTrack.defaultProps,
|
||||||
|
|
||||||
|
className: '',
|
||||||
|
|
||||||
|
id: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code VideoTrack} component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
...AbstractVideoTrack.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
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
|
@ -1 +1,2 @@
|
||||||
export { default as Audio } from './Audio';
|
export { default as Audio } from './Audio';
|
||||||
|
export { default as VideoTrack } from './VideoTrack';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import { translate } from '../../base/i18n';
|
import { VideoTrack } from '../../base/media';
|
||||||
|
|
||||||
const VIDEO_ERROR_CLASS = 'video-preview-has-error';
|
const VIDEO_ERROR_CLASS = 'video-preview-has-error';
|
||||||
|
|
||||||
|
@ -23,86 +23,12 @@ class VideoInputPreview extends Component {
|
||||||
*/
|
*/
|
||||||
error: React.PropTypes.string,
|
error: React.PropTypes.string,
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked to obtain translated strings.
|
|
||||||
*/
|
|
||||||
t: React.PropTypes.func,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The JitsiLocalTrack to display.
|
* The JitsiLocalTrack to display.
|
||||||
*/
|
*/
|
||||||
track: React.PropTypes.object
|
track: React.PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a new VideoInputPreview instance.
|
|
||||||
*
|
|
||||||
* @param {Object} props - The read-only React Component props with which
|
|
||||||
* the new instance is to be initialized.
|
|
||||||
*/
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The internal reference to the DOM/HTML element intended for showing
|
|
||||||
* error messages.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @type {HTMLDivElement}
|
|
||||||
*/
|
|
||||||
this._errorElement = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The internal reference to topmost DOM/HTML element backing the React
|
|
||||||
* {@code Component}. Accessed directly for toggling a classname to
|
|
||||||
* indicate an error is present so styling can be changed to display it.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @type {HTMLDivElement}
|
|
||||||
*/
|
|
||||||
this._rootElement = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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._setErrorElement = this._setErrorElement.bind(this);
|
|
||||||
this._setRootElement = this._setRootElement.bind(this);
|
|
||||||
this._setVideoElement = this._setVideoElement.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the library for rendering the video on initial display.
|
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
componentDidMount() {
|
|
||||||
if (this.props.error) {
|
|
||||||
this._updateErrorView(this.props.error);
|
|
||||||
} else {
|
|
||||||
this._attachTrack(this.props.track);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove any existing associations between the current previewed track and
|
|
||||||
* the component's video element.
|
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
componentWillUnmount() {
|
|
||||||
this._detachTrack(this.props.track);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements React's {@link Component#render()}.
|
* Implements React's {@link Component#render()}.
|
||||||
*
|
*
|
||||||
|
@ -110,146 +36,22 @@ class VideoInputPreview extends Component {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
|
const { error } = this.props;
|
||||||
|
const errorClass = error ? VIDEO_ERROR_CLASS : '';
|
||||||
|
const className = `video-input-preview ${errorClass}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className = { className }>
|
||||||
className = 'video-input-preview'
|
<VideoTrack
|
||||||
ref = { this._setRootElement }>
|
|
||||||
<video
|
|
||||||
autoPlay = { true }
|
|
||||||
className = 'video-input-preview-display flipVideoX'
|
className = 'video-input-preview-display flipVideoX'
|
||||||
ref = { this._setVideoElement } />
|
triggerOnPlayingUpdate = { false }
|
||||||
<div
|
videoTrack = {{ jitsiTrack: this.props.track }} />
|
||||||
className = 'video-input-preview-error'
|
<div className = 'video-input-preview-error'>
|
||||||
ref = { this._setErrorElement } />
|
{ error || '' }
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Only update when the deviceId has changed. This component is somewhat
|
|
||||||
* black-boxed from React's rendering so lib-jitsi-meet can instead handle
|
|
||||||
* updating of the video preview, which takes browser differences into
|
|
||||||
* consideration. For example, temasys's video object must be visible to
|
|
||||||
* update the displayed track, but React's re-rendering could potentially
|
|
||||||
* remove the video object from the page.
|
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
shouldComponentUpdate(nextProps) {
|
|
||||||
const hasNewTrack = nextProps.track !== this.props.track;
|
|
||||||
|
|
||||||
if (hasNewTrack || nextProps.error) {
|
|
||||||
this._detachTrack(this.props.track);
|
|
||||||
this._updateErrorView(nextProps.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Never attempt to show the new track if there is an error present.
|
|
||||||
if (hasNewTrack && !nextProps.error) {
|
|
||||||
this._attachTrack(nextProps.track);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls into the passed in track to associate the track with the
|
|
||||||
* component's video element and render video. Also sets the instance
|
|
||||||
* variable for the video element as the element the track attached to,
|
|
||||||
* which could be an Object if on a temasys supported browser.
|
|
||||||
*
|
|
||||||
* @param {JitsiLocalTrack} track - The library's track model which will be
|
|
||||||
* displayed.
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_attachTrack(track) {
|
|
||||||
if (!track) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedVideoElement = track.attach(this._videoElement);
|
|
||||||
|
|
||||||
this._setVideoElement(updatedVideoElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the association to the component's video element from the passed
|
|
||||||
* in JitsiLocalTrack to stop the track from rendering. With temasys, the
|
|
||||||
* video element must still be visible for detaching to complete.
|
|
||||||
*
|
|
||||||
* @param {JitsiLocalTrack} track - The library's track model which needs
|
|
||||||
* to stop previewing in the video element.
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_detachTrack(track) {
|
|
||||||
// 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
|
|
||||||
&& track
|
|
||||||
&& track.containers.includes(this._videoElement)) {
|
|
||||||
track.detach(this._videoElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets an instance variable for the component's element intended for
|
|
||||||
* displaying error messages. The element will be accessed directly to
|
|
||||||
* display an error message.
|
|
||||||
*
|
|
||||||
* @param {Object} element - DOM element intended for displaying errors.
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_setErrorElement(element) {
|
|
||||||
this._errorElement = element;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the component's root element.
|
|
||||||
*
|
|
||||||
* @param {Object} element - The highest DOM element in the component.
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_setRootElement(element) {
|
|
||||||
this._rootElement = element;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds or removes a class to the component's parent node to indicate an
|
|
||||||
* error has occurred. Also sets the error text.
|
|
||||||
*
|
|
||||||
* @param {string} error - The error message to display. If falsy, error
|
|
||||||
* message display will be hidden.
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_updateErrorView(error) {
|
|
||||||
if (error) {
|
|
||||||
this._rootElement.classList.add(VIDEO_ERROR_CLASS);
|
|
||||||
} else {
|
|
||||||
this._rootElement.classList.remove(VIDEO_ERROR_CLASS);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._errorElement.innerText = error || '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translate(VideoInputPreview);
|
export default VideoInputPreview;
|
||||||
|
|
Loading…
Reference in New Issue