Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(livestream): add separate fields for stream URL and stream key #13306

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions css/_recording.scss
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,14 @@

.google-panel {
align-items: center;
border-bottom: 2px solid rgba(0, 0, 0, 0.3);
border-bottom: 1px solid #5e6d7a;
display: flex;
flex-direction: column;
padding-bottom: 10px;
padding-bottom: 20px;
}

.stream-key-form {
padding-top: 20px;
}

.warning-text {
Expand Down
6 changes: 4 additions & 2 deletions lang/main-enGB.json
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,8 @@
"stopRecording": "Stop recording",
"stopRecordingWarning": "Are you sure you would like to stop the recording?",
"stopStreamingWarning": "Are you sure you would like to stop the live streaming?",
"streamKey": "Live stream key",
"streamBaseURL": "Stream URL",
"streamKey": "Stream key",
"thankYou": "Thank you for using {{appName}}!",
"token": "token",
"tokenAuthFailed": "Sorry, you're not allowed to join this call.",
Expand Down Expand Up @@ -373,7 +374,8 @@
"changeSignIn": "Switch accounts.",
"choose": "Choose a live stream",
"chooseCTA": "Choose a streaming option. You're currently logged in as {{email}}.",
"enterStreamKey": "Enter your YouTube live stream key here.",
"enterStreamBaseURL": "Enter your live stream URL here.",
"enterStreamKey": "Enter your live stream key here.",
"error": "Live Streaming failed. Please try again.",
"errorAPI": "An error occurred while accessing your YouTube broadcasts. Please try logging in again.",
"errorLiveStreamNotEnabled": "Live Streaming is not enabled on {{email}}. Please enable live streaming or log into an account with live streaming enabled.",
Expand Down
6 changes: 4 additions & 2 deletions lang/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,8 @@
"stopRecording": "Stop recording",
"stopRecordingWarning": "Are you sure you would like to stop the recording?",
"stopStreamingWarning": "Are you sure you would like to stop the live streaming?",
"streamKey": "Live stream key",
"streamBaseURL": "Stream URL",
"streamKey": "Stream key",
"thankYou": "Thank you for using {{appName}}!",
"token": "token",
"tokenAuthFailed": "Sorry, you're not allowed to join this call.",
Expand Down Expand Up @@ -554,7 +555,8 @@
"changeSignIn": "Switch accounts.",
"choose": "Choose a live stream",
"chooseCTA": "Choose a streaming option. You're currently logged in as {{email}}.",
"enterStreamKey": "Enter your YouTube live stream key here.",
"enterStreamBaseURL": "Enter your live stream URL here.",
"enterStreamKey": "Enter your live stream key here.",
"error": "Live Streaming failed. Please try again.",
"errorAPI": "An error occurred while accessing your YouTube broadcasts. Please try logging in again.",
"errorLiveStreamNotEnabled": "Live Streaming is not enabled on {{email}}. Please enable live streaming or log into an account with live streaming enabled.",
Expand Down
10 changes: 10 additions & 0 deletions react/features/recording/actionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ export const SET_PENDING_RECORDING_NOTIFICATION_UID
*/
export const SET_SELECTED_RECORDING_SERVICE = 'SET_SELECTED_RECORDING_SERVICE';

/**
* Sets the stream base url last used by the user for later reuse.
*
* {
* type: SET_STREAM_BASE_URL,
* streamKey: string
* }
*/
export const SET_STREAM_BASE_URL = 'SET_STREAM_BASE_URL';

/**
* Sets the stream key last used by the user for later reuse.
*
Expand Down
17 changes: 17 additions & 0 deletions react/features/recording/actions.any.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
SET_MEETING_HIGHLIGHT_BUTTON_STATE,
SET_PENDING_RECORDING_NOTIFICATION_UID,
SET_SELECTED_RECORDING_SERVICE,
SET_STREAM_BASE_URL,
SET_STREAM_KEY,
START_LOCAL_RECORDING,
STOP_LOCAL_RECORDING
Expand Down Expand Up @@ -81,6 +82,22 @@ export function hidePendingRecordingNotification(streamType: string) {
};
}

/**
* Sets the stream base url last used by the user for later reuse.
*
* @param {string} streamBaseURL - The stream base url to set.
* @returns {{
* type: SET_STREAM_BASE_URL,
* streamBaseURL: string
* }}
*/
export function setLiveStreamBaseURL(streamBaseURL: string) {
horymury marked this conversation as resolved.
Show resolved Hide resolved
return {
type: SET_STREAM_BASE_URL,
streamBaseURL
};
}

/**
* Sets the stream key last used by the user for later reuse.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { IReduxState } from '../../../app/types';
import { IJitsiConference } from '../../../base/conference/reducer';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';

import { YOUTUBE_RTMP_URL } from './constants';

/**
* The type of the React {@code Component} props of
* {@link AbstractStartLiveStreamDialog}.
Expand All @@ -30,6 +32,11 @@ export interface IProps extends WithTranslation {
*/
_googleProfileEmail: string;

/**
* The live stream base URL that was used before.
*/
_streamBaseURL?: string;

/**
* The live stream key that was used before.
*/
Expand Down Expand Up @@ -67,6 +74,11 @@ export interface IState {
*/
selectedBoundStreamID?: string;

/**
* The entered stream base URL to use for YouTube live streaming.
*/
streamBaseURL?: string;

/**
* The selected or entered stream key to use for YouTube live streaming.
*/
Expand Down Expand Up @@ -96,7 +108,8 @@ export default class AbstractStartLiveStreamDialog<P extends IProps>
broadcasts: undefined,
errorType: undefined,
selectedBoundStreamID: undefined,
streamKey: ''
streamKey: '',
streamBaseURL: YOUTUBE_RTMP_URL
};

/**
Expand All @@ -111,6 +124,7 @@ export default class AbstractStartLiveStreamDialog<P extends IProps>

this._onCancel = this._onCancel.bind(this);
this._onStreamKeyChange = this._onStreamKeyChange.bind(this);
this._onStreamBaseURLChange = this._onStreamBaseURLChange.bind(this);
this._onSubmit = this._onSubmit.bind(this);
}

Expand Down Expand Up @@ -176,6 +190,21 @@ export default class AbstractStartLiveStreamDialog<P extends IProps>
});
}

/**
* Callback invoked to update the {@code StartLiveStreamDialog} component's
* display of the entered YouTube stream base URL.
*
* @param {string} streamBaseURL - The stream base URL entered in the field.
* @private
* @returns {void}
*/
_onStreamBaseURLChange(streamBaseURL: string) {
this._setStateIfMounted({
streamBaseURL,
selectedBoundStreamID: undefined
});
}

/**
* Invokes the passed in {@link onSubmit} callback with the entered stream
* key, and then closes {@code StartLiveStreamDialog}.
Expand All @@ -188,11 +217,18 @@ export default class AbstractStartLiveStreamDialog<P extends IProps>
const { broadcasts, selectedBoundStreamID } = this.state;
const key
= (this.state.streamKey || this.props._streamKey || '').trim();
const base = (this.state.streamBaseURL || this.props._streamBaseURL || '').trim();

if (!key) {
if (!base) {
return false;
}

let rtmpURL = base;

if (key) {
rtmpURL = base.endsWith('/') ? base + key : `${base}/${key}`;
}

let selectedBroadcastID = null;

if (selectedBoundStreamID) {
Expand All @@ -204,11 +240,10 @@ export default class AbstractStartLiveStreamDialog<P extends IProps>

sendAnalytics(
createLiveStreamingDialogEvent('start', 'confirm.button'));

this.props._conference?.startRecording({
broadcastId: selectedBroadcastID,
mode: JitsiRecordingConstants.mode.STREAM,
streamId: key
streamId: rtmpURL
});

return true;
Expand Down Expand Up @@ -238,14 +273,16 @@ export default class AbstractStartLiveStreamDialog<P extends IProps>
* _conference: Object,
* _googleAPIState: number,
* _googleProfileEmail: string,
* _streamKey: string
* _streamKey: string,
* _streamBaseUrl: string
* }}
*/
export function _mapStateToProps(state: IReduxState) {
return {
_conference: state['features/base/conference'].conference,
_googleAPIState: state['features/google-api'].googleAPIState,
_googleProfileEmail: state['features/google-api'].profileEmail,
_streamKey: state['features/recording'].streamKey
_streamKey: state['features/recording'].streamKey,
_streamBaseURL: state['features/recording'].streamBaseURL
horymury marked this conversation as resolved.
Show resolved Hide resolved
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,25 @@ export interface IProps extends WithTranslation {

classes?: any;

/**
* Callback invoked when the entered stream base url has changed.
*/
onStreamBaseURLChange: Function;

/**
* Callback invoked when the entered stream key has changed.
*/
onChange: Function;
onStreamKeyChange: Function;

/**
* The stream base URL value to display as having been entered so far.
*/
streamBaseURLValue: string;

/**
* The stream key value to display as having been entered so far.
*/
value: string;
streamKeyValue: string;
}

/**
Expand Down Expand Up @@ -83,8 +93,8 @@ export default class AbstractStreamKeyForm<P extends IProps>
super(props);

this.state = {
showValidationError: Boolean(this.props.value)
&& !this._validateStreamKey(this.props.value)
showValidationError: Boolean(this.props.streamKeyValue)
&& !this._validateStreamKey(this.props.streamKeyValue)
};

this._debouncedUpdateValidationErrorVisibility = debounce(
Expand All @@ -94,7 +104,8 @@ export default class AbstractStreamKeyForm<P extends IProps>
);

// Bind event handlers so they are only bound once per instance.
this._onInputChange = this._onInputChange.bind(this);
this._onStreamKeyChange = this._onStreamKeyChange.bind(this);
this._onStreamBaseURLChange = this._onStreamBaseURLChange.bind(this);
}

/**
Expand All @@ -103,7 +114,7 @@ export default class AbstractStreamKeyForm<P extends IProps>
* @inheritdoc
*/
componentDidUpdate(prevProps: P) {
if (this.props.value !== prevProps.value) {
if (this.props.streamKeyValue !== prevProps.streamKeyValue) {
this._debouncedUpdateValidationErrorVisibility();
}
}
Expand All @@ -118,19 +129,35 @@ export default class AbstractStreamKeyForm<P extends IProps>
}

/**
* Callback invoked when the value of the input field has updated through
* Callback invoked when the value of the input stream baser url field has updated through
* user input. This forwards the value (string only, even if it was a dom
* event) to the onStreamBaseURLChange prop provided to the component.
*
* @param {Object | string} change - DOM Event for value change or the
* changed text.
* @private
* @returns {void}
*/
_onStreamBaseURLChange(change: any) {
const value = typeof change === 'object' ? change.target.value : change;

this.props.onStreamBaseURLChange(value);
}

/**
* Callback invoked when the value of the input stream key field has updated through
* user input. This forwards the value (string only, even if it was a dom
* event) to the onChange prop provided to the component.
* event) to the onStreamKeyChange prop provided to the component.
*
* @param {Object | string} change - DOM Event for value change or the
* changed text.
* @private
* @returns {void}
*/
_onInputChange(change: any) {
_onStreamKeyChange(change: any) {
const value = typeof change === 'object' ? change.target.value : change;

this.props.onChange(value);
this.props.onStreamKeyChange(value);
}

/**
Expand All @@ -142,8 +169,8 @@ export default class AbstractStreamKeyForm<P extends IProps>
* @returns {boolean}
*/
_updateValidationErrorVisibility() {
const newShowValidationError = Boolean(this.props.value)
&& !this._validateStreamKey(this.props.value);
const newShowValidationError = Boolean(this.props.streamKeyValue)
&& !this._validateStreamKey(this.props.streamKeyValue);

if (newShowValidationError !== this.state.showValidationError) {
this.setState({
Expand Down
6 changes: 6 additions & 0 deletions react/features/recording/components/LiveStream/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ export const GOOGLE_PRIVACY_POLICY = 'https://policies.google.com/privacy';
*/
export const YOUTUBE_LIVE_DASHBOARD_URL = 'https://www.youtube.com/live_dashboard';

/**
* The default live streaming URL to display.
*/
export const YOUTUBE_RTMP_URL = 'rtmp://a.rtmp.youtube.com/live2';

/**
* The URL for YouTube terms and conditions.
*/
Expand All @@ -24,3 +29,4 @@ export const JITSI_LIVE_STREAMING_HELP_LINK = 'https://jitsi.org/live';
*/
export const FOUR_GROUPS_DASH_SEPARATED = /^(?:[a-zA-Z0-9]{4}(?:-(?!$)|$)){4}/;


Loading