From 290cd481e506aaec296c08e540a3a031532c39a3 Mon Sep 17 00:00:00 2001 From: Lucas Zawacki Date: Mon, 7 Oct 2019 14:45:16 -0300 Subject: [PATCH 01/11] Don't run synchronization code on a player with autoplay blocked and add an autoplay warning --- .../external-video-player/component.jsx | 51 ++++++++++++++++--- .../external-video-player/container.jsx | 14 +---- .../external-video-player/styles.scss | 14 +++++ bigbluebutton-html5/private/locales/en.json | 1 + 4 files changed, 61 insertions(+), 19 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx index af0f39a0e9..4106efeff2 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx @@ -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,9 +29,12 @@ 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, }; @@ -43,6 +54,7 @@ class VideoPlayer extends Component { }; this.registerVideoListeners = this.registerVideoListeners.bind(this); + this.autoPlayBlockDetected = this.autoPlayBlockDetected.bind(this); this.clearVideoListeners = this.clearVideoListeners.bind(this); this.handleResize = this.handleResize.bind(this); this.handleOnReady = this.handleOnReady.bind(this); @@ -65,6 +77,7 @@ class VideoPlayer extends Component { this.clearVideoListeners(); clearInterval(this.syncInterval); + clearTimeout(this.autoPlayTimeout); this.player = null; } @@ -83,6 +96,10 @@ class VideoPlayer extends Component { return { mutedByEchoTest: inEchoTest }; } + autoPlayBlockDetected() { + this.setState({autoPlayBlocked: true}); + } + getCurrentPlaybackRate() { const intPlayer = this.player.getInternalPlayer(); @@ -131,7 +148,7 @@ class VideoPlayer extends Component { }, SYNC_INTERVAL_SECONDS * 1000); } else { onMessage('play', ({ time }) => { - if (!this.player) { + if (!this.player || !this.state.hasPlayedBefore) { return; } @@ -142,7 +159,7 @@ class VideoPlayer extends Component { }); onMessage('stop', ({ time }) => { - if (!this.player) { + if (!this.player || !this.state.hasPlayedBefore) { return; } this.player.seekTo(time); @@ -152,7 +169,7 @@ class VideoPlayer extends Component { }); onMessage('playerUpdate', (data) => { - if (!this.player) { + if (!this.player || !this.state.hasPlayedBefore) { return; } @@ -191,6 +208,10 @@ class VideoPlayer extends Component { } this.handleResize(); + + this.autoPlayTimeout = setTimeout(this.autoPlayBlockDetected, AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS * 1000); + + this.setState({ playing: true }); } handleOnPlay() { @@ -201,6 +222,11 @@ class VideoPlayer extends Component { sendMessage('play', { time: curTime }); } this.setState({ playing: true }); + + if (!this.state.hasPlayedBefore) { + this.setState({ hasPlayedBefore: true, autoPlayBlocked: false }); + clearTimeout(this.autoPlayTimeout); + } } handleOnPause() { @@ -211,11 +237,16 @@ class VideoPlayer extends Component { sendMessage('stop', { time: curTime }); } this.setState({ playing: false }); + + if (!this.state.hasPlayedBefore) { + this.setState({ hasPlayedBefore: true, autoPlayBlocked: false }); + clearTimeout(this.autoPlayTimeout); + } } render() { - const { videoUrl } = this.props; - const { playing, playbackRate, mutedByEchoTest } = this.state; + const { videoUrl, intl } = this.props; + const { playing, playbackRate, mutedByEchoTest, autoPlayBlocked } = this.state; return (
{ this.playerParent = ref; }} > + {autoPlayBlocked ? +

+ {intl.formatMessage(intlMessages.autoPlayWarning)} +

+ : '' + } ( ); -export default injectIntl(withTracker(({ intl, isPresenter }) => { - const title = intl.formatMessage(intlMessages.title); +export default withTracker(({ intl, isPresenter }) => { const inEchoTest = Session.get('inEchoTest'); return { inEchoTest, - title, isPresenter, videoUrl: getVideoUrl(), }; -})(ExternalVideoContainer)); +})(ExternalVideoContainer); diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/styles.scss b/bigbluebutton-html5/imports/ui/components/external-video-player/styles.scss index 2af8c737bd..cadb30d484 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/styles.scss @@ -12,3 +12,17 @@ border-style: none; border-bottom: none; } + +.autoPlayWarning { + position: absolute; + z-index: 100; + font-size: 2rem; + color: white; + text-align: center; + width: 100%; + background-color: rgba(6,23,42,0.5); + bottom: 35%; + vertical-align: middle; + text-align: center; + +} diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json index de866409e0..062bf9848f 100755 --- a/bigbluebutton-html5/private/locales/en.json +++ b/bigbluebutton-html5/private/locales/en.json @@ -658,6 +658,7 @@ "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", From ec88bd24e487a6e53a879f0dc6995b39cb76f1ae Mon Sep 17 00:00:00 2001 From: Lucas Zawacki Date: Mon, 7 Oct 2019 15:42:01 -0300 Subject: [PATCH 02/11] Prevent viewers from playing before video has loaded for presenter --- .../external-video-player/component.jsx | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx index 4106efeff2..e286260e7f 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx @@ -56,6 +56,7 @@ 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); @@ -100,6 +101,19 @@ class VideoPlayer extends Component { this.setState({autoPlayBlocked: true}); } + handleFirstPlay() { + const isPresenter = this.props; + + if (!this.state.hasPlayedBefore) { + this.setState({ hasPlayedBefore: true, autoPlayBlocked: false }); + clearTimeout(this.autoPlayTimeout); + + if (isPresenter) { + sendMessage('presenterReady'); + } + } + } + getCurrentPlaybackRate() { const intPlayer = this.player.getInternalPlayer(); @@ -146,6 +160,13 @@ class VideoPlayer extends Component { sendMessage('playerUpdate', { rate, time: curTime, state: this.state.playing }); }, SYNC_INTERVAL_SECONDS * 1000); + + onMessage('viewerJoined', () => { + logger.debug({ logCode: 'external_video_viewer_joined' }, 'Viewer joined external video'); + if (this.state.hasPlayedBefore) { + sendMessage('presenterReady'); + } + }); } else { onMessage('play', ({ time }) => { if (!this.player || !this.state.hasPlayedBefore) { @@ -168,6 +189,14 @@ class VideoPlayer extends Component { logger.debug({ logCode: 'external_video_client_stop' }, 'Stop external video'); }); + onMessage('presenterReady', (data) => { + logger.debug({ logCode: 'external_video_presenter_ready' }, 'Presenter is ready to sync'); + + if (!this.state.hasPlayedBefore) { + this.setState({playing: true}); + } + }); + onMessage('playerUpdate', (data) => { if (!this.player || !this.state.hasPlayedBefore) { return; @@ -205,13 +234,13 @@ class VideoPlayer extends Component { if (!isPresenter) { sendMessage('viewerJoined'); + } else { + this.setState({ playing: true }); } this.handleResize(); this.autoPlayTimeout = setTimeout(this.autoPlayBlockDetected, AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS * 1000); - - this.setState({ playing: true }); } handleOnPlay() { @@ -223,10 +252,7 @@ class VideoPlayer extends Component { } this.setState({ playing: true }); - if (!this.state.hasPlayedBefore) { - this.setState({ hasPlayedBefore: true, autoPlayBlocked: false }); - clearTimeout(this.autoPlayTimeout); - } + this.handleFirstPlay(); } handleOnPause() { @@ -238,10 +264,7 @@ class VideoPlayer extends Component { } this.setState({ playing: false }); - if (!this.state.hasPlayedBefore) { - this.setState({ hasPlayedBefore: true, autoPlayBlocked: false }); - clearTimeout(this.autoPlayTimeout); - } + this.handleFirstPlay(); } render() { From 1fb6922712fca0004ed2a1e016d25a2ee3703152 Mon Sep 17 00:00:00 2001 From: Lucas Zawacki Date: Wed, 9 Oct 2019 11:52:26 -0300 Subject: [PATCH 03/11] Properly show/hide controls for more kinds of external video players --- .../ui/components/external-video-player/component.jsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx index e286260e7f..7d4e6d17b3 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx @@ -40,6 +40,16 @@ class VideoPlayer extends Component { this.opts = { controls: isPresenter, + file: { + attributes: { + controls: isPresenter, + }, + }, + dailymotion: { + params: { + controls: isPresenter, + }, + }, youtube: { playerVars: { autoplay: 1, From 0b99a4cf6353b79911b5db324ab5767b41758170 Mon Sep 17 00:00:00 2001 From: Lucas Zawacki Date: Wed, 9 Oct 2019 12:51:17 -0300 Subject: [PATCH 04/11] Add supported video urls in external video modal note --- .../ui/components/external-video-player/modal/styles.scss | 5 +++-- bigbluebutton-html5/private/locales/en.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/modal/styles.scss b/bigbluebutton-html5/imports/ui/components/external-video-player/modal/styles.scss index e4d8dbe273..be8c4437b6 100755 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/modal/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/modal/styles.scss @@ -133,7 +133,7 @@ .urlError { color: red; padding: 1em; - + :global(.animationsEnabled) & { transition: 1s; } @@ -143,5 +143,6 @@ 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); + padding-bottom: var(--jumbo-padding-y); } diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json index 062bf9848f..c9eb8cee48 100755 --- a/bigbluebutton-html5/private/locales/en.json +++ b/bigbluebutton-html5/private/locales/en.json @@ -661,7 +661,7 @@ "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", From 22be388b70aa72683440fe3af5499cdedf1e765f Mon Sep 17 00:00:00 2001 From: Lucas Zawacki Date: Thu, 10 Oct 2019 18:01:27 -0300 Subject: [PATCH 05/11] Remove unused intl variable from external-video --- .../imports/ui/components/external-video-player/container.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/container.jsx b/bigbluebutton-html5/imports/ui/components/external-video-player/container.jsx index ace8350919..b19b4cad92 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/container.jsx @@ -8,7 +8,7 @@ const ExternalVideoContainer = props => ( ); -export default withTracker(({ intl, isPresenter }) => { +export default withTracker(({ isPresenter }) => { const inEchoTest = Session.get('inEchoTest'); return { inEchoTest, From dcd0ea7d5d8f7035956db3d8a2f9cf4f54c54c5b Mon Sep 17 00:00:00 2001 From: Lucas Zawacki Date: Thu, 10 Oct 2019 18:03:52 -0300 Subject: [PATCH 06/11] Fix style of external video modal after adding new note --- .../ui/components/external-video-player/modal/styles.scss | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/modal/styles.scss b/bigbluebutton-html5/imports/ui/components/external-video-player/modal/styles.scss index be8c4437b6..2ca1d27d66 100755 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/modal/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/modal/styles.scss @@ -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,7 +132,7 @@ .urlError { color: red; - padding: 1em; + padding: 1em 0 2.5em 0; :global(.animationsEnabled) & { transition: 1s; @@ -144,5 +144,4 @@ font-size: var(--font-size-small); font-style: italic; padding-top: var(--sm-padding-y); - padding-bottom: var(--jumbo-padding-y); } From 30b303a6d459cceb502260fa8ed97cd234619cbc Mon Sep 17 00:00:00 2001 From: Lucas Zawacki Date: Thu, 10 Oct 2019 18:15:32 -0300 Subject: [PATCH 07/11] Making sure we show controls for all video formats because viewer always need to change volume --- .../ui/components/external-video-player/component.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx index 7d4e6d17b3..97a0631718 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx @@ -39,15 +39,14 @@ class VideoPlayer extends Component { }; this.opts = { - controls: isPresenter, file: { attributes: { - controls: isPresenter, + controls: true, }, }, dailymotion: { params: { - controls: isPresenter, + controls: true, }, }, youtube: { From 24198c48a63dff725b37359c4fe5f08bba434397 Mon Sep 17 00:00:00 2001 From: Lucas Zawacki Date: Thu, 10 Oct 2019 18:56:15 -0300 Subject: [PATCH 08/11] Prevent erroneous autoplay warning if video player sends multiple ready messages --- .../imports/ui/components/external-video-player/component.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx index 97a0631718..29dc10ea53 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx @@ -241,6 +241,10 @@ class VideoPlayer extends Component { handleOnReady() { const { isPresenter } = this.props; + if (this.state.hasPlayedBefore) { + return; + } + if (!isPresenter) { sendMessage('viewerJoined'); } else { From 926d4bb36e52af7b3243f4aa5ee3642342aed8bb Mon Sep 17 00:00:00 2001 From: Lucas Zawacki Date: Wed, 16 Oct 2019 14:47:22 -0300 Subject: [PATCH 09/11] Destructure assignments and fix wrong assignment for isPresenter --- .../external-video-player/component.jsx | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx index 29dc10ea53..e31b2e1a50 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx @@ -111,9 +111,10 @@ class VideoPlayer extends Component { } handleFirstPlay() { - const isPresenter = this.props; + const { isPresenter } = this.props; + const { hasPlayedBefore } = this.state; - if (!this.state.hasPlayedBefore) { + if (!hasPlayedBefore) { this.setState({ hasPlayedBefore: true, autoPlayBlocked: false }); clearTimeout(this.autoPlayTimeout); @@ -164,21 +165,26 @@ class VideoPlayer extends Component { if (isPresenter) { this.syncInterval = setInterval(() => { + const { playing } = this.state; const curTime = this.player.getCurrentTime(); const rate = this.getCurrentPlaybackRate(); - sendMessage('playerUpdate', { rate, time: curTime, state: this.state.playing }); + sendMessage('playerUpdate', { rate, time: curTime, state: playing }); }, SYNC_INTERVAL_SECONDS * 1000); onMessage('viewerJoined', () => { + const { hasPlayedBefore } = this.state; + logger.debug({ logCode: 'external_video_viewer_joined' }, 'Viewer joined external video'); - if (this.state.hasPlayedBefore) { + if (hasPlayedBefore) { sendMessage('presenterReady'); } }); } else { onMessage('play', ({ time }) => { - if (!this.player || !this.state.hasPlayedBefore) { + const { hasPlayedBefore } = this.state; + + if (!this.player || !hasPlayedBefore) { return; } @@ -189,7 +195,9 @@ class VideoPlayer extends Component { }); onMessage('stop', ({ time }) => { - if (!this.player || !this.state.hasPlayedBefore) { + const { hasPlayedBefore } = this.state; + + if (!this.player || !hasPlayedBefore) { return; } this.player.seekTo(time); @@ -199,15 +207,19 @@ class VideoPlayer extends Component { }); onMessage('presenterReady', (data) => { + const { hasPlayedBefore } = this.state; + logger.debug({ logCode: 'external_video_presenter_ready' }, 'Presenter is ready to sync'); - if (!this.state.hasPlayedBefore) { + if (!hasPlayedBefore) { this.setState({playing: true}); } }); onMessage('playerUpdate', (data) => { - if (!this.player || !this.state.hasPlayedBefore) { + const { hasPlayedBefore, playing } = this.state; + + if (!this.player || !hasPlayedBefore) { return; } @@ -231,7 +243,7 @@ class VideoPlayer extends Component { }, 'Seek external video to:'); } - if (this.state.playing !== data.state) { + if (playing !== data.state) { this.setState({ playing: data.state }); } }); @@ -240,8 +252,9 @@ class VideoPlayer extends Component { handleOnReady() { const { isPresenter } = this.props; + const { hasPlayedBefore } = this.state; - if (this.state.hasPlayedBefore) { + if (hasPlayedBefore) { return; } From ceb8853231e8a829ade25cf35902026d208321cd Mon Sep 17 00:00:00 2001 From: Lucas Zawacki Date: Wed, 16 Oct 2019 13:31:37 -0300 Subject: [PATCH 10/11] Prevent problems if presenter reconnects while sharing a video --- .../ui/components/external-video-player/component.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx index e31b2e1a50..39131535f1 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx @@ -165,11 +165,14 @@ class VideoPlayer extends Component { if (isPresenter) { this.syncInterval = setInterval(() => { - const { playing } = this.state; + const { playing, hasPlayedBefore } = this.state; const curTime = this.player.getCurrentTime(); const rate = this.getCurrentPlaybackRate(); - sendMessage('playerUpdate', { rate, time: curTime, 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', () => { From 25b4c93283d2964c87d25ea304373eba0ac3891f Mon Sep 17 00:00:00 2001 From: Lucas Zawacki Date: Wed, 16 Oct 2019 14:16:25 -0300 Subject: [PATCH 11/11] Better style for autoplay blocked message that does not obscure play button --- .../imports/ui/components/external-video-player/styles.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/styles.scss b/bigbluebutton-html5/imports/ui/components/external-video-player/styles.scss index cadb30d484..1b888e2bb3 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/styles.scss @@ -16,13 +16,13 @@ .autoPlayWarning { position: absolute; z-index: 100; - font-size: 2rem; + font-size: x-large; color: white; - text-align: center; width: 100%; background-color: rgba(6,23,42,0.5); - bottom: 35%; + bottom: 20%; vertical-align: middle; text-align: center; + pointer-events: none; }