feat(liveStreamting) add configuration to customize streaming dialog

This commit is contained in:
Steffen Kolmer 2022-08-02 10:27:18 +02:00 committed by GitHub
parent 3f0e50a9cd
commit 87f6d27fb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 165 additions and 38 deletions

View File

@ -310,9 +310,6 @@ var config = {
// DEPRECATED. Use recordingService.sharingEnabled instead. // DEPRECATED. Use recordingService.sharingEnabled instead.
// fileRecordingsServiceSharingEnabled: false, // fileRecordingsServiceSharingEnabled: false,
// Whether to enable live streaming or not.
// liveStreamingEnabled: false,
// Local recording configuration. // Local recording configuration.
// localRecording: { // localRecording: {
// // Whether to disable local recording or not. // // Whether to disable local recording or not.
@ -325,6 +322,23 @@ var config = {
// disableSelfRecording: false, // disableSelfRecording: false,
// }, // },
// Customize the Live Streaming dialog. Can be modified for a non-YouTube provider.
// liveStreaming: {
// // Whether to enable live streaming or not.
// enabled: false,
// // Terms link
// termsLink: 'https://www.youtube.com/t/terms',
// // Data privacy link
// dataPrivacyLink: 'https://policies.google.com/privacy',
// // RegExp string that validates the stream key input field
// validatorRegExpString: '^(?:[a-zA-Z0-9]{4}(?:-(?!$)|$)){4}',
// // Documentation reference for the live streaming feature.
// helpLink: 'https://jitsi.org/live'
// },
// DEPRECATED. Use liveStreaming.enabled instead.
// liveStreamingEnabled: false,
// DEPRECATED. Use transcription.enabled instead. // DEPRECATED. Use transcription.enabled instead.
// transcribingEnabled: false, // transcribingEnabled: false,

View File

@ -90,7 +90,6 @@ var interfaceConfig = {
JITSI_WATERMARK_LINK: 'https://jitsi.org', JITSI_WATERMARK_LINK: 'https://jitsi.org',
LANG_DETECTION: true, // Allow i18n to detect the system language LANG_DETECTION: true, // Allow i18n to detect the system language
LIVE_STREAMING_HELP_LINK: 'https://jitsi.org/live', // Documentation reference for the live streaming feature.
LOCAL_THUMBNAIL_RATIO: 16 / 9, // 16:9 LOCAL_THUMBNAIL_RATIO: 16 / 9, // 16:9
/** /**
@ -248,6 +247,10 @@ var interfaceConfig = {
// Moved to config.js as `toolbarConfig.initialTimeout`. // Moved to config.js as `toolbarConfig.initialTimeout`.
// INITIAL_TOOLBAR_TIMEOUT: 20000, // INITIAL_TOOLBAR_TIMEOUT: 20000,
// Please use `liveStreaming.helpLink` from config.js
// Documentation reference for the live streaming feature.
// LIVE_STREAMING_HELP_LINK: 'https://jitsi.org/live',
// Moved to config.js as `toolbarConfig.alwaysVisible`. // Moved to config.js as `toolbarConfig.alwaysVisible`.
// TOOLBAR_ALWAYS_VISIBLE: false, // TOOLBAR_ALWAYS_VISIBLE: false,

View File

@ -332,6 +332,13 @@ export interface IConfig {
lastNLimits?: { lastNLimits?: {
[key: number]: number; [key: number]: number;
}; };
liveStreaming?: {
dataPrivacyLink?: string;
enabled?: boolean;
helpLink?: string;
termsLink?: string;
validatorRegExpString?: string;
};
liveStreamingEnabled?: boolean; liveStreamingEnabled?: boolean;
localRecording?: { localRecording?: {
disable?: boolean; disable?: boolean;

View File

@ -183,6 +183,7 @@ export default [
'iceTransportPolicy', 'iceTransportPolicy',
'ignoreStartMuted', 'ignoreStartMuted',
'inviteAppName', 'inviteAppName',
'liveStreaming',
'liveStreamingEnabled', 'liveStreamingEnabled',
'localRecording', 'localRecording',
'localSubject', 'localSubject',

View File

@ -420,6 +420,26 @@ function _translateLegacyConfig(oldValue: IConfig) {
}; };
} }
newValue.liveStreaming = newValue.liveStreaming || {};
// Migrate config.liveStreamingEnabled
if (oldValue.liveStreamingEnabled !== undefined) {
newValue.liveStreaming = {
...newValue.liveStreaming,
enabled: oldValue.liveStreamingEnabled
};
}
// Migrate interfaceConfig.LIVE_STREAMING_HELP_LINK
if (oldValue.liveStreaming === undefined
&& typeof interfaceConfig === 'object'
&& interfaceConfig.hasOwnProperty('LIVE_STREAMING_HELP_LINK')) {
newValue.liveStreaming = {
...newValue.liveStreaming,
helpLink: interfaceConfig.LIVE_STREAMING_HELP_LINK
};
}
return newValue; return newValue;
} }

View File

@ -3,6 +3,7 @@
import type { Dispatch } from 'redux'; import type { Dispatch } from 'redux';
import { getShareInfoText } from '../invite'; import { getShareInfoText } from '../invite';
import { getLiveStreaming } from '../recording/components/LiveStream/functions';
import { import {
SET_GOOGLE_API_PROFILE, SET_GOOGLE_API_PROFILE,
@ -36,15 +37,16 @@ export function loadGoogleAPI() {
googleApi.get() googleApi.get()
.then(() => { .then(() => {
const { const {
liveStreamingEnabled,
enableCalendarIntegration, enableCalendarIntegration,
googleApiApplicationClientID googleApiApplicationClientID
} = getState()['features/base/config']; } = getState()['features/base/config'];
const liveStreaming = getLiveStreaming(getState());
if (getState()['features/google-api'].googleAPIState if (getState()['features/google-api'].googleAPIState
=== GOOGLE_API_STATES.NEEDS_LOADING) { === GOOGLE_API_STATES.NEEDS_LOADING) {
return googleApi.initializeClient( return googleApi.initializeClient(
googleApiApplicationClientID, liveStreamingEnabled, enableCalendarIntegration); googleApiApplicationClientID, liveStreaming.enabled, enableCalendarIntegration);
} }
return Promise.resolve(); return Promise.resolve();

View File

@ -12,6 +12,8 @@ import { maybeShowPremiumFeatureDialog } from '../../../jaas/actions';
import { FEATURES } from '../../../jaas/constants'; import { FEATURES } from '../../../jaas/constants';
import { getActiveSession } from '../../functions'; import { getActiveSession } from '../../functions';
import { getLiveStreaming } from './functions';
/** /**
* The type of the React {@code Component} props of * The type of the React {@code Component} props of
@ -141,12 +143,12 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
// its own to be visible or not. // its own to be visible or not.
const isModerator = isLocalParticipantModerator(state); const isModerator = isLocalParticipantModerator(state);
const { const {
enableFeaturesBasedOnToken, enableFeaturesBasedOnToken
liveStreamingEnabled
} = state['features/base/config']; } = state['features/base/config'];
const liveStreaming = getLiveStreaming(state);
const { features = {} } = getLocalParticipant(state); const { features = {} } = getLocalParticipant(state);
visible = isModerator && liveStreamingEnabled; visible = isModerator && liveStreaming.enabled;
if (enableFeaturesBasedOnToken) { if (enableFeaturesBasedOnToken) {
visible = visible && String(features.livestreaming) === 'true'; visible = visible && String(features.livestreaming) === 'true';

View File

@ -3,21 +3,35 @@
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { Component } from 'react'; import { Component } from 'react';
declare var interfaceConfig: Object; import { getLiveStreaming } from './functions';
/**
* The live streaming help link to display. On web it comes from export type LiveStreaming = {
* interfaceConfig, but we don't have that on mobile. enabled: boolean,
* helpLink: string, // Documentation reference for the live streaming feature.
* FIXME: This is in props now to prepare for the Redux-based interfaceConfig. termsLink: string, // Terms link
*/ dataPrivacyLink: string, // Data privacy link
const LIVE_STREAMING_HELP_LINK = 'https://jitsi.org/live'; validatorRegExpString: string // RegExp string that validates the stream key input field
}
export type LiveStreamingProps = {
enabled: boolean,
helpURL: string,
termsURL: string,
dataPrivacyURL: string,
streamLinkRegexp: RegExp
}
/** /**
* The props of the component. * The props of the component.
*/ */
export type Props = { export type Props = {
/**
* The live streaming dialog properties.
*/
_liveStreaming: LiveStreamingProps,
/** /**
* Callback invoked when the entered stream key has changed. * Callback invoked when the entered stream key has changed.
*/ */
@ -54,7 +68,7 @@ type State = {
*/ */
export default class AbstractStreamKeyForm<P: Props> export default class AbstractStreamKeyForm<P: Props>
extends Component<P, State> { extends Component<P, State> {
helpURL: string;
_debouncedUpdateValidationErrorVisibility: Function; _debouncedUpdateValidationErrorVisibility: Function;
/** /**
@ -70,10 +84,6 @@ export default class AbstractStreamKeyForm<P: Props>
&& !this._validateStreamKey(this.props.value) && !this._validateStreamKey(this.props.value)
}; };
this.helpURL = (typeof interfaceConfig !== 'undefined'
&& interfaceConfig.LIVE_STREAMING_HELP_LINK)
|| LIVE_STREAMING_HELP_LINK;
this._debouncedUpdateValidationErrorVisibility = debounce( this._debouncedUpdateValidationErrorVisibility = debounce(
this._updateValidationErrorVisibility.bind(this), this._updateValidationErrorVisibility.bind(this),
800, 800,
@ -150,9 +160,22 @@ export default class AbstractStreamKeyForm<P: Props>
*/ */
_validateStreamKey(streamKey = '') { _validateStreamKey(streamKey = '') {
const trimmedKey = streamKey.trim(); const trimmedKey = streamKey.trim();
const fourGroupsDashSeparated = /^(?:[a-zA-Z0-9]{4}(?:-(?!$)|$)){4}/; const match = this.props._liveStreaming.streamLinkRegexp.exec(trimmedKey);
const match = fourGroupsDashSeparated.exec(trimmedKey);
return Boolean(match); return Boolean(match);
} }
} }
/**
* Maps part of the Redux state to the component's props.
*
* @param {Object} state - The Redux state.
* @returns {{
* _liveStreaming: LiveStreamingProps
* }}
*/
export function _mapStateToProps(state: Object) {
return {
_liveStreaming: getLiveStreaming(state)
};
}

View File

@ -13,3 +13,14 @@ export const YOUTUBE_LIVE_DASHBOARD_URL = 'https://www.youtube.com/live_dashboar
* The URL for YouTube terms and conditions. * The URL for YouTube terms and conditions.
*/ */
export const YOUTUBE_TERMS_URL = 'https://www.youtube.com/t/terms'; export const YOUTUBE_TERMS_URL = 'https://www.youtube.com/t/terms';
/**
* The live streaming help link to display.
*/
export const JITSI_LIVE_STREAMING_HELP_LINK = 'https://jitsi.org/live';
/**
* The YouTube stream link RegExp.
*/
export const FOUR_GROUPS_DASH_SEPARATED = /^(?:[a-zA-Z0-9]{4}(?:-(?!$)|$)){4}/;

View File

@ -0,0 +1,27 @@
import {
FOUR_GROUPS_DASH_SEPARATED,
GOOGLE_PRIVACY_POLICY,
JITSI_LIVE_STREAMING_HELP_LINK,
YOUTUBE_TERMS_URL
} from './constants';
/**
* Get the live streaming options.
*
* @param {Object} state - The global state.
* @returns {LiveStreaming}
*/
export function getLiveStreaming(state: Object) {
const { liveStreaming = {} } = state['features/base/config'];
const regexp = liveStreaming.validatorRegExpString
&& new RegExp(liveStreaming.validatorRegExpString);
return {
enabled: Boolean(liveStreaming.enabled),
helpURL: liveStreaming.helpLink || JITSI_LIVE_STREAMING_HELP_LINK,
termsURL: liveStreaming.termsLink || YOUTUBE_TERMS_URL,
dataPrivacyURL: liveStreaming.dataPrivacyLink || GOOGLE_PRIVACY_POLICY,
streamLinkRegexp: regexp || FOUR_GROUPS_DASH_SEPARATED
};
}

View File

@ -10,7 +10,9 @@ import { StyleType } from '../../../../base/styles';
import AbstractStreamKeyForm, { import AbstractStreamKeyForm, {
type Props as AbstractProps type Props as AbstractProps
} from '../AbstractStreamKeyForm'; } from '../AbstractStreamKeyForm';
import { GOOGLE_PRIVACY_POLICY, YOUTUBE_TERMS_URL } from '../constants'; import { getLiveStreaming } from '../functions';
import styles, { PLACEHOLDER_COLOR } from './styles';
type Props = AbstractProps & { type Props = AbstractProps & {
@ -20,8 +22,6 @@ type Props = AbstractProps & {
_dialogStyles: StyleType _dialogStyles: StyleType
}; };
import styles, { PLACEHOLDER_COLOR } from './styles';
/** /**
* A React Component for entering a key for starting a YouTube live stream. * A React Component for entering a key for starting a YouTube live stream.
* *
@ -148,7 +148,7 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
* @returns {void} * @returns {void}
*/ */
_onOpenGooglePrivacyPolicy() { _onOpenGooglePrivacyPolicy() {
Linking.openURL(GOOGLE_PRIVACY_POLICY); Linking.openURL(this.props._liveStreaming.dataPrivacyURL);
} }
_onOpenHelp: () => void; _onOpenHelp: () => void;
@ -161,7 +161,7 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
* @returns {void} * @returns {void}
*/ */
_onOpenHelp() { _onOpenHelp() {
const { helpURL } = this; const helpURL = this.props._liveStreaming.helpURL;
if (typeof helpURL === 'string') { if (typeof helpURL === 'string') {
Linking.openURL(helpURL); Linking.openURL(helpURL);
@ -177,8 +177,25 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
* @returns {void} * @returns {void}
*/ */
_onOpenYoutubeTerms() { _onOpenYoutubeTerms() {
Linking.openURL(YOUTUBE_TERMS_URL); Linking.openURL(this.props._liveStreaming.termsURL);
} }
} }
export default translate(connect(_abstractMapStateToProps)(StreamKeyForm)); /**
* Maps (parts of) the redux state to the associated props for the
* {@code StreamKeyForm} component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _liveStreaming: LiveStreamingProps
* }}
*/
function _mapStateToProps(state: Object) {
return {
..._abstractMapStateToProps(state),
_liveStreaming: getLiveStreaming(state)
};
}
export default translate(connect(_mapStateToProps)(StreamKeyForm));

View File

@ -4,10 +4,10 @@ import { FieldTextStateless } from '@atlaskit/field-text';
import React from 'react'; import React from 'react';
import { translate } from '../../../../base/i18n'; import { translate } from '../../../../base/i18n';
import { connect } from '../../../../base/redux';
import AbstractStreamKeyForm, { import AbstractStreamKeyForm, {
type Props type Props, _mapStateToProps
} from '../AbstractStreamKeyForm'; } from '../AbstractStreamKeyForm';
import { GOOGLE_PRIVACY_POLICY, YOUTUBE_TERMS_URL } from '../constants';
/** /**
* A React Component for entering a key for starting a YouTube live stream. * A React Component for entering a key for starting a YouTube live stream.
@ -62,7 +62,7 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
</span> </span>
: null : null
} }
{ this.helpURL { this.props._liveStreaming.helpURL
? <a ? <a
aria-label = { t('liveStreaming.streamIdHelp') } aria-label = { t('liveStreaming.streamIdHelp') }
className = 'helper-link' className = 'helper-link'
@ -77,14 +77,14 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
</div> </div>
<a <a
className = 'helper-link' className = 'helper-link'
href = { YOUTUBE_TERMS_URL } href = { this.props._liveStreaming.termsURL }
rel = 'noopener noreferrer' rel = 'noopener noreferrer'
target = '_blank'> target = '_blank'>
{ t('liveStreaming.youtubeTerms') } { t('liveStreaming.youtubeTerms') }
</a> </a>
<a <a
className = 'helper-link' className = 'helper-link'
href = { GOOGLE_PRIVACY_POLICY } href = { this.props._liveStreaming.dataPrivacyURL }
rel = 'noopener noreferrer' rel = 'noopener noreferrer'
target = '_blank'> target = '_blank'>
{ t('liveStreaming.googlePrivacyPolicy') } { t('liveStreaming.googlePrivacyPolicy') }
@ -106,7 +106,7 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
* @returns {void} * @returns {void}
*/ */
_onOpenHelp() { _onOpenHelp() {
window.open(this.helpURL, '_blank', 'noopener'); window.open(this.props._liveStreaming.helpURL, '_blank', 'noopener');
} }
_onOpenHelpKeyPress: () => void; _onOpenHelpKeyPress: () => void;
@ -128,4 +128,4 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
} }
} }
export default translate(StreamKeyForm); export default translate(connect(_mapStateToProps)(StreamKeyForm));