UNPKG

bitmovin-player-ui

Version:
244 lines (216 loc) 8.33 kB
import { SubtitleRegionContainer, SubtitleLabel } from '../components/overlays/SubtitleOverlay'; import { VTTProperties, VTTRegionProperties } from 'bitmovin-player/types/subtitles/vtt/API'; import { DOM, Size } from '../DOM'; // Our default height of a line const lineHeight = 28; // Default relative line height const lineHeightPercent = 5; let lineCount: number = 1; const defaultLineNumber = 21; // Our default amount of lines enum Direction { Top = 'top', Bottom = 'bottom', Left = 'left', Right = 'right', } enum VttVerticalWriting { GrowingRight = 'lr', GrowingLeft = 'rl', } type VerticalWritingDirection = Direction.Left | Direction.Right; const DirectionPair = new Map<Direction, Direction>([ [Direction.Top, Direction.Bottom], [Direction.Bottom, Direction.Top], [Direction.Left, Direction.Right], [Direction.Right, Direction.Left], ]); /** * Sets the default standardized styles for the Cue Box * https://w3.org/TR/webvtt1/#applying-css-properties */ const setDefaultVttStyles = (cueContainerDom: DOM, vtt: VTTProperties) => { if (vtt.region) { cueContainerDom.css('position', 'relative'); cueContainerDom.css('unicode-bidi', 'plaintext'); } else { cueContainerDom.css('position', 'absolute'); cueContainerDom.css('overflow-wrap', 'break-word'); cueContainerDom.css('overflow', 'hidden'); cueContainerDom.css('flex-flow', 'column'); } cueContainerDom.css('display', 'inline-flex'); }; /** * Align the Cue Box's line * https://w3.org/TR/webvtt1/#webvtt-cue-line-alignment */ const setVttLineAlign = ( cueContainerDom: DOM, { lineAlign }: VTTProperties, direction: Direction, relativeCueBoxPosition: number, ) => { switch (lineAlign) { case 'center': setCssForCenterLineAlign(cueContainerDom, direction, relativeCueBoxPosition); break; case 'end': setCssForEndLineAlign(cueContainerDom, direction, relativeCueBoxPosition); } }; /** * Defines the line positioning of the Cue Box * https://w3.org/TR/webvtt1/#webvtt-cue-line */ const setVttLine = (cueContainerDom: DOM, vtt: VTTProperties, direction: Direction, subtitleOverLaySize: Size) => { const overlayReferenceEdge = DirectionPair.get(direction); if (vtt.line === 'auto' && vtt.vertical) { cueContainerDom.css(overlayReferenceEdge, '0'); return; } if (vtt.line === 'auto' && !vtt.vertical) { return; } let relativeLinePosition = parseFloat(vtt.line as string); if (vtt.snapToLines) { let targetLine = Number(vtt.line); if (targetLine < 0) { targetLine = defaultLineNumber + targetLine; } const lineHeight = subtitleOverLaySize.height / defaultLineNumber; const absoluteLinePosition = lineHeight * targetLine; relativeLinePosition = (100 * absoluteLinePosition) / subtitleOverLaySize.height; } if (vtt.lineAlign !== 'end') cueContainerDom.css(overlayReferenceEdge, `${relativeLinePosition}%`); setVttLineAlign(cueContainerDom, vtt, direction, relativeLinePosition); }; /** * Defines the writing direction of the Cue Box * https://w3.org/TR/webvtt1/#webvtt-cue-writing-direction */ const setVttWritingDirectionAndCueBoxPositioning = ( cueContainerDom: DOM, vtt: VTTProperties, subtitleOverlaySize: Size, ) => { switch (vtt.vertical) { case '': cueContainerDom.css('writing-mode', 'horizontal-tb'); cueContainerDom.css(Direction.Bottom, '0'); setVttLine(cueContainerDom, vtt, Direction.Bottom, subtitleOverlaySize); break; case VttVerticalWriting.GrowingRight: setCueBoxPositionForVerticalWriting(cueContainerDom, Direction.Right, vtt, subtitleOverlaySize); break; case VttVerticalWriting.GrowingLeft: setCueBoxPositionForVerticalWriting(cueContainerDom, Direction.Left, vtt, subtitleOverlaySize); break; } }; const setCueBoxPositionForVerticalWriting = ( cueContainerDom: DOM, direction: VerticalWritingDirection, vtt: VTTProperties, subtitleOverlaySize: Size, ) => { const writingMode = direction === Direction.Right ? 'vertical-lr' : 'vertical-rl'; cueContainerDom.css('writing-mode', writingMode); cueContainerDom.css(Direction.Top, '0'); setVttLine(cueContainerDom, vtt, direction, subtitleOverlaySize); }; /** * Defines the Cue position alignment * https://w3.org/TR/webvtt1/#webvtt-cue-position-alignment */ const setVttPositionAlign = (cueContainerDom: DOM, vtt: VTTProperties, direction: Direction) => { // https://www.w3.org/TR/webvtt1/#webvtt-cue-position if (vtt.position === 'auto') { cueContainerDom.css(direction, '0'); } else { switch (vtt.positionAlign) { case 'line-left': cueContainerDom.css(direction, `${vtt.position}%`); cueContainerDom.css(DirectionPair.get(direction), 'auto'); cueContainerDom.css('justify-content', 'flex-start'); break; case 'center': cueContainerDom.css(direction, `${vtt.position - vtt.size / 2}%`); cueContainerDom.css(DirectionPair.get(direction), 'auto'); cueContainerDom.css('justify-content', 'center'); break; case 'line-right': cueContainerDom.css(direction, 'auto'); cueContainerDom.css(DirectionPair.get(direction), `${100 - vtt.position}%`); cueContainerDom.css('justify-content', 'flex-end'); break; default: cueContainerDom.css(direction, `${vtt.position}%`); cueContainerDom.css('justify-content', 'flex-start'); } } }; const countLines = (innerHtml: string) => innerHtml.split('<br />').length; const setCssForCenterLineAlign = (cueContainerDom: DOM, direction: Direction, relativeCueBoxPosition: number) => { switch (direction) { case Direction.Bottom: cueContainerDom.css('transform', 'translateY(-50%)'); break; case Direction.Left: cueContainerDom.css('transform', 'translateX(50%)'); break; case Direction.Right: cueContainerDom.css('transform', 'translateX(-50%)'); break; } }; const setCssForEndLineAlign = (cueContainerDom: DOM, direction: Direction, offset: number) => { const opositeToOverlayReferenceEdge = direction; cueContainerDom.css(opositeToOverlayReferenceEdge, `${100 - offset}%`); }; /** * @category Utils */ export namespace VttUtils { export const setVttCueBoxStyles = (cueContainer: SubtitleLabel, subtitleOverlaySize: Size) => { const vtt = cueContainer.vtt; const cueContainerDom = cueContainer.getDomElement(); setDefaultVttStyles(cueContainerDom, vtt); lineCount = countLines(cueContainer.getText()); setVttWritingDirectionAndCueBoxPositioning(cueContainerDom, vtt, subtitleOverlaySize); // https://w3.org/TR/webvtt1/#webvtt-cue-text-alignment const textAlign = vtt.align === 'middle' ? 'center' : vtt.align; cueContainerDom.css('text-align', textAlign); // https://w3.org/TR/webvtt1/#webvtt-cue-size const containerSize = vtt.size; if (vtt.vertical === '') { cueContainerDom.css('width', `${containerSize}%`); setVttPositionAlign(cueContainerDom, vtt, Direction.Left); } else { cueContainerDom.css('height', `${containerSize}%`); setVttPositionAlign(cueContainerDom, vtt, Direction.Top); } }; /** https://www.w3.org/TR/webvtt1/#regions * https://www.speechpad.com/captions/webvtt#toc_16 */ export const setVttRegionStyles = ( regionContainer: SubtitleRegionContainer, region: VTTRegionProperties, overlaySize: Size, ) => { const regionContainerDom = regionContainer.getDomElement(); const regionPositionX = (overlaySize.width * region.viewportAnchorX) / 100 - (((overlaySize.width * region.width) / 100) * region.regionAnchorX) / 100; const regionPositionY = (overlaySize.height * region.viewportAnchorY) / 100 - (region.lines * lineHeight * region.regionAnchorY) / 100; regionContainerDom.css('position', 'absolute'); regionContainerDom.css('overflow', 'hidden'); regionContainerDom.css('width', `${region.width}%`); regionContainerDom.css(Direction.Left, `${regionPositionX}px`); regionContainerDom.css(Direction.Right, 'unset'); regionContainerDom.css(Direction.Top, `${regionPositionY}px`); regionContainerDom.css(Direction.Bottom, 'unset'); regionContainerDom.css('height', `${region.lines * lineHeight}px`); }; }