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(FEC-13506): Add Captions language selector in bottom bar #871

Merged
merged 5 commits into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
120 changes: 120 additions & 0 deletions src/components/captions-control/captions-control.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import {h} from 'preact';
import {connect, useSelector} from 'react-redux';
import {ClosedCaptions} from '../closed-captions';
import {CaptionsMenu} from '../captions-menu';
import {ButtonControl} from '../button-control';
import {Tooltip} from '../tooltip';
import {Button} from '../button';
import style from '../../styles/style.scss';
import {Icon, IconType} from '../icon';
import {SmartContainer} from '../smart-container';
import {Text, withText} from 'preact-i18n';
import {useRef, useState, useEffect, useCallback} from 'preact/hooks';
import {focusElement} from '../../utils';
import {createPortal} from 'preact/compat';
import {CVAAOverlay} from '../cvaa-overlay';

/**
* mapping state to props
* @param {*} state - redux store state
* @returns {Object} - mapped state to this component
*/
const mapStateToProps = state => ({
textTracks: state.engine.textTracks,
showCCButton: state.config.showCCButton,
openMenuFromCCCButton: state.config.openMenuFromCCButton,
isMobile: state.shell.isMobile,
isSmallSize: state.shell.isSmallSize,
isCVAAOverlayOpen: state.shell.isCVAAOverlayOpen
});

const COMPONENT_NAME = 'CaptionsControl';

/**
* CaptionsControl component
*
* @class CaptionsControl
* @example <CaptionsControl />
* @extends {Component}
*/
const CaptionsControl = connect(mapStateToProps)(
withText({
captionsLabelText: 'captions.captions',
advancedCaptionsSettingsText: 'captions.advanced_captions_settings'
})((props, context) => {
const [smartContainerOpen, setSmartContainerOpen] = useState(false);
const [cvaaOverlay, setCVAAOverlay] = useState(false);
const [ccOn, setCCOn] = useState(false);
const buttonRef = useRef<HTMLElement>(null);
const controlCaptionsElement = useRef<HTMLElement>(null);

const {player} = context;
const {isSmallSize, isMobile, textTracks} = props;
const activeTextTrack = textTracks.find(textTrack => textTrack.active);

const onControlButtonClick = (e?: KeyboardEvent, byKeyboard?: boolean): void => {
setSmartContainerOpen(smartContainerOpen => !smartContainerOpen);
if (byKeyboard && smartContainerOpen) {
focusElement(buttonRef.current);
}
};

const toggleCVAAOverlay = (): void => {
setCVAAOverlay(cvaaOverlay => !cvaaOverlay);
};

const onCVAAOverlayClose = (e?: KeyboardEvent, byKeyboard?: boolean): void => {
toggleCVAAOverlay();
onControlButtonClick(e, byKeyboard);
};

const handleClickOutside = (e: any) => {
if (!isMobile && !isSmallSize && !!controlCaptionsElement.current && !controlCaptionsElement.current.contains(e.target)) {
setSmartContainerOpen(false);
}
};

useEffect(() => {
document.addEventListener('click', handleClickOutside);
return () => document.removeEventListener('click', handleClickOutside);
}, [isSmallSize, isMobile]);

useEffect(() => {
setCCOn(activeTextTrack?.language !== 'off');
}, [activeTextTrack]);

const shouldRender = !!textTracks?.length && props.showCCButton;
props.onToggle(COMPONENT_NAME, shouldRender);
if (!shouldRender) return undefined;

const targetId: HTMLDivElement | Document = (document.getElementById(player.config.targetId) as HTMLDivElement) || document;
const portalSelector = `.overlay-portal`;

return props.openMenuFromCCCButton ? (
<ButtonControl name={COMPONENT_NAME} ref={controlCaptionsElement}>
<Tooltip label={props.captionsLabelText}>
<Button
ref={buttonRef}
tabIndex="0"
aria-label={props.captionsLabelText}
aria-haspopup="true"
className={[style.controlButton, smartContainerOpen ? style.active : ''].join(' ')}
onClick={onControlButtonClick}>
<Icon type={ccOn ? IconType.ClosedCaptionsOn : IconType.ClosedCaptionsOff} />
</Button>
</Tooltip>
{smartContainerOpen && !cvaaOverlay && (
<SmartContainer targetId={player.config.targetId} onClose={() => setSmartContainerOpen(false)} title={<Text id="controls.language" />}>
<CaptionsMenu asDropdown={false} onAdvancedCaptionsClick={toggleCVAAOverlay} />
</SmartContainer>
)}
{cvaaOverlay ? createPortal(<CVAAOverlay onClose={onCVAAOverlayClose} />, targetId.querySelector(portalSelector)!) : <div />}
</ButtonControl>
) : (
<ClosedCaptions />
);
})
);

CaptionsControl.displayName = COMPONENT_NAME;
export {CaptionsControl};
1 change: 1 addition & 0 deletions src/components/captions-control/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {CaptionsControl} from './captions-control';
27 changes: 16 additions & 11 deletions src/components/captions-menu/captions-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {withEventDispatcher} from '../event-dispatcher';
import {KeyMap} from '../../utils';
import {withKeyboardEvent} from '../../components/keyboard';
import {KeyboardEventHandlers} from '../../types';
import {Menu} from '../menu';

/**
* mapping state to props
Expand Down Expand Up @@ -122,17 +123,21 @@ class CaptionsMenu extends Component<any, any> {
textOptions.push({label: props.advancedCaptionsSettingsText, value: props.advancedCaptionsSettingsText, active: false});
}

return (
<SmartContainerItem
pushRef={el => {
props.pushRef(el);
}}
icon={IconType.Captions}
label={this.props.captionsLabelText}
options={textOptions}
onMenuChosen={textTrack => this.onCaptionsChange(textTrack)}
/>
);
if (this.props.asDropdown) {
return (
<SmartContainerItem
pushRef={el => {
props.pushRef(el);
}}
icon={IconType.Captions}
label={this.props.captionsLabelText}
options={textOptions}
onMenuChosen={textTrack => this.onCaptionsChange(textTrack)}
/>
);
} else {
return <Menu options={textOptions} onMenuChosen={textTrack => this.onCaptionsChange(textTrack)} onClose={() => {}} />;
}
}
}

Expand Down
4 changes: 0 additions & 4 deletions src/components/closed-captions/closed-captions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {ButtonControl} from '../button-control';
*/
const mapStateToProps = state => ({
textTracks: state.engine.textTracks,
showCCButton: state.config.showCCButton
});

const COMPONENT_NAME = 'ClosedCaptions';
Expand Down Expand Up @@ -44,9 +43,6 @@ const ClosedCaptions = connect(mapStateToProps)(
setCCOn(activeTextTrack?.language !== 'off');
}, [activeTextTrack]);

const shouldRender = !!(props.textTracks?.length && props.showCCButton);
props.onToggle(COMPONENT_NAME, shouldRender);
if (!shouldRender) return undefined;
return (
<ButtonControl name={COMPONENT_NAME}>
{ccOn ? (
Expand Down
3 changes: 1 addition & 2 deletions src/components/cvaa-overlay/cvaa-overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,7 @@ class CVAAOverlay extends Component<any, any> {
addAccessibleChild={this.props.addAccessibleChild}
onClose={props.onClose}
type="cvaa"
label={props.cvvaDialogText}
>
label={props.cvvaDialogText}>
{this.state.activeWindow === cvaaOverlayState.Main ? (
<MainCaptionsWindow
cvaaOverlayState={cvaaOverlayState}
Expand Down
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,4 @@ export {Settings, Settings as SettingsControl};
export {Volume, Volume as VolumeControl};
export {VrStereo, VrStereo as VrStereoControl};
export {ClosedCaptions, ClosedCaptions as ClosedCaptionsControl};
export {CaptionsControl} from './captions-control';
2 changes: 1 addition & 1 deletion src/components/settings/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ class Settings extends Component<any, any> {
<SmartContainer targetId={props.player.config.targetId} title={<Text id="settings.title" />} onClose={this.onControlButtonClick}>
{showAdvancedAudioDescToggle && <AdvancedAudioDescToggle />}
{showAudioMenu && <AudioMenu />}
{showCaptionsMenu && <CaptionsMenu onAdvancedCaptionsClick={this.toggleCVAAOverlay} />}
{showCaptionsMenu && <CaptionsMenu asDropdown={true} onAdvancedCaptionsClick={this.toggleCVAAOverlay} />}
{showQualityMenu && <QualityMenu />}
{showSpeedMenu && <SpeedMenu />}
</SmartContainer>
Expand Down
5 changes: 5 additions & 0 deletions src/components/smart-container/smart-container-item-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum SmartContainerItemType {
DropDown,
Menu,
ToggleSwitch
}
1 change: 1 addition & 0 deletions src/reducers/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const initialState = {
targetId: undefined as unknown as string,
forceTouchUI: false,
showCCButton: true,
openMenuFromCCButton: false,
settings: {
showAudioMenu: true,
showCaptionsMenu: true,
Expand Down
1 change: 1 addition & 0 deletions src/types/ui-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface UIOptionsObject {
debugActions?: boolean;
forceTouchUI?: boolean;
showCCButton?: boolean;
openMenuFromCCButton?: boolean;
settings?: {
showAudioMenu?: boolean;
showCaptionsMenu?: boolean;
Expand Down
4 changes: 2 additions & 2 deletions src/ui-presets/playback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {PrePlaybackPlayOverlay} from '../components';
import {Loading} from '../components';
import {Rewind} from '../components';
import {Forward} from '../components';
import {CaptionsControl} from '../components';
import {SeekBarPlaybackContainer} from '../components';
import {Volume} from '../components';
import {Settings} from '../components';
Expand Down Expand Up @@ -89,8 +90,7 @@ class PlaybackUI extends Component<any, any> {
</InteractiveArea>
<BottomBar
leftControls={[PlaybackControls, Rewind, Forward, TimeDisplayPlaybackContainer]}
rightControls={[VrStereo, Volume, ClosedCaptions, Settings, Cast, PictureInPicture, Fullscreen, Logo]}
>
rightControls={[VrStereo, Volume, CaptionsControl, Settings, Cast, PictureInPicture, Fullscreen, Logo]}>
<SeekBarPlaybackContainer showFramePreview showTimeBubble playerContainer={containerRef} />
</BottomBar>
</Fragment>
Expand Down
Loading