diff --git a/bigbluebutton-html5/imports/api/external-videos/server/methods/startWatchingExternalVideo.js b/bigbluebutton-html5/imports/api/external-videos/server/methods/startWatchingExternalVideo.js index b5d6b8550acca0b6cad435325419b9b4ea0fdaa5..407d8a2b0903f037979c9528eca0f70f24ed42a0 100644 --- a/bigbluebutton-html5/imports/api/external-videos/server/methods/startWatchingExternalVideo.js +++ b/bigbluebutton-html5/imports/api/external-videos/server/methods/startWatchingExternalVideo.js @@ -20,7 +20,7 @@ export default function startWatchingExternalVideo(credentials, options) { const payload = { externalVideoUrl }; - Logger.info(`User id=${requesterUserId} sharing a youtube video: ${externalVideoUrl} for meeting ${meetingId}`); + Logger.info(`User id=${requesterUserId} sharing an external video: ${externalVideoUrl} for meeting ${meetingId}`); return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); } diff --git a/bigbluebutton-html5/imports/api/external-videos/server/methods/stopWatchingExternalVideo.js b/bigbluebutton-html5/imports/api/external-videos/server/methods/stopWatchingExternalVideo.js index 8b0496232da1126a4c38d97749f6c1bf47a455db..04a009c59fa558592dd9b524f515cc57923a1bad 100644 --- a/bigbluebutton-html5/imports/api/external-videos/server/methods/stopWatchingExternalVideo.js +++ b/bigbluebutton-html5/imports/api/external-videos/server/methods/stopWatchingExternalVideo.js @@ -17,7 +17,7 @@ export default function stopWatchingExternalVideo(credentials) { Meetings.update({ meetingId }, { $set: { externalVideoUrl: null } }); const payload = {}; - Logger.info(`User id=${requesterUserId} stopped sharing a youtube video for meeting=${meetingId}`); + Logger.info(`User id=${requesterUserId} stopped sharing an external video for meeting=${meetingId}`); RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); } diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/service.js b/bigbluebutton-html5/imports/ui/components/actions-bar/service.js index bde56d3e16dd3ed2226fd3ac3ca00d9431265cfc..20960bf10cf39ce9ed2e1cfb657d942ebce0f455 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/service.js +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/service.js @@ -3,7 +3,7 @@ import Users from '/imports/api/users'; import { makeCall } from '/imports/ui/services/api'; import Meetings from '/imports/api/meetings'; import Breakouts from '/imports/api/breakouts'; -import { getVideoId } from '/imports/ui/components/external-video-player/service'; +import { getVideoUrl } from '/imports/ui/components/external-video-player/service'; const USER_CONFIG = Meteor.settings.public.user; const ROLE_MODERATOR = USER_CONFIG.role_moderator; @@ -45,5 +45,5 @@ export default { getBreakouts, getUsersNotAssigned, takePresenterRole, - isSharingVideo: () => getVideoId(), + isSharingVideo: () => getVideoUrl(), }; 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 50edb7e48536178092f521f5020314369aaf9536..20b5eee6386fe492f7dbed4f35e12a9dd77f5af0 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/component.jsx @@ -1,42 +1,52 @@ import React, { Component } from 'react'; import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component'; -import YouTube from 'react-youtube'; import { sendMessage, onMessage } from './service'; import logger from '/imports/startup/client/logger'; -const { PlayerState } = YouTube; +import ArcPlayer from './custom-players/arc-player'; + +import ReactPlayer from 'react-player'; + +import { styles } from './styles'; const SYNC_INTERVAL_SECONDS = 2; +ReactPlayer.addCustomPlayer(ArcPlayer); + class VideoPlayer extends Component { constructor(props) { super(props); + const isPresenter = { props }; + this.player = null; this.syncInterval = null; - this.playerState = PlayerState.UNSTARTED; - this.presenterCommand = false; - this.preventStateChange = false; this.state = { mutedByEchoTest: false, + playing: false, + playbackRate: 1, }; this.opts = { - playerVars: { - width: '100%', - height: '100%', - autoplay: 1, - modestbranding: true, - rel: 0, - ecver: 2, + controls: isPresenter, + youtube: { + playerVars: { + autoplay: 1, + modestbranding: 1, + autohide: 1, + rel: 0, + ecver: 2, + controls: this.props.isPresenter ? 1 : 2, + }, }, + preload: true, }; this.keepSync = this.keepSync.bind(this); this.handleResize = this.handleResize.bind(this); this.handleOnReady = this.handleOnReady.bind(this); - this.handleStateChange = this.handleStateChange.bind(this); - this.changeState = this.changeState.bind(this); + this.handleOnPlay = this.handleOnPlay.bind(this); + this.handleOnPause = this.handleOnPause.bind(this); this.resizeListener = () => { setTimeout(this.handleResize, 0); }; @@ -44,6 +54,9 @@ class VideoPlayer extends Component { componentDidMount() { window.addEventListener('resize', this.resizeListener); + + clearInterval(this.syncInterval); + this.keepSync(); } componentDidUpdate(prevProps) { @@ -53,17 +66,11 @@ class VideoPlayer extends Component { } = this.state; if (inEchoTest && !this.player.isMuted() && !mutedByEchoTest) { - this.player.mute(); - this.changeState(true); + this.setState({ mutedByEchoTest: true }); } if (!inEchoTest && prevProps.inEchoTest && mutedByEchoTest) { - this.player.unMute(); - this.changeState(false); - } - - if (!prevProps.videoId) { - clearInterval(this.syncInterval); + this.setState({ mutedByEchoTest: false }); } } @@ -72,29 +79,36 @@ class VideoPlayer extends Component { clearInterval(this.syncInterval); this.player = null; - this.refPlayer = null; - } - - changeState(booleanValue) { - this.setState({ mutedByEchoTest: booleanValue }); } handleResize() { - if (!this.player || !this.refPlayer) { + if (!this.player || !this.playerParent) { return; } - const el = this.refPlayer; - const parent = el.parentElement; - const w = parent.clientWidth; - const h = parent.clientHeight; + const par = this.playerParent.parentElement; + const w = par.clientWidth; + const h = par.clientHeight; const idealW = h * 16 / 9; + let style = {} if (idealW > w) { - this.player.setSize(w, w * 9 / 16); + style.width = w; + style.height = w * 9 / 16; } else { - this.player.setSize(idealW, h); + style.width = idealW; + style.height = h; } + + var styleStr = `width: ${style.width}px; height: ${style.height}px;`; + this.player.wrapper.style = styleStr; + this.playerParent.style = styleStr; + } + + getCurrentPlaybackRate() { + const intPlayer = this.player.getInternalPlayer(); + + return (intPlayer.getPlaybackRate && intPlayer.getPlaybackRate()) || 1; } keepSync() { @@ -103,38 +117,41 @@ class VideoPlayer extends Component { if (isPresenter) { this.syncInterval = setInterval(() => { const curTime = this.player.getCurrentTime(); - const rate = this.player.getPlaybackRate(); - sendMessage('playerUpdate', { rate, time: curTime, state: this.playerState }); + const rate = this.getCurrentPlaybackRate(); + + sendMessage('playerUpdate', { rate, time: curTime, state: this.state.playing }); }, SYNC_INTERVAL_SECONDS * 1000); } else { + onMessage('play', ({ time }) => { - this.presenterCommand = true; - if (this.player) { - this.player.seekTo(time, true); - this.playerState = PlayerState.PLAYING; - this.player.playVideo(); + if (!this.player) { + return; } + + this.player.seekTo(time); + this.setState({playing: true}); + logger.debug({ logCode: 'external_video_client_play' }, 'Play external video'); }); onMessage('stop', ({ time }) => { - this.presenterCommand = true; - - if (this.player) { - this.playerState = PlayerState.PAUSED; - this.player.seekTo(time, true); - this.player.pauseVideo(); + if (!this.player) { + return; } + this.player.seekTo(time); + this.setState({playing: false}); + logger.debug({ logCode: 'external_video_client_stop' }, 'Stop external video'); }); onMessage('playerUpdate', (data) => { + if (!this.player) { return; } - if (data.rate !== this.player.getPlaybackRate()) { - this.player.setPlaybackRate(data.rate); + if (data.rate !== this.player.props.playbackRate) { + this.setState({playbackRate: data.rate}); logger.debug({ logCode: 'external_video_client_update_rate', extraInfo: { @@ -153,16 +170,8 @@ class VideoPlayer extends Component { }, 'Seek external video to:'); } - if (this.playerState !== data.state) { - this.presenterCommand = true; - this.playerState = data.state; - if (this.playerState === PlayerState.PLAYING) { - this.player.playVideo(); - logger.debug({ logCode: 'external_video_client_prevent_pause' }, 'Prevent pause external video'); - } else { - this.player.pauseVideo(); - logger.debug({ logCode: 'external_video_client_prevent_play' }, 'Prevent play external video'); - } + if (this.state.playing !== data.state) { + this.setState({playing: data.state}); } }); } @@ -171,75 +180,55 @@ class VideoPlayer extends Component { handleOnReady(event) { const { isPresenter } = this.props; - this.player = event.target; - this.player.pauseVideo(); - - this.keepSync(); - if (!isPresenter) { sendMessage('viewerJoined'); - } else { - this.player.playVideo(); } this.handleResize(); } - handleStateChange(event) { + handleOnPlay() { const { isPresenter } = this.props; const curTime = this.player.getCurrentTime(); - if (this.preventStateChange && [PlayerState.PLAYING, PlayerState.PAUSED].includes(event.data)) { - this.preventStateChange = false; - return; + if (isPresenter) { + sendMessage('play', { time: curTime }); } + this.setState({playing: true}); + } - if (this.playerState === event.data) { - return; - } + handleOnPause() { + const { isPresenter } = this.props; + const curTime = this.player.getCurrentTime(); - if (event.data === PlayerState.PLAYING) { - if (isPresenter) { - sendMessage('play', { time: curTime }); - this.playerState = event.data; - } else if (!this.presenterCommand) { - this.player.seekTo(curTime, true); - this.preventStateChange = true; - this.player.pauseVideo(); - } else { - this.playerState = event.data; - this.presenterCommand = false; - } - } else if (event.data === PlayerState.PAUSED) { - if (isPresenter) { - sendMessage('stop', { time: curTime }); - this.playerState = event.data; - } else if (!this.presenterCommand) { - this.player.seekTo(curTime); - this.preventStateChange = true; - this.player.playVideo(); - } else { - this.playerState = event.data; - this.presenterCommand = false; - } + if (isPresenter) { + sendMessage('stop', { time: curTime }); } + this.setState({playing: false}); } render() { - const { videoId } = this.props; - const { opts, handleOnReady, handleStateChange } = this; + const { videoUrl } = this.props; + const { playing, playbackRate, mutedByEchoTest } = this.state; + const { opts, commonOpts, handleOnReady, handleStateChange } = this; return ( <div - id="youtube-video-player" + id="video-player" data-test="videoPlayer" - ref={(ref) => { this.refPlayer = ref; }} + ref={(ref) => { this.playerParent = ref; }} > - <YouTube - videoId={videoId} - opts={opts} - onReady={handleOnReady} - onStateChange={handleStateChange} + <ReactPlayer + className={styles.videoPlayer} + url={videoUrl} + config={this.opts} + muted={mutedByEchoTest} + playing={playing} + playbackRate={playbackRate} + onReady={this.handleOnReady} + onPlay={this.handleOnPlay} + onPause={this.handleOnPause} + ref={(ref) => { this.player = ref; }} /> </div> ); 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 aca54887bf628b7b778478cb63199ebba75739c5..e00da62abbd156b1a76bbb879542aaba4f295e5f 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/container.jsx @@ -2,6 +2,7 @@ import React from 'react'; import { defineMessages, injectIntl } from 'react-intl'; import { withTracker } from 'meteor/react-meteor-data'; import { Session } from 'meteor/session'; +import Service from './service'; import ExternalVideo from './component'; const intlMessages = defineMessages({ @@ -12,7 +13,7 @@ const intlMessages = defineMessages({ }); const ExternalVideoContainer = props => ( - <ExternalVideo {...props}> + <ExternalVideo {...{...props, videoUrl: Service.getVideoUrl() } }> {props.children} </ExternalVideo> ); diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/custom-players/arc-player.jsx b/bigbluebutton-html5/imports/ui/components/external-video-player/custom-players/arc-player.jsx new file mode 100644 index 0000000000000000000000000000000000000000..4528fea16f4df86f8ea7a21cd8249f2121e25015 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/custom-players/arc-player.jsx @@ -0,0 +1,193 @@ +import loadScript from 'load-script'; +import React, { Component } from 'react' + +const MATCH_URL = new RegExp("https?:\/\/(\\w+)\.(instructuremedia.com)(\/embed)?\/([-abcdef0-9]+)"); + +const SDK_URL = 'https://files.instructuremedia.com/instructure-media-script/instructure-media-1.1.0.js'; + +const EMBED_PATH = "/embed/"; + +// Util function to load an external SDK or return the SDK if it is already loaded +// From https://github.com/CookPete/react-player/blob/master/src/utils.js +const resolves = {} +export function getSDK (url, sdkGlobal, sdkReady = null, isLoaded = () => true, fetchScript = loadScript) { + if (window[sdkGlobal] && isLoaded(window[sdkGlobal])) { + return Promise.resolve(window[sdkGlobal]) + } + return new Promise((resolve, reject) => { + // If we are already loading the SDK, add the resolve + // function to the existing array of resolve functions + if (resolves[url]) { + resolves[url].push(resolve) + return + } + resolves[url] = [resolve] + const onLoaded = sdk => { + // When loaded, resolve all pending promises + resolves[url].forEach(resolve => resolve(sdk)) + } + if (sdkReady) { + const previousOnReady = window[sdkReady] + window[sdkReady] = function () { + if (previousOnReady) previousOnReady() + onLoaded(window[sdkGlobal]) + } + } + fetchScript(url, err => { + if (err) { + reject(err); + } + window[sdkGlobal] = url; + if (!sdkReady) { + onLoaded(window[sdkGlobal]) + } + }) + }) +} + +export class ArcPlayer extends Component { + static displayName = 'ArcPlayer' + + static canPlay = url => { + return MATCH_URL.test(url) + } + + constructor(props) { + super(props); + + this.currentTime = 0; + this.updateCurrentTime = this.updateCurrentTime.bind(this); + this.getCurrentTime = this.getCurrentTime.bind(this); + this.getEmbedUrl = this.getEmbedUrl.bind(this); + this.onStateChange = this.onStateChange.bind(this); + } + + load() { + new Promise((resolve, reject) => { + this.render(); + resolve(); + }) + .then(() => { return getSDK(SDK_URL, 'ArcPlayer') }) + .then(() => { + this.player = new InstructureMedia.Player('arcPlayerContainer', { + height: '100%', + width: '100%', + embedUrl: this.getEmbedUrl(), + events: { + onStateChange: this.onStateChange, + } + }); + this.player.playVideo(); + }); + } + + onStateChange(event) { + console.log('js_api index.development.html onStateChange', event); + this.player.getCurrentTime().then((t) => { + this.updateCurrentTime(t.data); + }); + + if (event.data === "CUED") { + this.props.onReady(); + } else if (event.data === "PLAYING") { + this.props.onPlay && this.props.onPlay(); + } else if (event.data === "PAUSED") { + this.props.onPause && this.props.onPause(); + } else if (event.data === "SEEKED") { + // TODO + } else if (event.data === "SEEKING") { + // TODO + } + } + + updateCurrentTime(e) { + this.currentTime = e; + } + + getVideoId() { + const { url } = this.props; + const m = url.match(MATCH_URL); + return m && m[4]; + } + + getHostUrl() { + const { url } = this.props; + const m = url.match(MATCH_URL); + return m && 'https://' + m[1] + '.' + m[2]; + } + + getEmbedUrl() { + let url = this.getHostUrl() + EMBED_PATH + this.getVideoId(); + return url; + } + + play() { + this.player.playVideo(); + } + + pause() { + this.player.pauseVideo(); + } + + stop() { + // TODO: STOP + } + + seekTo(seconds) { + this.player.seekTo(seconds); + } + + setVolume(fraction) { + // console.log("SET VOLUME"); + } + + setLoop(loop) { + // console.log("SET LOOP"); + } + + mute() { + // console.log("SET MUTE"); + } + + unmute() { + // console.log("SET UNMUTE"); + } + + getDuration() { + //console.log("GET DURATION"); + } + + getCurrentTime () { + this.player.getCurrentTime().then((t) => { + this.updateCurrentTime(t.data); + }); + + return this.currentTime; + } + + getSecondsLoaded () { + } + + render () { + const style = { + width: '100%', + height: '100%', + overflow: 'hidden', + backgroundColor: 'black' + } + return ( + <div + key={this.props.url} + style={style} + id={"arcPlayerContainer"} + ref={(container) => { + this.container = container; + }} + > + </div> + ) + } +} + +export default ArcPlayer; + diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/modal/component.jsx b/bigbluebutton-html5/imports/ui/components/external-video-player/modal/component.jsx index f6524395c2130d75b044b8f6ea8a6732f8be0b53..3601f3f9ccfb2e02a30cb9cfda0d29c7a0ed601c 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/modal/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/modal/component.jsx @@ -4,14 +4,14 @@ import Modal from '/imports/ui/components/modal/simple/component'; import Button from '/imports/ui/components/button/component'; import { defineMessages, injectIntl } from 'react-intl'; -import { isUrlValid, getUrlFromVideoId } from '../service'; +import { isUrlValid } from '../service'; import { styles } from './styles'; const intlMessages = defineMessages({ start: { id: 'app.externalVideo.start', - description: 'Share youtube video', + description: 'Share external video', }, urlError: { id: 'app.externalVideo.urlError', @@ -35,7 +35,7 @@ const intlMessages = defineMessages({ }, note: { id: 'app.externalVideo.noteLabel', - description: 'provides hint about Shared YouTube videos', + description: 'provides hint about Shared External videos', }, }); @@ -43,11 +43,11 @@ class ExternalVideoModal extends Component { constructor(props) { super(props); - const { videoId } = props; + const { videoUrl } = props; this.state = { - url: getUrlFromVideoId(videoId), - sharing: videoId, + url: videoUrl, + sharing: videoUrl, }; this.startWatchingHandler = this.startWatchingHandler.bind(this); @@ -93,10 +93,10 @@ class ExternalVideoModal extends Component { } render() { - const { intl, videoId, closeModal } = this.props; + const { intl, videoUrl, closeModal } = this.props; const { url, sharing } = this.state; - const startDisabled = !isUrlValid(url) || (getUrlFromVideoId(videoId) === url); + const startDisabled = !isUrlValid(url); return ( <Modal @@ -118,13 +118,12 @@ class ExternalVideoModal extends Component { id="video-modal-input" onChange={this.updateVideoUrlHandler} name="video-modal-input" - value={url} placeholder={intl.formatMessage(intlMessages.urlInput)} disabled={sharing} - aria-describedby="youtube-note" + aria-describedby="exernal-video-note" /> </label> - <div className={styles.youtubeNote} id="youtube-note"> + <div className={styles.externalVideoNote} id="external-video-note"> {intl.formatMessage(intlMessages.note)} </div> </div> diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/modal/container.jsx b/bigbluebutton-html5/imports/ui/components/external-video-player/modal/container.jsx index 6a91801ccbc75d82035aab942d9998708aaf2809..6d53925557393181102f30c6008670aed277d97c 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/modal/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/modal/container.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import { withModalMounter } from '/imports/ui/components/modal/service'; import ExternalVideoModal from './component'; -import { startWatching, getVideoId } from '../service'; +import { startWatching, getVideoUrl } from '../service'; import mediaService from '/imports/ui/components/media/service'; const ExternalVideoModalContainer = props => <ExternalVideoModal {...props} />; @@ -12,7 +12,7 @@ export default withModalMounter(withTracker(({ mountModal }) => ({ mountModal(null); }, startWatching, - videoId: getVideoId(), + videoUrl: getVideoUrl(), toggleLayout: mediaService.toggleSwapLayout, isSwapped: mediaService.getSwapLayout(), }))(ExternalVideoModalContainer)); 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 e5fe49bf34d778be1eaeb14b2cae1ec61866d8ec..e4d8dbe2731817dbe5f8fc40b0552734073df7d0 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 @@ -139,7 +139,7 @@ } } -.youtubeNote { +.externalVideoNote { color: var(--color-gray); font-size: var(--font-size-small); font-style: italic; diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/service.js b/bigbluebutton-html5/imports/ui/components/external-video-player/service.js index db2fd93b795a0f20e2cb4e2ab6c2ad8fbe4abc65..b3a530435d13992edf26f30fea2d6db81401f3c2 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/service.js +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/service.js @@ -4,22 +4,13 @@ import ExternalVideoStreamer from '/imports/api/external-videos'; import { makeCall } from '/imports/ui/services/api'; -const YOUTUBE_PREFIX = 'https://youtube.com/watch?v='; -const YOUTUBE_REGEX = /(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\/?\?(?:\S*?&?v\=))|youtu\.be\/)([a-zA-Z0-9_-]{6,11})/g; +import ReactPlayer from 'react-player'; const isUrlEmpty = url => !url || url.length === 0; - -const isUrlValid = url => !isUrlEmpty(url) && url.match(YOUTUBE_REGEX); - -const getUrlFromVideoId = id => (id ? `${YOUTUBE_PREFIX}${id}` : ''); - -const videoIdFromUrl = (url) => { - const match = YOUTUBE_REGEX.exec(url); - return match ? match[1] : false; -}; +const isUrlValid = url => ReactPlayer.canPlay(url); const startWatching = (url) => { - const externalVideoUrl = videoIdFromUrl(url); + const externalVideoUrl = url; makeCall('startWatchingExternalVideo', { externalVideoUrl }); }; @@ -39,7 +30,7 @@ const onMessage = (message, func) => { ExternalVideoStreamer.on(message, func); }; -const getVideoId = () => { +const getVideoUrl = () => { const meetingId = Auth.meetingID; const meeting = Meetings.findOne({ meetingId }); @@ -49,8 +40,7 @@ const getVideoId = () => { export { sendMessage, onMessage, - getVideoId, - getUrlFromVideoId, + getVideoUrl, isUrlValid, startWatching, stopWatching, 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 5bb369f6e5cf3772faba2e0595a05f75f2a579ae..2af8c737bdd6d061cfcc34860d43861b5831129a 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/styles.scss @@ -1,49 +1,7 @@ @import "/imports/ui/stylesheets/mixins/focus"; @import "/imports/ui/stylesheets/variables/_all"; -$icon-offset: -.4em; - -.note { - background-color: #fff; - padding-top: var(--md-padding-x); - display: flex; - flex-grow: 1; - flex-direction: column; - justify-content: space-around; - overflow: hidden; -} - -.header { - display: flex; - flex-direction: row; - align-items: left; - flex-shrink: 0; - padding-left: var(--md-padding-x); - padding-right: var(--md-padding-x); - - a { - @include elementFocus(var(--color-primary)); - padding: 0 0 var(--sm-padding-y) var(--sm-padding-y); - text-decoration: none; - display: block; - - [dir="rtl"] & { - padding: 0 var(--sm-padding-y) var(--sm-padding-y) 0; - } - } - - [class^="icon-bbb-"], - [class*=" icon-bbb-"] { - font-size: 85%; - } -} - -.title { - @extend %text-elipsis; - flex: 1; -} - -iframe { +.videoPlayer iframe { display: flex; flex-flow: column; flex-grow: 1; @@ -52,4 +10,5 @@ iframe { overflow-x: hidden; overflow-y: auto; border-style: none; + border-bottom: none; } diff --git a/bigbluebutton-html5/imports/ui/components/media/container.jsx b/bigbluebutton-html5/imports/ui/components/media/container.jsx index dbaababf71897f1493b50a8e884a252e7657eadb..a049c4d6e469ae5f35e81433670af0248c7179d1 100755 --- a/bigbluebutton-html5/imports/ui/components/media/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/media/container.jsx @@ -14,7 +14,6 @@ import PresentationPodsContainer from '../presentation-pod/container'; import ScreenshareContainer from '../screenshare/container'; import DefaultContent from '../presentation/default-content/component'; import ExternalVideoContainer from '../external-video-player/container'; -import { getVideoId } from '../external-video-player/service'; const LAYOUT_CONFIG = Meteor.settings.public.layout; const KURENTO_CONFIG = Meteor.settings.public.kurento; @@ -143,7 +142,6 @@ export default withModalMounter(withTracker(() => { data.children = ( <ExternalVideoContainer isPresenter={MediaService.isUserPresenter()} - videoId={getVideoId()} /> ); } diff --git a/bigbluebutton-html5/imports/ui/components/media/service.js b/bigbluebutton-html5/imports/ui/components/media/service.js index a2f121773c4f63136cbc6d5bfa66cecd97c82c6c..1e03deafcafe009937fc39b7809e4668bfdf7cd6 100755 --- a/bigbluebutton-html5/imports/ui/components/media/service.js +++ b/bigbluebutton-html5/imports/ui/components/media/service.js @@ -1,6 +1,6 @@ import Presentations from '/imports/api/presentations'; import { isVideoBroadcasting } from '/imports/ui/components/screenshare/service'; -import { getVideoId } from '/imports/ui/components/external-video-player/service'; +import { getVideoUrl } from '/imports/ui/components/external-video-player/service'; import Auth from '/imports/ui/services/auth'; import Users from '/imports/api/users'; import Settings from '/imports/ui/services/settings'; @@ -36,7 +36,7 @@ function shouldShowScreenshare() { function shouldShowExternalVideo() { const { enabled: enableExternalVideo } = Meteor.settings.public.externalVideoPlayer; - return enableExternalVideo && getVideoId(); + return enableExternalVideo && getVideoUrl(); } function shouldShowOverlay() { diff --git a/bigbluebutton-html5/package-lock.json b/bigbluebutton-html5/package-lock.json index 6700180d031aa0420b1bf42563e82de027c09a03..5dcc97c0bf63bd1fc05e25ab16af077fb84ce14a 100644 --- a/bigbluebutton-html5/package-lock.json +++ b/bigbluebutton-html5/package-lock.json @@ -5601,6 +5601,23 @@ "warning": "^3.0.0" } }, + "react-player": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/react-player/-/react-player-1.11.1.tgz", + "integrity": "sha512-bS4qls4eICdwklCm/jqjSvYZt1dl07biT8aAiXG9dMZ2s789cZtCDiHdwKP2FH82jHYk5oFfHXy7EL9lMjUfxA==", + "requires": { + "deepmerge": "^3.0.0", + "load-script": "^1.0.0", + "prop-types": "^15.5.6" + }, + "dependencies": { + "deepmerge": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", + "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==" + } + } + }, "react-render-in-browser": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/react-render-in-browser/-/react-render-in-browser-1.1.1.tgz", @@ -5660,16 +5677,6 @@ } } }, - "react-youtube": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/react-youtube/-/react-youtube-7.9.0.tgz", - "integrity": "sha512-2+nBF4qP8nStYEILIO1/SylKOCnnJUxuZm+qCeWA0eeZxnWZIIixfAeAqbzblwx5L1n/26ACocy3epm9Glox8w==", - "requires": { - "fast-deep-equal": "^2.0.1", - "prop-types": "^15.5.3", - "youtube-player": "^5.5.1" - } - }, "reactcss": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", @@ -6201,11 +6208,6 @@ } } }, - "sister": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/sister/-/sister-3.0.1.tgz", - "integrity": "sha512-aG41gNRHRRxPq52MpX4vtm9tapnr6ENmHUx8LMAJWCOplEMwXzh/dp5WIo52Wl8Zlc/VUyHLJ2snX0ck+Nma9g==" - }, "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", @@ -7522,16 +7524,6 @@ } } }, - "youtube-player": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/youtube-player/-/youtube-player-5.5.2.tgz", - "integrity": "sha512-ZGtsemSpXnDky2AUYWgxjaopgB+shFHgXVpiJFeNB5nWEugpW1KWYDaHKuLqh2b67r24GtP6HoSW5swvf0fFIQ==", - "requires": { - "debug": "^2.6.6", - "load-script": "^1.0.0", - "sister": "^3.0.0" - } - }, "yup": { "version": "0.26.10", "resolved": "https://registry.npmjs.org/yup/-/yup-0.26.10.tgz", diff --git a/bigbluebutton-html5/package.json b/bigbluebutton-html5/package.json index e59f7f51e7a0e57340f6655610de4620630ac139..e1e0e3e9a7a04e36a21c88cd260617fd369b1001 100755 --- a/bigbluebutton-html5/package.json +++ b/bigbluebutton-html5/package.json @@ -62,12 +62,12 @@ "react-dropzone": "^7.0.1", "react-intl": "~2.7.2", "react-modal": "~3.6.1", + "react-player": "^1.11.1", "react-render-in-browser": "^1.1.1", "react-tabs": "^2.3.1", "react-toastify": "^4.5.2", "react-toggle": "~4.0.2", "react-transition-group": "^2.9.0", - "react-youtube": "^7.9.0", "reconnecting-websocket": "~v4.1.10", "redis": "~2.8.0", "sdp-transform": "2.7.0", diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json index 652d4b0005bfe5b4d2fda3bd0c99af196c09c66c..61b75fad23ef6e2ed799eadd7123a92c1445ea1d 100755 --- a/bigbluebutton-html5/private/locales/en.json +++ b/bigbluebutton-html5/private/locales/en.json @@ -644,16 +644,16 @@ "app.createBreakoutRoom.roomTime": "{0} minutes", "app.createBreakoutRoom.numberOfRoomsError": "The number of rooms is invalid.", "app.externalVideo.start": "Share a new video", - "app.externalVideo.title": "Share a YouTube video", - "app.externalVideo.input": "YouTube video URL", - "app.externalVideo.urlInput": "Add YouTube URL", - "app.externalVideo.urlError": "This URL isn't a valid YouTube video", + "app.externalVideo.title": "Share an external video", + "app.externalVideo.input": "External Video URL", + "app.externalVideo.urlInput": "Add Video URL", + "app.externalVideo.urlError": "This video URL isn't supported", "app.externalVideo.close": "Close", "app.network.connection.effective.slow": "We're noticing connectivity issues.", "app.network.connection.effective.slow.help": "More information", - "app.externalVideo.noteLabel": "Note: Shared YouTube videos will not appear in the recording", - "app.actionsBar.actionsDropdown.shareExternalVideo": "Share YouTube video", - "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Stop sharing YouTube video", + "app.externalVideo.noteLabel": "Note: Shared external videos will not appear in the recording", + "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", "app.legacy.unsupportedBrowser": "It looks like you're using a browser that is not supported. Please use either {0} or {1} for full support.", "app.legacy.upgradeBrowser": "It looks like you're using an older version of a supported browser. Please upgrade your browser for full support.",