diff --git a/conference.js b/conference.js
index 85d7d3071..2bf68c48a 100644
--- a/conference.js
+++ b/conference.js
@@ -28,6 +28,7 @@ import {
redirectWithStoredParams,
reloadWithStoredParams
} from './react/features/app';
+import { updateRecordingState } from './react/features/recording';
import EventEmitter from 'events';
@@ -1851,6 +1852,12 @@ export default {
APP.store.dispatch(dominantSpeakerChanged(id));
});
+ room.on(JitsiConferenceEvents.LIVE_STREAM_URL_CHANGED,
+ (from, liveStreamViewURL) =>
+ APP.store.dispatch(updateRecordingState({
+ liveStreamViewURL
+ })));
+
if (!interfaceConfig.filmStripOnly) {
room.on(JitsiConferenceEvents.CONNECTION_INTERRUPTED, () => {
APP.UI.markVideoInterrupted(true);
diff --git a/css/modals/invite/_info.scss b/css/modals/invite/_info.scss
index b5c849fb5..5fa66f917 100644
--- a/css/modals/invite/_info.scss
+++ b/css/modals/invite/_info.scss
@@ -59,7 +59,8 @@
}
}
- .info-dialog-conference-url {
+ .info-dialog-conference-url,
+ .info-dialog-live-stream-url {
width: max-content;
width: -moz-max-content;
width: -webkit-max-content;
@@ -81,8 +82,8 @@
font-size: 16px;
}
- .info-dialog-invite-link,
- .info-dialog-invite-link:hover {
+ .info-dialog-url-text,
+ .info-dialog-url-text:hover {
color: inherit;
cursor: inherit;
}
diff --git a/lang/main.json b/lang/main.json
index d0a415515..0de3da582 100644
--- a/lang/main.json
+++ b/lang/main.json
@@ -516,9 +516,11 @@
"dialInConferenceID": "PIN:",
"dialInNotSupported": "Sorry, dialing in is currently not suppported.",
"genericError": "Whoops, something went wrong.",
+ "inviteLiveStream": "To view the live stream of this meeting, click this link: __url__",
"invitePhone": "To join by phone, dial __number__ and enter this PIN: __conferenceID__#",
"invitePhoneAlternatives": "To view more phone numbers, click this link: __url__",
"inviteURL": "To join the video meeting, click this link: __url__",
+ "liveStreamURL": "Live stream:",
"moreNumbers": "More numbers",
"noNumbers": "No dial-in numbers.",
"noPassword": "None",
diff --git a/modules/UI/recording/Recording.js b/modules/UI/recording/Recording.js
index af6deb155..537b414ac 100644
--- a/modules/UI/recording/Recording.js
+++ b/modules/UI/recording/Recording.js
@@ -109,7 +109,10 @@ function _requestLiveStreamId() {
return new Promise((resolve, reject) =>
APP.store.dispatch(openDialog(StartLiveStreamDialog, {
onCancel: reject,
- onSubmit: resolve
+ onSubmit: (streamId, broadcastId) => resolve({
+ broadcastId,
+ streamId
+ })
})));
}
@@ -257,7 +260,6 @@ const Recording = {
* @param recordingState gives us the current recording state
*/
updateRecordingUI(recordingState) {
-
const oldState = this.currentState;
this.currentState = recordingState;
@@ -388,10 +390,13 @@ const Recording = {
case JitsiRecordingStatus.OFF: {
if (this.recordingType === 'jibri') {
_requestLiveStreamId()
- .then(streamId => {
+ .then(({ broadcastId, streamId }) => {
this.eventEmitter.emit(
UIEvents.RECORDING_TOGGLED,
- { streamId });
+ {
+ broadcastId,
+ streamId
+ });
// The confirm button on the start recording dialog was
// clicked
diff --git a/react/features/invite/components/InfoDialogButton.web.js b/react/features/invite/components/InfoDialogButton.web.js
index 57826919a..815847de2 100644
--- a/react/features/invite/components/InfoDialogButton.web.js
+++ b/react/features/invite/components/InfoDialogButton.web.js
@@ -35,12 +35,9 @@ class InfoDialogButton extends Component {
static propTypes = {
/**
- * Phone numbers for dialing into the conference.
+ * The redux state representing the dial-in numbers feature.
*/
- _dialInNumbers: PropTypes.oneOfType([
- PropTypes.object,
- PropTypes.array
- ]),
+ _dialIn: PropTypes.object,
/**
* Whether or not the {@code InfoDialog} should display automatically
@@ -48,6 +45,11 @@ class InfoDialogButton extends Component {
*/
_disableAutoShow: PropTypes.bool,
+ /**
+ * The URL for a currently active live broadcast
+ */
+ _liveStreamViewURL: PropTypes.string,
+
/**
* The number of real participants in the call. If in a lonely call,
* the {@code InfoDialog} will be automatically shown.
@@ -117,7 +119,7 @@ class InfoDialogButton extends Component {
this._maybeAutoShowDialog();
}, INFO_DIALOG_AUTO_SHOW_TIMEOUT);
- if (!this.props._dialInNumbers) {
+ if (!this.props._dialIn.numbers) {
this.props.dispatch(updateDialInNumbers());
}
}
@@ -150,7 +152,7 @@ class InfoDialogButton extends Component {
* @returns {ReactElement}
*/
render() {
- const { t } = this.props;
+ const { _dialIn, _liveStreamViewURL, t } = this.props;
const { showDialog } = this.state;
const iconClass = `icon-info ${showDialog ? 'toggled' : ''}`;
@@ -158,7 +160,10 @@ class InfoDialogButton extends Component {
}
+
}
isOpen = { showDialog }
onClose = { this._onDialogClose }
position = { 'top right' }>
@@ -215,16 +220,18 @@ class InfoDialogButton extends Component {
* @param {Object} state - The Redux state.
* @private
* @returns {{
- * _dialInNumbers: Array,
- * _disableAutoShow: bolean,
+ * _dialIn: Object,
+ * _disableAutoShow: boolean,
+ * _liveStreamViewURL: string,
* _participantCount: number,
* _toolboxVisible: boolean
* }}
*/
function _mapStateToProps(state) {
return {
- _dialInNumbers: state['features/invite'].numbers,
+ _dialIn: state['features/invite'],
_disableAutoShow: state['features/base/config'].iAmRecorder,
+ _liveStreamViewURL: state['features/recording'].liveStreamViewURL,
_participantCount:
getParticipantCount(state['features/base/participants']),
_toolboxVisible: state['features/toolbox'].visible
diff --git a/react/features/invite/components/info-dialog/InfoDialog.web.js b/react/features/invite/components/info-dialog/InfoDialog.web.js
index fa8302d3e..068ed0ed6 100644
--- a/react/features/invite/components/info-dialog/InfoDialog.web.js
+++ b/react/features/invite/components/info-dialog/InfoDialog.web.js
@@ -10,8 +10,6 @@ import {
getLocalParticipant
} from '../../../base/participants';
-import { updateDialInNumbers } from '../../actions';
-
import DialInNumber from './DialInNumber';
import PasswordForm from './PasswordForm';
@@ -24,15 +22,6 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
* @extends Component
*/
class InfoDialog extends Component {
- /**
- * Default values for {@code InfoDialog} component's properties.
- *
- * @static
- */
- static defaultProps = {
- autoUpdateNumbers: true
- };
-
/**
* {@code InfoDialog} component's property types.
*
@@ -57,11 +46,6 @@ class InfoDialog extends Component {
*/
_conferenceName: PropTypes.string,
- /**
- * The redux state representing the dial-in numbers feature.
- */
- _dialIn: PropTypes.object,
-
/**
* The current url of the conference to be copied onto the clipboard.
*/
@@ -79,17 +63,20 @@ class InfoDialog extends Component {
_password: PropTypes.string,
/**
- * Whether or not this component should make a request for dial-in
- * numbers. If false, this component will rely on an outside source
- * updating and passing in numbers through the _dialIn prop.
+ * The object representing the dialIn feature.
*/
- autoUpdateNumbers: PropTypes.bool,
+ dialIn: PropTypes.object,
/**
* Invoked to open a dialog for adding participants to the conference.
*/
dispatch: PropTypes.func,
+ /**
+ * The current known URL for a live stream in progress.
+ */
+ liveStreamViewURL: PropTypes.string,
+
/**
* Callback invoked when the dialog should be closed.
*/
@@ -129,7 +116,7 @@ class InfoDialog extends Component {
constructor(props) {
super(props);
- const { defaultCountry, numbers } = props._dialIn;
+ const { defaultCountry, numbers } = props.dialIn;
if (numbers) {
this.state.phoneNumber
@@ -147,7 +134,7 @@ class InfoDialog extends Component {
this._copyElement = null;
// Bind event handlers so they are only bound once for every instance.
- this._onClickInviteURL = this._onClickInviteURL.bind(this);
+ this._onClickURLText = this._onClickURLText.bind(this);
this._onCopyInviteURL = this._onCopyInviteURL.bind(this);
this._onPasswordRemove = this._onPasswordRemove.bind(this);
this._onPasswordSubmit = this._onPasswordSubmit.bind(this);
@@ -156,20 +143,6 @@ class InfoDialog extends Component {
this._setCopyElement = this._setCopyElement.bind(this);
}
- /**
- * Implements {@link Component#componentDidMount()}. Invoked immediately
- * after this component is mounted. Requests dial-in numbers if not
- * already known.
- *
- * @inheritdoc
- * @returns {void}
- */
- componentDidMount() {
- if (!this.state.phoneNumber && this.props.autoUpdateNumbers) {
- this.props.dispatch(updateDialInNumbers());
- }
- }
-
/**
* Implements React's {@link Component#componentWillReceiveProps()}. Invoked
* before this mounted component receives new props.
@@ -182,8 +155,8 @@ class InfoDialog extends Component {
this.setState({ passwordEditEnabled: false });
}
- if (!this.state.phoneNumber && nextProps._dialIn.numbers) {
- const { defaultCountry, numbers } = nextProps._dialIn;
+ if (!this.state.phoneNumber && nextProps.dialIn.numbers) {
+ const { defaultCountry, numbers } = nextProps.dialIn;
this.setState({
phoneNumber:
@@ -199,7 +172,7 @@ class InfoDialog extends Component {
* @returns {ReactElement}
*/
render() {
- const { onMouseOver, t } = this.props;
+ const { liveStreamViewURL, onMouseOver, t } = this.props;
return (
+ onClick = { this._onClickURLText } >
{ this._getURLToDisplay() }
@@ -231,6 +204,7 @@ class InfoDialog extends Component {
{ this._renderDialInDisplay() }
+ { liveStreamViewURL && this._renderLiveStreamURL() }
+ );
+ }
+
/**
* Returns whether or not dial-in related UI should be displayed.
*
@@ -497,7 +507,7 @@ class InfoDialog extends Component {
* @returns {boolean}
*/
_shouldDisplayDialIn() {
- const { conferenceID, numbers, numbersEnabled } = this.props._dialIn;
+ const { conferenceID, numbers, numbersEnabled } = this.props.dialIn;
const { phoneNumber } = this.state;
return Boolean(
@@ -531,7 +541,6 @@ class InfoDialog extends Component {
* _canEditPassword: boolean,
* _conference: Object,
* _conferenceName: string,
- * _dialIn: Object,
* _inviteURL: string,
* _locked: string,
* _password: string
@@ -558,7 +567,6 @@ function _mapStateToProps(state) {
_canEditPassword: canEditPassword,
_conference: conference,
_conferenceName: room,
- _dialIn: state['features/invite'],
_inviteURL: getInviteURL(state),
_locked: locked,
_password: password
diff --git a/react/features/recording/components/LiveStream/BroadcastsDropdown.web.js b/react/features/recording/components/LiveStream/BroadcastsDropdown.web.js
index fd4f1aafb..f7b0bc631 100644
--- a/react/features/recording/components/LiveStream/BroadcastsDropdown.web.js
+++ b/react/features/recording/components/LiveStream/BroadcastsDropdown.web.js
@@ -44,7 +44,7 @@ class BroadcastsDropdown extends PureComponent {
* The boundStreamID of the broadcast that should display as selected in
* the dropdown.
*/
- selectedBroadcastID: PropTypes.string,
+ selectedBoundStreamID: PropTypes.string,
/**
* Invoked to obtain translated strings.
@@ -84,7 +84,7 @@ class BroadcastsDropdown extends PureComponent {
* @returns {ReactElement}
*/
render() {
- const { broadcasts, selectedBroadcastID, t } = this.props;
+ const { broadcasts, selectedBoundStreamID, t } = this.props;
const dropdownItems = broadcasts.map(broadcast =>
// eslint-disable-next-line react/jsx-wrap-multilines
@@ -96,7 +96,7 @@ class BroadcastsDropdown extends PureComponent {
);
const selected = this.props.broadcasts.find(
- broadcast => broadcast.boundStreamID === selectedBroadcastID);
+ broadcast => broadcast.boundStreamID === selectedBoundStreamID);
const triggerText = (selected && selected.title)
|| t('liveStreaming.choose');
diff --git a/react/features/recording/components/LiveStream/StartLiveStreamDialog.web.js b/react/features/recording/components/LiveStream/StartLiveStreamDialog.web.js
index a3b70a4d1..7f0c71045 100644
--- a/react/features/recording/components/LiveStream/StartLiveStreamDialog.web.js
+++ b/react/features/recording/components/LiveStream/StartLiveStreamDialog.web.js
@@ -89,6 +89,8 @@ class StartLiveStreamDialog extends Component {
* available for use for the logged in Google user's YouTube account.
* @property {string} googleProfileEmail - The email of the user currently
* logged in to the Google web client application.
+ * @property {string} selectedBoundStreamID - The boundStreamID of the
+ * broadcast currently selected in the broadcast dropdown.
* @property {string} streamKey - The selected or entered stream key to use
* for YouTube live streaming.
*/
@@ -96,7 +98,7 @@ class StartLiveStreamDialog extends Component {
broadcasts: undefined,
googleAPIState: GOOGLE_API_STATES.NEEDS_LOADING,
googleProfileEmail: '',
- selectedBroadcastID: undefined,
+ selectedBoundStreamID: undefined,
streamKey: ''
};
@@ -291,7 +293,7 @@ class StartLiveStreamDialog extends Component {
_onStreamKeyChange(event) {
this._setStateIfMounted({
streamKey: event.target.value,
- selectedBroadcastID: undefined
+ selectedBoundStreamID: undefined
});
}
@@ -304,11 +306,22 @@ class StartLiveStreamDialog extends Component {
* closing, true to close the modal.
*/
_onSubmit() {
- if (!this.state.streamKey) {
+ const { streamKey, selectedBoundStreamID } = this.state;
+
+ if (!streamKey) {
return false;
}
- this.props.onSubmit(this.state.streamKey);
+ let selectedBroadcastID = null;
+
+ if (selectedBoundStreamID) {
+ const selectedBroadcast = this.state.broadcasts.find(
+ broadcast => broadcast.boundStreamID === selectedBoundStreamID);
+
+ selectedBroadcastID = selectedBroadcast && selectedBroadcast.id;
+ }
+
+ this.props.onSubmit(streamKey, selectedBroadcastID);
return true;
}
@@ -333,7 +346,7 @@ class StartLiveStreamDialog extends Component {
this._setStateIfMounted({
streamKey,
- selectedBroadcastID: boundStreamID
+ selectedBoundStreamID: boundStreamID
});
});
}
@@ -358,6 +371,7 @@ class StartLiveStreamDialog extends Component {
if (boundStreamID && !parsedBroadcasts[boundStreamID]) {
parsedBroadcasts[boundStreamID] = {
boundStreamID,
+ id: broadcast.id,
status: broadcast.status.lifeCycleStatus,
title: broadcast.snippet.title
};
@@ -378,7 +392,7 @@ class StartLiveStreamDialog extends Component {
const {
broadcasts,
googleProfileEmail,
- selectedBroadcastID
+ selectedBoundStreamID
} = this.state;
let googleContent, helpText;
@@ -399,7 +413,7 @@ class StartLiveStreamDialog extends Component {
+ selectedBoundStreamID = { selectedBoundStreamID } />
);
/**