export interface IVttItem { start: number; end: number; coords: { x: number; y: number; w: number; h: number }; } /** Загружает и парсит VTT-файл с тайм-кодами и координатами спрайтов превью видео. */ export class VttHelper { private readonly vttUrl: string; public hasInited: boolean; private vttData: Array | null; constructor(vttUrl: string) { this.vttUrl = vttUrl; this.hasInited = false; this.vttData = null; } /** Загружает и парсит VTT-файл по url из конструктора. Устанавливает hasInited = true после успешной загрузки. */ async init(): Promise { if (!this.vttUrl) { return; } try { const vttContent: Response = await fetch(this.vttUrl); if (vttContent.body) { const reader: ReadableStreamDefaultReader = vttContent.body .pipeThrough(new TextDecoderStream()) .getReader(); const vtt: ReadableStreamReadResult = await reader.read(); this.vttData = this.parseVTT(vtt); this.hasInited = true; } } catch { this.hasInited = false; } } private parseCoords(coordsString: string): { x: number; y: number; w: number; h: number } { const [x, y, w, h] = coordsString.split(',').map(Number); return { x, y, w, h }; } private parseVTT(vttContent: ReadableStreamReadResult): Array { if (!vttContent.value) { return []; } const lines: Array = vttContent.value.split('\n'); const result: Array = []; let item: Partial = {}; for (let i = 0; i < lines.length; i++) { const line: string = lines[i].trim(); if (line.includes('-->')) { const parts: Array = line.split(' '); item.start = this.timeToSeconds(parts[0]); item.end = this.timeToSeconds(parts[2]); } else if (line.includes('#xywh=')) { item.coords = this.parseCoords(line.split('#xywh=')[1]); } else if (line.includes('Img')) { if (Object.values(item).length && item.start !== undefined && item.end !== undefined && item.coords) { result.push(item as IVttItem); } item = {}; } } return result; } private timeToSeconds(time: string): number { const parts: Array = time.split(':'); const hh: number = parseInt(parts[0], 10); const mm: number = parseInt(parts[1], 10); const ss: number = parseFloat(parts[2].replace(',', '.')); return hh * 3600 + mm * 60 + ss; } /** * Возвращает координаты спрайта превью для указанного времени воспроизведения. * @param time - время в секундах * @param vttData - данные VTT (по умолчанию загруженные через init) */ getCoordsByTime(time: number, vttData: Array | null = this.vttData): { x: number; y: number; w: number; h: number } | string { if (!this.hasInited || !vttData) { return 'No preview'; } return ( vttData.find((item: IVttItem) => { return time >= item.start && time <= item.end; }) || vttData[0] ).coords; } }