UNPKG

bitmovin-player-ui

Version:
159 lines (135 loc) 5.48 kB
import { NavigationGroup } from './NavigationGroup'; import { RootNavigationGroup } from './RootNavigationGroup'; import { NodeEventSubscriber } from './NodeEventSubscriber'; import { SeekBarHandler } from './SeekBarHandler'; import { getKeyMapForPlatform } from './getKeyMapForPlatform'; import { Action, Direction, KeyMap } from './types'; import { isAction, isDirection } from './TypeGuards'; /** * SpatialNavigation keeps track of all navigation groups, and updates the active navigation group when visibility * changes on group container. * * It listens to key events, and triggers either handleNavigation or handleAction on the active group. * SeekBarHandler will get instantiated with root navigation group and disposed on release of the spatial navigation. */ export class SpatialNavigation { private unsubscribeVisibilityChangesFns: (() => void)[]; private readonly navigationGroups: NavigationGroup[] = []; private readonly activeNavigationGroups: NavigationGroup[]; private readonly eventSubscriber: NodeEventSubscriber; private readonly seekBarHandler: SeekBarHandler; private readonly keyMap: KeyMap; constructor(rootNavigationGroup: RootNavigationGroup, ...navigationGroups: NavigationGroup[]) { this.seekBarHandler = new SeekBarHandler(rootNavigationGroup); this.activeNavigationGroups = []; this.unsubscribeVisibilityChangesFns = []; this.eventSubscriber = new NodeEventSubscriber(); this.navigationGroups = [rootNavigationGroup, ...navigationGroups]; this.keyMap = getKeyMapForPlatform(); this.subscribeToNavigationGroupVisibilityChanges(); this.attachKeyEventHandler(); this.enableDefaultNavigationGroup(); } private attachKeyEventHandler(): void { this.eventSubscriber.on(document, 'keydown', this.handleKeyEvent, true); } private onShow = (group: NavigationGroup): void => { this.activeNavigationGroups.push(group); this.updateEnabledNavigationGroup(); }; private onHide = (group: NavigationGroup): void => { const groupIndex = this.activeNavigationGroups.findIndex(other => other === group); if (groupIndex > -1) { group.disable(); this.activeNavigationGroups.splice(groupIndex, 1); this.updateEnabledNavigationGroup(); } }; /** * Subscribes to onHide and onShow on all navigation groups containers as Spatial navigation tracks active navigation * group based on their container visibility. */ private subscribeToNavigationGroupVisibilityChanges(): void { this.navigationGroups.forEach(group => { const onShowHandler = () => this.onShow(group); const onHideHandler = () => this.onHide(group); group.container.onShow.subscribe(onShowHandler); group.container.onHide.subscribe(onHideHandler); this.unsubscribeVisibilityChangesFns.push( () => group.container.onShow.unsubscribe(onShowHandler), () => group.container.onHide.unsubscribe(onHideHandler), ); }); } private unsubscribeFromNavigationGroupVisibilityChanges(): void { this.unsubscribeVisibilityChangesFns.forEach(unsub => unsub()); this.unsubscribeVisibilityChangesFns = []; } /** * It will enable group of which container is currently shown * If there are no groups with containers that are currently visible, it will enable root navigation group */ private enableDefaultNavigationGroup(): void { const isShown = (group: NavigationGroup) => group.container.isShown(); const groupToEnable = this.navigationGroups.find(isShown) ?? this.navigationGroups[0]; if (groupToEnable) { this.activeNavigationGroups.push(groupToEnable); this.updateEnabledNavigationGroup(); } } /** * Disables navigation groups that are no longer active and calls enable on last pushed navigation group */ private updateEnabledNavigationGroup(): void { this.activeNavigationGroups.forEach((group, idx) => { if (idx < this.activeNavigationGroups.length - 1) { group.disable(); } else { group.enable(); } }); } /** * Returns currently active navigation group */ public getActiveNavigationGroup(): NavigationGroup | undefined { return this.activeNavigationGroups[this.activeNavigationGroups.length - 1]; } /** * Checks if keyboard event keycode is tracked either as Direction or Action and calls appropriate handler on active * navigation group * * @param e {KeyboardEvent} */ private handleKeyEvent = (e: KeyboardEvent): void => { const event: Direction | Action | undefined = this.keyMap[getKeyCode(e)]; const active = this.getActiveNavigationGroup(); if (!active || !active.container || active.container.isHidden() || active.container.isDisabled()) { return; } if (isDirection(event)) { active.handleNavigation(event); e.preventDefault(); e.stopPropagation(); } if (isAction(event)) { active.handleAction(event); e.preventDefault(); e.stopPropagation(); } }; /** * Dispose of SpatialNavigation * * Remove all event handlers, release seekbar handler and release all navigation groups. */ public release(): void { this.unsubscribeFromNavigationGroupVisibilityChanges(); this.eventSubscriber.release(); this.navigationGroups.forEach(group => group.release()); this.seekBarHandler.release(); } } function getKeyCode(event: KeyboardEvent): number { return event.keyCode; }