Skip to content

Commit

Permalink
feat(FEC-12015): CC button for on/off subtitles (#670)
Browse files Browse the repository at this point in the history
add CC button component
add `showCCButton` option to the ui config (default `false`)
add `UIElementClickedEvent` event for generic element click

Solves FEC-12015
  • Loading branch information
yairans committed Apr 12, 2022
1 parent fb1a4c1 commit b298776
Show file tree
Hide file tree
Showing 16 changed files with 181 additions and 2 deletions.
11 changes: 11 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ var uiManager = new playkit.ui.UIManager(player, config);
targetId: string,
debugActions?: boolean, // optional
forceTouchUI?: boolean, // optional
showCCButton?: boolean, // optional
hoverTimeout?: number, // optional
logger?: loggerType, // optional
components?: Object, // optional
Expand Down Expand Up @@ -61,6 +62,16 @@ var uiManager = new playkit.ui.UIManager(player, config);
##

> ### config.showCCButton
>
> ##### Type: `boolean`
>
> ##### Default: `false`
>
> ##### Description: Whether to show enable/disable captions button in the bottom bar.
##

> ### config.hoverTimeout
>
> ##### Type: `number`
Expand Down
14 changes: 14 additions & 0 deletions docs/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
| [`USER_CLICKED_UNMUTE`](#USER_CLICKED_UNMUTE) |
| [`USER_CHANGED_VOLUME`](#USER_CHANGED_VOLUME) |
| [`USER_SELECTED_CAPTION_TRACK`](#USER_SELECTED_CAPTION_TRACK) |
| [`USER_SHOWED_CAPTIONS`](#USER_SHOWED_CAPTIONS) |
| [`USER_HID_CAPTIONS`](#USER_HID_CAPTIONS) |
| [`USER_SELECTED_AUDIO_TRACK`](#USER_SELECTED_AUDIO_TRACK) |
| [`USER_SELECTED_QUALITY_TRACK`](#USER_SELECTED_QUALITY_TRACK) |
| [`USER_ENTERED_FULL_SCREEN`](#USER_ENTERED_FULL_SCREEN) |
Expand Down Expand Up @@ -153,6 +155,18 @@
#

> ### <a name="USER_SHOWED_CAPTIONS"></a>USER_SHOWED_CAPTIONS
>
> Fires when the user showed the captions by the CC button.
#

> ### <a name="USER_HID_CAPTIONS"></a>USER_HID_CAPTIONS
>
> Fires when the user hid the captions by the CC button.
#

> ### <a name="USER_SELECTED_AUDIO_TRACK"></a>USER_SELECTED_AUDIO_TRACK
>
> Fires when the user selected an audio track from the Audio dropdown.
Expand Down
1 change: 1 addition & 0 deletions flow-typed/types/ui-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ declare type UIOptionsObject = {
targetId: string,
debugActions?: boolean,
forceTouchUI?: boolean,
showCCButton?: boolean,
hoverTimeout?: number,
logger?: loggerType,
components?: ComponentsConfig,
Expand Down
14 changes: 14 additions & 0 deletions src/components/closed-captions/_closed-captions.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.control-button-container.control-closed-captions .control-button {
.icon-closed-captions-on {
display: none;
}

&.cc-on {
.icon-closed-captions-off {
display: none;
}
.icon-closed-captions-on {
display: block;
}
}
}
88 changes: 88 additions & 0 deletions src/components/closed-captions/closed-captions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//@flow
import style from '../../styles/style.scss';
import {h} from 'preact';
import {useEffect, useState} from 'preact/hooks';
import {withText} from 'preact-i18n';
import {default as Icon, IconType} from '../icon';
import {connect} from 'react-redux';
import {withEventDispatcher} from 'components/event-dispatcher';
import {withLogger} from 'components/logger';
import {Tooltip} from 'components/tooltip';
import {Button} from 'components/button';
import {ButtonControl} from 'components/button-control';
/**
* 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
});

const COMPONENT_NAME = 'ClosedCaptions';

/**
* ClosedCaptions component
*
* @class ClosedCaptions
* @example <ClosedCaptions />
* @extends {Component}
*/
const ClosedCaptions = connect(mapStateToProps)(
withLogger(COMPONENT_NAME)(
withEventDispatcher(COMPONENT_NAME)(
withText({
closedCaptionsOnText: 'controls.closedCaptionsOn',
closedCaptionsOffText: 'controls.closedCaptionsOff'
})((props, context) => {
const [ccOn, setCCOn] = useState(false);
const {textTracks} = props;
const {player} = context;
const activeTextTrack = textTracks.find(textTrack => textTrack.active);

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

if (!(props.textTracks?.length && props.showCCButton)) {
return undefined;
}
return (
<ButtonControl name={COMPONENT_NAME}>
{ccOn ? (
<Tooltip label={props.closedCaptionsOnText}>
<Button
tabIndex="0"
aria-label={props.closedCaptionsOnText}
className={[style.controlButton, style.ccOn].join(' ')}
onClick={() => {
props.notifyClick(true);
player.hideTextTrack();
}}>
<Icon type={IconType.ClosedCaptionsOn} />
</Button>
</Tooltip>
) : (
<Tooltip label={props.closedCaptionsOffText}>
<Button
tabIndex="0"
aria-label={props.closedCaptionsOffText}
className={style.controlButton}
onClick={() => {
props.notifyClick(false);
player.showTextTrack();
}}>
<Icon type={IconType.ClosedCaptionsOff} />
</Button>
</Tooltip>
)}
</ButtonControl>
);
})
)
)
);

ClosedCaptions.displayName = COMPONENT_NAME;
export {ClosedCaptions};
1 change: 1 addition & 0 deletions src/components/closed-captions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {ClosedCaptions} from './closed-captions';
18 changes: 18 additions & 0 deletions src/components/event-dispatcher/event-dispatcher-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ function onClickableComponentsHandler(store: any, action: Object, player: Object
case 'PictureInPicture':
onPictureInPictureClicked(store, action, player);
break;

case 'ClosedCaptions':
onClosedCaptionsClicked(store, action, player);
break;
}
}

Expand Down Expand Up @@ -280,6 +284,20 @@ function onOverlayActionClicked(store: any, action: Object, player: Object): voi
}
}

/**
* Handler for CC clicked actions.
* @param {any} store - The redux store.
* @param {Object} action - The action object.
* @param {Object} player - The video player.
* @returns {void}
*/
function onClosedCaptionsClicked(store: any, action: Object, player: Object): void {
const {payload: ccOn} = action;
ccOn
? player.dispatchEvent(new FakeEvent(FakeEvent.Type.USER_HID_CAPTIONS))
: player.dispatchEvent(new FakeEvent(FakeEvent.Type.USER_SHOWED_CAPTIONS));
}

/**
* Keyboard handler object.
* Maps key code to its event dispatching logic.
Expand Down
10 changes: 9 additions & 1 deletion src/components/icon/icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ const IconType = {
Next: 'next',
Prev: 'prev',
PictureInPictureStart: 'picture-in-picture-start',
PictureInPictureStop: 'picture-in-picture-stop'
PictureInPictureStop: 'picture-in-picture-stop',
ClosedCaptionsOn: 'closed-captions-on',
ClosedCaptionsOff: 'closed-captions-off'
};

const BadgeType = {
Expand Down Expand Up @@ -297,6 +299,12 @@ class Icon extends Component {
case IconType.PictureInPictureStop:
return <i className={[style.icon, style.iconPictureInPictureStop].join(' ')} />;

case IconType.ClosedCaptionsOn:
return <i className={[style.icon, style.iconClosedCaptionsOn].join(' ')} />;

case IconType.ClosedCaptionsOff:
return <i className={[style.icon, style.iconClosedCaptionsOff].join(' ')} />;

default:
break;
}
Expand Down

0 comments on commit b298776

Please sign in to comment.