Skip to content

Commit

Permalink
feat(shared-video): Shows confirmation dialog before playing video.
Browse files Browse the repository at this point in the history
  • Loading branch information
damencho committed Aug 26, 2024
1 parent 5b4383d commit ee57d5a
Show file tree
Hide file tree
Showing 11 changed files with 218 additions and 29 deletions.
2 changes: 2 additions & 0 deletions lang/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,8 @@
"shareScreenWarningD2": "you need to stop audio sharing, start screen sharing and check the \"share audio\" option.",
"shareScreenWarningH1": "If you want to share just your screen:",
"shareScreenWarningTitle": "You need to stop audio sharing before sharing your screen",
"shareVideoConfirmPlay": "You’re about to open an external website. Do you want to continue?",
"shareVideoConfirmPlayTitle": "{{name}} has shared a video with you.",
"shareVideoLinkError": "Please provide a correct video link.",
"shareVideoTitle": "Share video",
"shareYourScreen": "Share your screen",
Expand Down
2 changes: 1 addition & 1 deletion modules/API/API.js
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ function initCommands() {
},
'start-share-video': url => {
sendAnalytics(createApiEvent('share.video.start'));
const id = extractYoutubeIdOrURL(url, APP.store.getState()['features/shared-video'].allowedUrlDomains);
const id = extractYoutubeIdOrURL(url);

if (id) {
APP.store.dispatch(playSharedVideo(id));
Expand Down
19 changes: 19 additions & 0 deletions react/features/shared-video/actionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,25 @@ export const SET_SHARED_VIDEO_STATUS = 'SET_SHARED_VIDEO_STATUS';
*/
export const RESET_SHARED_VIDEO_STATUS = 'RESET_SHARED_VIDEO_STATUS';

/**
* The type of the action which signals to mark that dialog for asking whether to load shared video is currently shown.
*
* {
* type: SET_DIALOG_IN_PROGRESS
* }
*/
export const SET_DIALOG_IN_PROGRESS = 'SET_DIALOG_IN_PROGRESS';

/**
* The type of the action which signals to mark that dialog for asking whether to load shared video is shown.
*
* {
* type: SET_DIALOG_SHOWN
* }
*/
export const SET_DIALOG_SHOWN = 'SET_DIALOG_SHOWN';


/**
* The type of the action which signals to disable or enable the shared video
* button.
Expand Down
40 changes: 36 additions & 4 deletions react/features/shared-video/actions.any.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,42 @@ import { getCurrentConference } from '../base/conference/functions';
import { openDialog } from '../base/dialog/actions';
import { getLocalParticipant } from '../base/participants/functions';

import { RESET_SHARED_VIDEO_STATUS, SET_ALLOWED_URL_DOMAINS, SET_SHARED_VIDEO_STATUS } from './actionTypes';
import {
RESET_SHARED_VIDEO_STATUS,
SET_ALLOWED_URL_DOMAINS, SET_DIALOG_IN_PROGRESS,
SET_DIALOG_SHOWN,
SET_SHARED_VIDEO_STATUS
} from './actionTypes';
import { SharedVideoDialog } from './components';
import { isSharedVideoEnabled, isURLAllowedForSharedVideo } from './functions';
import { isSharedVideoEnabled } from './functions';

/**
* Marks dialog is in progress.
*
* @param {boolean} value - The value to set.
* @returns {{
* type: SET_DIALOG_IN_PROGRESS,
* }}
*/
export function setDialogInProgress(value: boolean) {
return {
type: SET_DIALOG_IN_PROGRESS,
value
};
}

/**
* Marks that dialog was shown.
*
* @returns {{
* type: SET_DIALOG_SHOWN,
* }}
*/
export function setDialogShown() {
return {
type: SET_DIALOG_SHOWN
};
}

/**
* Resets the status of the shared video.
Expand Down Expand Up @@ -90,8 +123,7 @@ export function stopSharedVideo() {
*/
export function playSharedVideo(videoUrl: string) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
if (!isSharedVideoEnabled(getState())
|| !isURLAllowedForSharedVideo(videoUrl, getState()['features/shared-video'].allowedUrlDomains, true)) {
if (!isSharedVideoEnabled(getState())) {
return;
}
const conference = getCurrentConference(getState());
Expand Down
34 changes: 34 additions & 0 deletions react/features/shared-video/actions.native.ts
Original file line number Diff line number Diff line change
@@ -1 +1,35 @@
import i18n from 'i18next';

import { IStore } from '../app/types';
import { openDialog } from '../base/dialog/actions';
import ConfirmDialog from '../base/dialog/components/native/ConfirmDialog';

import { setDialogInProgress } from './actions.any';

export * from './actions.any';

/**
* Shows a confirmation dialog whether to play the external video link.
*
* @param {string} actor - The actor's name.
* @param {Function} onSubmit - The function to execute when confirmed.
*
* @returns {Function}
*/
export function showConfirmPlayingDialog(actor: String, onSubmit: Function): Function {
return (dispatch: IStore['dispatch']) => {
dispatch(openDialog(ConfirmDialog, {
cancelLabel: 'dialog.Cancel',
confirmLabel: 'dialog.Ok',
descriptionKey: 'dialog.shareVideoConfirmPlay',
onCancel: () => dispatch(setDialogInProgress(false)),
onSubmit: () => {
dispatch(setDialogInProgress(false));
onSubmit();
},
title: i18n.t('dialog.shareVideoConfirmPlayTitle', {
name: actor
})
}));
};
}
28 changes: 28 additions & 0 deletions react/features/shared-video/actions.web.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { IStore } from '../app/types';
import { openDialog } from '../base/dialog/actions';

import { SET_DISABLE_BUTTON } from './actionTypes';
import { setDialogInProgress, setDialogShown } from './actions.any';
import ShareVideoConfirmDialog from './components/web/ShareVideoConfirmDialog';

export * from './actions.any';

Expand All @@ -17,3 +22,26 @@ export function setDisableButton(disabled: boolean) {
disabled
};
}

/**
* Shows a confirmation dialog whether to play the external video link.
*
* @param {string} actor - The actor's name.
* @param {Function} onSubmit - The function to execute when confirmed.
*
* @returns {Function}
*/
export function showConfirmPlayingDialog(actor: String, onSubmit: Function) {
return (dispatch: IStore['dispatch']) => {
dispatch(setDialogInProgress(true));
dispatch(setDialogShown());
dispatch(openDialog(ShareVideoConfirmDialog, {
actorName: actor,
onCancel: () => dispatch(setDialogInProgress(false)),
onSubmit: () => {
dispatch(setDialogInProgress(false));
onSubmit();
}
}));
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ export default class AbstractSharedVideoDialog<S> extends Component < IProps, S
* @returns {boolean}
*/
_onSetVideoLink(link: string) {
const { _allowedUrlDomains, onPostSubmit } = this.props;
const { onPostSubmit } = this.props;

const id = extractYoutubeIdOrURL(link, _allowedUrlDomains);
const id = extractYoutubeIdOrURL(link);

if (!id) {
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import { useTranslation } from 'react-i18next';

import { DialogProps } from '../../../base/dialog/constants';
import Dialog from '../../../base/ui/components/web/Dialog';

interface IProps extends DialogProps {

/**
* The name of the remote participant that shared the video.
*/
actorName: string;

/**
* The function to execute when canceled.
*/
onCancel: () => void;

/**
* The function to execute when confirmed.
*/
onSubmit: () => void;
}

/**
* Dialog to confirm playing a video shared from a remote participant.
*
* @returns {JSX.Element}
*/
export default function ShareVideoConfirmDialog({ actorName, onCancel, onSubmit }: IProps): JSX.Element {
const { t } = useTranslation();

return (
<Dialog
onCancel = { onCancel }
onSubmit = { onSubmit }
title = { t('dialog.shareVideoConfirmPlayTitle', {
name: actorName
}) }>
<div>
{ t('dialog.shareVideoConfirmPlay') }
</div>
</Dialog>
);
}
20 changes: 10 additions & 10 deletions react/features/shared-video/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,9 @@ export function isVideoPlaying(stateful: IStateful): boolean {
* Extracts a Youtube id or URL from the user input.
*
* @param {string} input - The user input.
* @param {Array<string>} allowedUrlDomains - The allowed URL domains for shared video.
* @returns {string|undefined}
*/
export function extractYoutubeIdOrURL(input: string, allowedUrlDomains?: Array<string>) {
export function extractYoutubeIdOrURL(input: string) {
if (!input) {
return;
}
Expand All @@ -77,15 +76,17 @@ export function extractYoutubeIdOrURL(input: string, allowedUrlDomains?: Array<s
return;
}

if (areYoutubeURLsAllowedForSharedVideo(allowedUrlDomains)) {
const youtubeId = getYoutubeId(trimmedLink);
const youtubeId = getYoutubeId(trimmedLink);

if (youtubeId) {
return youtubeId;
}
if (youtubeId) {
return youtubeId;
}

if (!isURLAllowedForSharedVideo(trimmedLink, allowedUrlDomains)) {
// Check if the URL is valid, native may crash otherwise.
try {
// eslint-disable-next-line no-new
new URL(trimmedLink);
} catch (_) {
return;
}

Expand All @@ -101,10 +102,9 @@ export function extractYoutubeIdOrURL(input: string, allowedUrlDomains?: Array<s
export function isSharedVideoEnabled(stateful: IStateful) {
const state = toState(stateful);

const { allowedUrlDomains = [] } = toState(stateful)['features/shared-video'];
const { disableThirdPartyRequests = false } = state['features/base/config'];

return !disableThirdPartyRequests && allowedUrlDomains.length > 0;
return !disableThirdPartyRequests;
}

/**
Expand Down
34 changes: 23 additions & 11 deletions react/features/shared-video/middleware.any.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { SET_CONFIG } from '../base/config/actionTypes';
import { MEDIA_TYPE } from '../base/media/constants';
import { PARTICIPANT_LEFT } from '../base/participants/actionTypes';
import { participantJoined, participantLeft, pinParticipant } from '../base/participants/actions';
import { getLocalParticipant, getParticipantById } from '../base/participants/functions';
import { getLocalParticipant, getParticipantById, getParticipantDisplayName } from '../base/participants/functions';
import { FakeParticipant } from '../base/participants/types';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { SET_DYNAMIC_BRANDING_DATA } from '../dynamic-branding/actionTypes';
Expand All @@ -17,8 +17,9 @@ import { RESET_SHARED_VIDEO_STATUS, SET_SHARED_VIDEO_STATUS } from './actionType
import {
resetSharedVideoStatus,
setAllowedUrlDomians,
setSharedVideoStatus
} from './actions.any';
setSharedVideoStatus,
showConfirmPlayingDialog
} from './actions';
import {
DEFAULT_ALLOWED_URL_DOMAINS,
PLAYBACK_STATUSES,
Expand Down Expand Up @@ -53,18 +54,29 @@ MiddlewareRegistry.register(store => next => action => {
from: string; muted: string; state: string; time: string; }; value: string; }) => {
const state = getState();

if (!isURLAllowedForSharedVideo(value, getState()['features/shared-video'].allowedUrlDomains, true)) {
logger.debug(`Shared Video: Received a not allowed URL ${value}`);

return;
}

const { from } = attributes;
const sharedVideoStatus = attributes.state;

if (isSharingStatus(sharedVideoStatus)) {
handleSharingVideoStatus(store, value, attributes, conference);
} else if (sharedVideoStatus === 'stop') {
if (getState()['features/shared-video'].dialogInProgress) {
return;
}

if (isURLAllowedForSharedVideo(value) || localParticipantId === from
|| getState()['features/shared-video'].dialogShown) {
handleSharingVideoStatus(store, value, attributes, conference);
} else {
dispatch(showConfirmPlayingDialog(getParticipantDisplayName(getState(), from), () => {

Check failure on line 69 in react/features/shared-video/middleware.any.ts

View workflow job for this annotation

GitHub Actions / Lint

No overload matches this call.
handleSharingVideoStatus(store, value, attributes, conference);

return true; // on mobile this is used to close the dialog
}));
}

return;
}

if (sharedVideoStatus === 'stop') {
const videoParticipant = getParticipantById(state, value);

dispatch(participantLeft(value, conference, {
Expand Down
19 changes: 18 additions & 1 deletion react/features/shared-video/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@ import ReducerRegistry from '../base/redux/ReducerRegistry';
import {
RESET_SHARED_VIDEO_STATUS,
SET_ALLOWED_URL_DOMAINS,
SET_DIALOG_IN_PROGRESS,
SET_DIALOG_SHOWN,
SET_DISABLE_BUTTON,
SET_SHARED_VIDEO_STATUS
} from './actionTypes';
import { DEFAULT_ALLOWED_URL_DOMAINS } from './constants';

const initialState = {
allowedUrlDomains: DEFAULT_ALLOWED_URL_DOMAINS
allowedUrlDomains: DEFAULT_ALLOWED_URL_DOMAINS,
dialogInProgress: false,
dialogShown: false
};

export interface ISharedVideoState {
allowedUrlDomains: Array<string>;
dialogInProgress: boolean;
dialogShown: boolean;
disabled?: boolean;
muted?: boolean;
ownerId?: string;
Expand All @@ -36,6 +42,17 @@ ReducerRegistry.register<ISharedVideoState>('features/shared-video',
...initialState,
allowedUrlDomains: state.allowedUrlDomains
};
case SET_DIALOG_IN_PROGRESS: {
return {
...state,
dialogInProgress: action.value
};
}
case SET_DIALOG_SHOWN:
return {
...state,
dialogShown: true
};
case SET_SHARED_VIDEO_STATUS:
return {
...state,
Expand Down

0 comments on commit ee57d5a

Please sign in to comment.