Merge pull request #8192 from lfzawacki/detect-autoplay-problems-external-video
Don't run synchronization code on a player with autoplay blocked and add an autoplay warning
This commit is contained in:
commit
692eec9a94
@ -1,15 +1,23 @@
|
||||
import React, { Component } from 'react';
|
||||
import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ReactPlayer from 'react-player';
|
||||
import { sendMessage, onMessage, removeAllListeners } from './service';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
|
||||
import ArcPlayer from './custom-players/arc-player';
|
||||
|
||||
|
||||
import { styles } from './styles';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
autoPlayWarning: {
|
||||
id: 'app.externalVideo.autoPlayWarning',
|
||||
description: 'Shown when user needs to interact with player to make it work',
|
||||
},
|
||||
});
|
||||
|
||||
const SYNC_INTERVAL_SECONDS = 2;
|
||||
const AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS = 5;
|
||||
|
||||
ReactPlayer.addCustomPlayer(ArcPlayer);
|
||||
|
||||
@ -21,14 +29,26 @@ class VideoPlayer extends Component {
|
||||
|
||||
this.player = null;
|
||||
this.syncInterval = null;
|
||||
this.autoPlayTimeout = null;
|
||||
this.state = {
|
||||
mutedByEchoTest: false,
|
||||
playing: false,
|
||||
hasPlayedBefore: false,
|
||||
autoPlayBlocked: false,
|
||||
playbackRate: 1,
|
||||
};
|
||||
|
||||
this.opts = {
|
||||
controls: isPresenter,
|
||||
file: {
|
||||
attributes: {
|
||||
controls: true,
|
||||
},
|
||||
},
|
||||
dailymotion: {
|
||||
params: {
|
||||
controls: true,
|
||||
},
|
||||
},
|
||||
youtube: {
|
||||
playerVars: {
|
||||
autoplay: 1,
|
||||
@ -43,7 +63,9 @@ class VideoPlayer extends Component {
|
||||
};
|
||||
|
||||
this.registerVideoListeners = this.registerVideoListeners.bind(this);
|
||||
this.autoPlayBlockDetected = this.autoPlayBlockDetected.bind(this);
|
||||
this.clearVideoListeners = this.clearVideoListeners.bind(this);
|
||||
this.handleFirstPlay = this.handleFirstPlay.bind(this);
|
||||
this.handleResize = this.handleResize.bind(this);
|
||||
this.handleOnReady = this.handleOnReady.bind(this);
|
||||
this.handleOnPlay = this.handleOnPlay.bind(this);
|
||||
@ -65,6 +87,7 @@ class VideoPlayer extends Component {
|
||||
this.clearVideoListeners();
|
||||
|
||||
clearInterval(this.syncInterval);
|
||||
clearTimeout(this.autoPlayTimeout);
|
||||
this.player = null;
|
||||
}
|
||||
|
||||
@ -83,6 +106,24 @@ class VideoPlayer extends Component {
|
||||
return { mutedByEchoTest: inEchoTest };
|
||||
}
|
||||
|
||||
autoPlayBlockDetected() {
|
||||
this.setState({autoPlayBlocked: true});
|
||||
}
|
||||
|
||||
handleFirstPlay() {
|
||||
const { isPresenter } = this.props;
|
||||
const { hasPlayedBefore } = this.state;
|
||||
|
||||
if (!hasPlayedBefore) {
|
||||
this.setState({ hasPlayedBefore: true, autoPlayBlocked: false });
|
||||
clearTimeout(this.autoPlayTimeout);
|
||||
|
||||
if (isPresenter) {
|
||||
sendMessage('presenterReady');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentPlaybackRate() {
|
||||
const intPlayer = this.player.getInternalPlayer();
|
||||
|
||||
@ -124,14 +165,29 @@ class VideoPlayer extends Component {
|
||||
|
||||
if (isPresenter) {
|
||||
this.syncInterval = setInterval(() => {
|
||||
const { playing, hasPlayedBefore } = this.state;
|
||||
const curTime = this.player.getCurrentTime();
|
||||
const rate = this.getCurrentPlaybackRate();
|
||||
|
||||
sendMessage('playerUpdate', { rate, time: curTime, state: this.state.playing });
|
||||
// Always pause video if presenter is has not started sharing, e.g., blocked by autoplay
|
||||
const playingState = hasPlayedBefore ? playing : false;
|
||||
|
||||
sendMessage('playerUpdate', { rate, time: curTime, state: playingState });
|
||||
}, SYNC_INTERVAL_SECONDS * 1000);
|
||||
|
||||
onMessage('viewerJoined', () => {
|
||||
const { hasPlayedBefore } = this.state;
|
||||
|
||||
logger.debug({ logCode: 'external_video_viewer_joined' }, 'Viewer joined external video');
|
||||
if (hasPlayedBefore) {
|
||||
sendMessage('presenterReady');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
onMessage('play', ({ time }) => {
|
||||
if (!this.player) {
|
||||
const { hasPlayedBefore } = this.state;
|
||||
|
||||
if (!this.player || !hasPlayedBefore) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -142,7 +198,9 @@ class VideoPlayer extends Component {
|
||||
});
|
||||
|
||||
onMessage('stop', ({ time }) => {
|
||||
if (!this.player) {
|
||||
const { hasPlayedBefore } = this.state;
|
||||
|
||||
if (!this.player || !hasPlayedBefore) {
|
||||
return;
|
||||
}
|
||||
this.player.seekTo(time);
|
||||
@ -151,8 +209,20 @@ class VideoPlayer extends Component {
|
||||
logger.debug({ logCode: 'external_video_client_stop' }, 'Stop external video');
|
||||
});
|
||||
|
||||
onMessage('presenterReady', (data) => {
|
||||
const { hasPlayedBefore } = this.state;
|
||||
|
||||
logger.debug({ logCode: 'external_video_presenter_ready' }, 'Presenter is ready to sync');
|
||||
|
||||
if (!hasPlayedBefore) {
|
||||
this.setState({playing: true});
|
||||
}
|
||||
});
|
||||
|
||||
onMessage('playerUpdate', (data) => {
|
||||
if (!this.player) {
|
||||
const { hasPlayedBefore, playing } = this.state;
|
||||
|
||||
if (!this.player || !hasPlayedBefore) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -176,7 +246,7 @@ class VideoPlayer extends Component {
|
||||
}, 'Seek external video to:');
|
||||
}
|
||||
|
||||
if (this.state.playing !== data.state) {
|
||||
if (playing !== data.state) {
|
||||
this.setState({ playing: data.state });
|
||||
}
|
||||
});
|
||||
@ -185,12 +255,21 @@ class VideoPlayer extends Component {
|
||||
|
||||
handleOnReady() {
|
||||
const { isPresenter } = this.props;
|
||||
const { hasPlayedBefore } = this.state;
|
||||
|
||||
if (hasPlayedBefore) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPresenter) {
|
||||
sendMessage('viewerJoined');
|
||||
} else {
|
||||
this.setState({ playing: true });
|
||||
}
|
||||
|
||||
this.handleResize();
|
||||
|
||||
this.autoPlayTimeout = setTimeout(this.autoPlayBlockDetected, AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS * 1000);
|
||||
}
|
||||
|
||||
handleOnPlay() {
|
||||
@ -201,6 +280,8 @@ class VideoPlayer extends Component {
|
||||
sendMessage('play', { time: curTime });
|
||||
}
|
||||
this.setState({ playing: true });
|
||||
|
||||
this.handleFirstPlay();
|
||||
}
|
||||
|
||||
handleOnPause() {
|
||||
@ -211,11 +292,13 @@ class VideoPlayer extends Component {
|
||||
sendMessage('stop', { time: curTime });
|
||||
}
|
||||
this.setState({ playing: false });
|
||||
|
||||
this.handleFirstPlay();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { videoUrl } = this.props;
|
||||
const { playing, playbackRate, mutedByEchoTest } = this.state;
|
||||
const { videoUrl, intl } = this.props;
|
||||
const { playing, playbackRate, mutedByEchoTest, autoPlayBlocked } = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -223,6 +306,12 @@ class VideoPlayer extends Component {
|
||||
data-test="videoPlayer"
|
||||
ref={(ref) => { this.playerParent = ref; }}
|
||||
>
|
||||
{autoPlayBlocked ?
|
||||
<p className={styles.autoPlayWarning}>
|
||||
{intl.formatMessage(intlMessages.autoPlayWarning)}
|
||||
</p>
|
||||
: ''
|
||||
}
|
||||
<ReactPlayer
|
||||
className={styles.videoPlayer}
|
||||
url={videoUrl}
|
||||
@ -240,4 +329,4 @@ class VideoPlayer extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default injectWbResizeEvent(VideoPlayer);
|
||||
export default injectIntl(injectWbResizeEvent(VideoPlayer));
|
||||
|
@ -1,28 +1,18 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import { Session } from 'meteor/session';
|
||||
import { getVideoUrl } from './service';
|
||||
import ExternalVideo from './component';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
title: {
|
||||
id: 'app.externalVideo.title',
|
||||
description: '',
|
||||
},
|
||||
});
|
||||
|
||||
const ExternalVideoContainer = props => (
|
||||
<ExternalVideo {...{ ...props }} />
|
||||
);
|
||||
|
||||
export default injectIntl(withTracker(({ intl, isPresenter }) => {
|
||||
const title = intl.formatMessage(intlMessages.title);
|
||||
export default withTracker(({ isPresenter }) => {
|
||||
const inEchoTest = Session.get('inEchoTest');
|
||||
return {
|
||||
inEchoTest,
|
||||
title,
|
||||
isPresenter,
|
||||
videoUrl: getVideoUrl(),
|
||||
};
|
||||
})(ExternalVideoContainer));
|
||||
})(ExternalVideoContainer);
|
||||
|
@ -13,7 +13,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding-bottom: 0;
|
||||
padding: 0;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
width: 100%;
|
||||
@ -32,7 +32,7 @@
|
||||
.modal {
|
||||
@extend .modal;
|
||||
padding: 1.5rem;
|
||||
min-height: 20rem;
|
||||
min-height: 23rem;
|
||||
}
|
||||
|
||||
.closeBtn {
|
||||
@ -132,8 +132,8 @@
|
||||
|
||||
.urlError {
|
||||
color: red;
|
||||
padding: 1em;
|
||||
|
||||
padding: 1em 0 2.5em 0;
|
||||
|
||||
:global(.animationsEnabled) & {
|
||||
transition: 1s;
|
||||
}
|
||||
@ -143,5 +143,5 @@
|
||||
color: var(--color-gray);
|
||||
font-size: var(--font-size-small);
|
||||
font-style: italic;
|
||||
padding-top: var(--sm-padding-x);
|
||||
padding-top: var(--sm-padding-y);
|
||||
}
|
||||
|
@ -12,3 +12,17 @@
|
||||
border-style: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.autoPlayWarning {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
font-size: x-large;
|
||||
color: white;
|
||||
width: 100%;
|
||||
background-color: rgba(6,23,42,0.5);
|
||||
bottom: 20%;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
|
||||
}
|
||||
|
@ -658,9 +658,10 @@
|
||||
"app.externalVideo.urlInput": "Add Video URL",
|
||||
"app.externalVideo.urlError": "This video URL isn't supported",
|
||||
"app.externalVideo.close": "Close",
|
||||
"app.externalVideo.autoPlayWarning": "Play the video to enable media synchronization",
|
||||
"app.network.connection.effective.slow": "We're noticing connectivity issues.",
|
||||
"app.network.connection.effective.slow.help": "More information",
|
||||
"app.externalVideo.noteLabel": "Note: Shared external videos will not appear in the recording",
|
||||
"app.externalVideo.noteLabel": "Note: Shared external videos will not appear in the recording. YouTube, Vimeo, Instructure Media, Twitch and Daily Motion URLs are supported.",
|
||||
"app.actionsBar.actionsDropdown.shareExternalVideo": "Share an external video",
|
||||
"app.actionsBar.actionsDropdown.stopShareExternalVideo": "Stop sharing external video",
|
||||
"app.iOSWarning.label": "Please upgrade to iOS 12.2 or higher",
|
||||
|
Loading…
Reference in New Issue
Block a user