UNPKG

playable

Version:

Video player based on HTML5Video

200 lines (158 loc) 5.24 kB
import { UIEvent } from '../../../constants'; import logger from '../../../utils/logger'; import playerAPI from '../../../core/player-api-decorator'; import SubtitlesView from './subtitles.view'; import { ISubtitlesAPI, ISubtitles, ISubtitleConfig } from './types'; import { IEventEmitter } from '../../event-emitter/types'; import { IRootContainer } from '../../root-container/types'; import { IPlaybackEngine } from '../../playback-engine/types'; function isSameOrigin(url: string): boolean { const { protocol, hostname, port } = window.location; const a = document.createElement('a'); a.setAttribute('href', url); return a.protocol === protocol && a.hostname === hostname && a.port === port; } class Subtitles implements ISubtitles { static moduleName = 'subtitle'; static dependencies = ['rootContainer', 'engine', 'eventEmitter']; static View = SubtitlesView; isHidden: boolean; view: SubtitlesView; private _eventEmitter: IEventEmitter; private _video: HTMLVideoElement; private _activeSubtitleIndex: number | null = null; private _trackList: Array<TextTrack> = []; private _unbindEvents: () => void; constructor({ rootContainer, engine, eventEmitter, }: { rootContainer: IRootContainer; engine: IPlaybackEngine; eventEmitter: IEventEmitter; }) { this._eventEmitter = eventEmitter; this._video = engine.getElement(); this._initUI(); this._bindCallbacks(); this._bindEvents(); rootContainer.appendComponentElement(this.getElement()); } @playerAPI() setSubtitles( subtitles: string | ISubtitleConfig | Array<ISubtitleConfig>, ): void { this.removeSubtitles(); if (!subtitles) { return; } const hasCrossOriginAttribute = this._video.hasAttribute('crossorigin'); let hasCrossOriginTrack = false; (Array.isArray(subtitles) ? subtitles : [subtitles]).forEach(subtitle => { subtitle = typeof subtitle === 'string' ? { src: subtitle } : subtitle; hasCrossOriginTrack = hasCrossOriginTrack || !isSameOrigin(subtitle.src); this._addSubtitle(subtitle); }); if (!hasCrossOriginAttribute && hasCrossOriginTrack) { logger.warn( "Subtitle are being loaded from another origin but video crossorigin attribute isn't used. " + 'This may prevent text tracks from loading.', ); } } @playerAPI() setActiveSubtitle(index: number): void { this._clearActiveSubtitle(); this._setActiveSubtitle(index); } @playerAPI() showSubtitles(): void { this.view.show(); } @playerAPI() hideSubtitles(): void { this.view.hide(); } private _addSubtitle(subtitle: ISubtitleConfig): void { const track = document.createElement('track'); track.setAttribute('src', subtitle.src); track.setAttribute('kind', 'subtitles'); if (subtitle.lang) { track.setAttribute('srclang', subtitle.lang); } if (subtitle.label) { track.setAttribute('label', subtitle.label); } this._video.appendChild(track); this._trackList.push( this._video.textTracks[this._video.textTracks.length - 1], ); } @playerAPI() removeSubtitles(): void { this._clearActiveSubtitle(); const subtitleTracks: NodeListOf<HTMLTrackElement> = this._video.querySelectorAll( 'track[kind="subtitles"]', ); Array.prototype.forEach.call( subtitleTracks, (trackElement: HTMLTrackElement) => this._video.removeChild(trackElement), ); this._trackList = []; } private _clearActiveSubtitle(): void { if (this._activeSubtitleIndex) { const textTrack: TextTrack = this._trackList[this._activeSubtitleIndex]; textTrack.removeEventListener('cuechange', this._showSubtitles); textTrack.mode = 'disabled'; this._activeSubtitleIndex = null; } this.view.clearSubtitles(); } private _setActiveSubtitle(index: number): void { const textTrack: TextTrack = this._trackList[index]; if (textTrack) { textTrack.mode = 'hidden'; textTrack.addEventListener('cuechange', this._showSubtitles); textTrack.activeCues && this.view.showSubtitles( Array.prototype.map.call( textTrack.activeCues, (cue: VTTCue) => cue.text, ), ); } this._activeSubtitleIndex = index; } getElement(): HTMLElement { return this.view.getElement(); } private _initUI(): void { this.view = new Subtitles.View(); } private _bindCallbacks(): void { this._showSubtitles = this._showSubtitles.bind(this); } private _bindEvents(): void { this._unbindEvents = this._eventEmitter.bindEvents( [ [UIEvent.MAIN_BLOCK_SHOW, this.view.moveSubtitlesUp, this.view], [UIEvent.MAIN_BLOCK_HIDE, this.view.moveSubtitlesDown, this.view], ], this, ); } private _showSubtitles(event: TrackEvent): void { const textTrack: TextTrack = event.target as TextTrack; this.view.showSubtitles( Array.prototype.map.call(textTrack.activeCues, (cue: VTTCue) => cue.text), ); } destroy(): void { this._unbindEvents(); this.view.destroy(); } } export { ISubtitlesAPI }; export default Subtitles;