vendor: 4,722 bytes, lines 1-183
1const ParseErrorCode = { 2 LoadFail: 0, 3 BadSignature: 1, 4 BadTimestamp: 2, 5 BadSettingValue: 3, 6 BadFormat: 4, 7 UnknownSetting: 5 8}; 9class ParseError extends Error { 10 code; 11 line; 12 constructor(init) { 13 super(init.reason); 14 this.code = init.code; 15 this.line = init.line; 16 } 17} 18 19const LINE_TERMINATOR_RE = /\r?\n|\r/gm; 20class TextLineTransformStream { 21 writable; 22 readable; 23 constructor(encoding) { 24 const transformer = new TextStreamLineIterator(encoding); 25 this.writable = new WritableStream({ 26 write(chunk) { 27 transformer.transform(chunk); 28 }, 29 close() { 30 transformer.close(); 31 } 32 }); 33 this.readable = new ReadableStream({ 34 start(controller) { 35 transformer.onLine = (line) => controller.enqueue(line); 36 transformer.onClose = () => controller.close(); 37 } 38 }); 39 } 40} 41class TextStreamLineIterator { 42 x = ""; 43 y; 44 onLine; 45 onClose; 46 constructor(encoding) { 47 this.y = new TextDecoder(encoding); 48 } 49 transform(chunk) { 50 this.x += this.y.decode(chunk, { stream: true }); 51 const lines = this.x.split(LINE_TERMINATOR_RE); 52 this.x = lines.pop() || ""; 53 for (let i = 0; i < lines.length; i++) 54 this.onLine(lines[i].trim()); 55 } 56 close() { 57 if (this.x) 58 this.onLine(this.x.trim()); 59 this.x = ""; 60 this.onClose(); 61 } 62} 63 64async function parseText(text, options) { 65 const stream = new ReadableStream({ 66 start(controller) { 67 const lines = text.split(LINE_TERMINATOR_RE); 68 for (const line of lines) 69 controller.enqueue(line); 70 controller.close(); 71 } 72 }); 73 return parseTextStream(stream, options); 74} 75async function parseTextStream(stream, options) { 76 const type = options?.type ?? "vtt"; 77 let factory; 78 if (typeof type === "string") { 79 switch (type) { 80 case "srt": 81 factory = (await import('./srt-parser.js')).default; 82 break; 83 case "ssa": 84 case "ass": 85 factory = (await import('./ssa-parser.js')).default; 86 break; 87 default: 88 factory = (await Promise.resolve().then(function () { return vttParser; })).default; 89 } 90 } else { 91 factory = type; 92 } 93 let result; 94 const reader = stream.getReader(), parser = factory(), errors = !!options?.strict || !!options?.errors; 95 await parser.init({ 96 strict: false, 97 ...options, 98 errors, 99 type, 100 cancel() { 101 reader.cancel(); 102 result = parser.done(true); 103 } 104 }); 105 let i = 1; 106 while (true) { 107 const { value, done } = await reader.read(); 108 if (done) { 109 parser.parse("", i); 110 result = parser.done(false); 111 break; 112 } 113 parser.parse(value, i); 114 i++; 115 } 116 return result; 117} 118 119async function parseResponse(response, options) { 120 const res = await response; 121 if (!res.ok || !res.body) { 122 let error; 123 return { 124 metadata: {}, 125 cues: [], 126 regions: [], 127 errors: [error] 128 }; 129 } 130 const contentType = res.headers.get("content-type") || "", type = contentType.match(/text\/(.*?)(?:;|$)/)?.[1], encoding = contentType.match(/charset=(.*?)(?:;|$)/)?.[1]; 131 return parseByteStream(res.body, { type, encoding, ...options }); 132} 133async function parseByteStream(stream, { encoding = "utf-8", ...options } = {}) { 134 const textStream = stream.pipeThrough(new TextLineTransformStream(encoding)); 135 return parseTextStream(textStream, options); 136} 137 138class TextCue extends EventTarget { 139 /** 140 * A string that identifies the cue. 141 * 142 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/TextTrackCue/id} 143 */ 144 id = ""; 145 /** 146 * A `double` that represents the video time that the cue will start being displayed, in seconds. 147 * 148 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/TextTrackCue/startTime} 149 */ 150 startTime; 151 /** 152 * A `double` that represents the video time that the cue will stop being displayed, in seconds. 153 * 154 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/TextTrackCue/endTime} 155 */ 156 endTime; 157 /** 158 * Returns a string with the contents of the cue. 159 * 160 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/text} 161 */ 162 text; 163 /** 164 * A `boolean` for whether the video will pause when this cue stops being displayed. 165 * 166 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/TextTrackCue/pauseOnExit} 167 */ 168 pauseOnExit = false; 169 constructor(startTime, endTime, text) { 170 super(); 171 this.startTime = startTime; 172 this.endTime = endTime; 173 this.text = text; 174 } 175 addEventListener(type, listener, options) { 176 super.addEventListener(type, listener, options); 177 } 178 removeEventListener(type, listener, options) { 179 super.removeEventListener(type, listener, options); 180 } 181} 182 183const
183IS_SERVER = typeof document === "undefined"; 184 185const CueBase = IS_SERVER ? TextCue :
vendor: 17,285 bytes, lines 185-782
185 window.VTTCue; 186class VTTCue extends CueBase { 187 /** 188 * A `VTTRegion` object describing the video's sub-region that the cue will be drawn onto, 189 * or `null` if none is assigned. 190 * 191 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/region} 192 */ 193 region = null; 194 /** 195 * The cue writing direction. 196 * 197 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/vertical} 198 */ 199 vertical = ""; 200 /** 201 * Returns `true` if the `VTTCue.line` attribute is an integer number of lines or a percentage 202 * of the video size. 203 * 204 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/snapToLines} 205 */ 206 snapToLines = true; 207 /** 208 * Returns the line positioning of the cue. This can be the string `'auto'` or a number whose 209 * interpretation depends on the value of `VTTCue.snapToLines`. 210 * 211 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/line} 212 */ 213 line = "auto"; 214 /** 215 * Returns an enum representing the alignment of the `VTTCue.line`. 216 * 217 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/lineAlign} 218 */ 219 lineAlign = "start"; 220 /** 221 * Returns the indentation of the cue within the line. This can be the string `'auto'` or a 222 * number representing the percentage of the `VTTCue.region`, or the video size if `VTTCue`.region` 223 * is `null`. 224 * 225 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/position} 226 */ 227 position = "auto"; 228 /** 229 * Returns an enum representing the alignment of the cue. This is used to determine what 230 * the `VTTCue.position` is anchored to. The default is `'auto'`. 231 * 232 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/positionAlign} 233 */ 234 positionAlign = "auto"; 235 /** 236 * Returns a double representing the size of the cue, as a percentage of the video size. 237 * 238 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/size} 239 */ 240 size = 100; 241 /** 242 * Returns an enum representing the alignment of all the lines of text within the cue box. 243 * 244 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/align} 245 */ 246 align = "center"; 247 /** 248 * Additional styles associated with the cue. 249 */ 250 style; 251} 252 253class VTTRegion { 254 /** 255 * A string that identifies the region. 256 */ 257 id = ""; 258 /** 259 * A `double` representing the width of the region, as a percentage of the video. 260 */ 261 width = 100; 262 /** 263 * A `double` representing the height of the region, in number of lines. 264 */ 265 lines = 3; 266 /** 267 * A `double` representing the region anchor X offset, as a percentage of the region. 268 */ 269 regionAnchorX = 0; 270 /** 271 * A `double` representing the region anchor Y offset, as a percentage of the region. 272 */ 273 regionAnchorY = 100; 274 /** 275 * A `double` representing the viewport anchor X offset, as a percentage of the video. 276 */ 277 viewportAnchorX = 0; 278 /** 279 * A `double` representing the viewport anchor Y offset, as a percentage of the video. 280 */ 281 viewportAnchorY = 100; 282 /** 283 * An enum representing how adding new cues will move existing cues. 284 */ 285 scroll = ""; 286} 287 288const COMMA$1 = ",", PERCENT_SIGN$1 = "%"; 289function toNumber(text) { 290 const num = parseInt(text, 10); 291 return !Number.isNaN(num) ? num : null; 292} 293function toPercentage(text) { 294 const num = parseInt(text.replace(PERCENT_SIGN$1, ""), 10); 295 return !Number.isNaN(num) && num >= 0 && num <= 100 ? num : null; 296} 297function toCoords(text) { 298 if (!text.includes(COMMA$1)) 299 return null; 300 const [x, y] = text.split(COMMA$1).map(toPercentage); 301 return x !== null && y !== null ? [x, y] : null; 302} 303function toFloat(text) { 304 const num = parseFloat(text); 305 return !Number.isNaN(num) ? num : null; 306} 307 308const HEADER_MAGIC = "WEBVTT", COMMA = ",", PERCENT_SIGN = "%", SETTING_SEP_RE = /[:=]/, SETTING_LINE_RE = /^[\s\t]*(region|vertical|line|position|size|align)[:=]/, NOTE_BLOCK_START = "NOTE", REGION_BLOCK_START = "REGION", REGION_BLOCK_START_RE = /^REGION:?[\s\t]+/, SPACE_RE = /[\s\t]+/, TIMESTAMP_SEP = "-->", TIMESTAMP_SEP_RE = /[\s\t]*-->[\s\t]+/, ALIGN_RE = /start|center|end|left|right/, LINE_ALIGN_RE = /start|center|end/, POS_ALIGN_RE = /line-(?:left|right)|center|auto/, TIMESTAMP_RE = /^(?:(\d{1,2}):)?(\d{2}):(\d{2})(?:\.(\d{1,3}))?$/; 309var VTTBlock = /* @__PURE__ */ ((VTTBlock2) => { 310 VTTBlock2[VTTBlock2["None"] = 0] = "None"; 311 VTTBlock2[VTTBlock2["Header"] = 1] = "Header"; 312 VTTBlock2[VTTBlock2["Cue"] = 2] = "Cue"; 313 VTTBlock2[VTTBlock2["Region"] = 3] = "Region"; 314 VTTBlock2[VTTBlock2["Note"] = 4] = "Note"; 315 return VTTBlock2; 316})(VTTBlock || {}); 317class VTTParser { 318 f; 319 c = 0; 320 g = {}; 321 h = {}; 322 j = []; 323 a = null; 324 b = null; 325 k = []; 326 d; 327 l = ""; 328 async init(init) { 329 this.f = init; 330 if (init.strict) 331 this.c = 1; 332 if (init.errors) 333 this.d = (await import('./errors.js')).ParseErrorBuilder; 334 } 335 parse(line, lineCount) { 336 if (line === "") { 337 if (this.a) { 338 this.j.push(this.a); 339 this.f.onCue?.(this.a); 340 this.a = null; 341 } else if (this.b) { 342 this.h[this.b.id] = this.b; 343 this.f.onRegion?.(this.b); 344 this.b = null; 345 } else if (this.c === 1) { 346 this.i(line, lineCount); 347 this.f.onHeaderMetadata?.(this.g); 348 } 349 this.c = 0; 350 } else if (this.c) { 351 switch (this.c) { 352 case 1: 353 this.i(line, lineCount); 354 break; 355 case 2: 356 if (this.a) { 357 const hasText = this.a.text.length > 0; 358 if (!hasText && SETTING_LINE_RE.test(line)) { 359 this.m(line.split(SPACE_RE), lineCount); 360 } else { 361 this.a.text += (hasText ? "\n" : "") + line; 362 } 363 } 364 break; 365 case 3: 366 this.n(line.split(SPACE_RE), lineCount); 367 break; 368 } 369 } else if (line.startsWith(NOTE_BLOCK_START)) { 370 this.c = 4; 371 } else if (line.startsWith(REGION_BLOCK_START)) { 372 this.c = 3; 373 this.b = new VTTRegion(); 374 this.n(line.replace(REGION_BLOCK_START_RE, "").split(SPACE_RE), lineCount); 375 } else if (line.includes(TIMESTAMP_SEP)) { 376 const result = this.o(line, lineCount); 377 if (result) { 378 this.a = new VTTCue(result[0], result[1], ""); 379 this.a.id = this.l; 380 this.m(result[2], lineCount); 381 } 382 this.c = 2; 383 } else if (lineCount === 1) { 384 this.i(line, lineCount); 385 } 386 this.l = line; 387 } 388 done() { 389 return { 390 metadata: this.g, 391 cues: this.j, 392 regions: Object.values(this.h), 393 errors: this.k 394 }; 395 } 396 i(line, lineCount) { 397 if (lineCount > 1) { 398 if (SETTING_SEP_RE.test(line)) { 399 const [key, value] = line.split(SETTING_SEP_RE); 400 if (key) 401 this.g[key] = (value || "").replace(SPACE_RE, ""); 402 } 403 } else if (line.startsWith(HEADER_MAGIC)) { 404 this.c = 1; 405 } else { 406 this.e(this.d?.p()); 407 } 408 } 409 o(line, lineCount) { 410 const [startTimeText, trailingText = ""] = line.split(TIMESTAMP_SEP_RE), [endTimeText, ...settingsText] = trailingText.split(SPACE_RE), startTime = parseVTTTimestamp(startTimeText), endTime = parseVTTTimestamp(endTimeText); 411 if (startTime !== null && endTime !== null && endTime > startTime) { 412 return [startTime, endTime, settingsText]; 413 } else { 414 if (startTime === null) { 415 this.e(this.d?.q(startTimeText, lineCount)); 416 } 417 if (endTime === null) { 418 this.e(this.d?.r(endTimeText, lineCount)); 419 } 420 if (startTime != null && endTime !== null && endTime > startTime) { 421 this.e(this.d?.s(startTime, endTime, lineCount)); 422 } 423 } 424 } 425 /** 426 * @see {@link https://www.w3.org/TR/webvtt1/#region-settings-parsing} 427 */ 428 n(settings, line) { 429 let badValue; 430 for (let i = 0; i < settings.length; i++) { 431 if (SETTING_SEP_RE.test(settings[i])) { 432 badValue = false; 433 const [name, value] = settings[i].split(SETTING_SEP_RE); 434 switch (name) { 435 case "id": 436 this.b.id = value; 437 break; 438 case "width": 439 const width = toPercentage(value); 440 if (width !== null) 441 this.b.width = width; 442 else 443 badValue = true; 444 break; 445 case "lines": 446 const lines = toNumber(value); 447 if (lines !== null) 448 this.b.lines = lines; 449 else 450 badValue = true; 451 break; 452 case "regionanchor": 453 const region = toCoords(value); 454 if (region !== null) { 455 this.b.regionAnchorX = region[0]; 456 this.b.regionAnchorY = region[1]; 457 } else 458 badValue = true; 459 break; 460 case "viewportanchor": 461 const viewport = toCoords(value); 462 if (viewport !== null) { 463 this.b.viewportAnchorX = viewport[0]; 464 this.b.viewportAnchorY = viewport[1]; 465 } else 466 badValue = true; 467 break; 468 case "scroll": 469 if (value === "up") 470 this.b.scroll = "up"; 471 else 472 badValue = true; 473 break; 474 default: 475 this.e(this.d?.t(name, value, line)); 476 } 477 if (badValue) { 478 this.e(this.d?.u(name, value, line)); 479 } 480 } 481 } 482 } 483 /** 484 * @see {@link https://www.w3.org/TR/webvtt1/#cue-timings-and-settings-parsing} 485 */ 486 m(settings, line) { 487 let badValue; 488 for (let i = 0; i < settings.length; i++) { 489 badValue = false; 490 if (SETTING_SEP_RE.test(settings[i])) { 491 const [name, value] = settings[i].split(SETTING_SEP_RE); 492 switch (name) { 493 case "region": 494 const region = this.h[value]; 495 if (region) 496 this.a.region = region; 497 break; 498 case "vertical": 499 if (value === "lr" || value === "rl") { 500 this.a.vertical = value; 501 this.a.region = null; 502 } else 503 badValue = true; 504 break; 505 case "line": 506 const [linePos, lineAlign] = value.split(COMMA); 507 if (linePos.includes(PERCENT_SIGN)) { 508 const percentage = toPercentage(linePos); 509 if (percentage !== null) { 510 this.a.line = percentage; 511 this.a.snapToLines = false; 512 } else 513 badValue = true; 514 } else { 515 const number = toFloat(linePos); 516 if (number !== null) 517 this.a.line = number; 518 else 519 badValue = true; 520 } 521 if (LINE_ALIGN_RE.test(lineAlign)) { 522 this.a.lineAlign = lineAlign; 523 } else if (lineAlign) { 524 badValue = true; 525 } 526 if (this.a.line !== "auto") 527 this.a.region = null; 528 break; 529 case "position": 530 const [colPos, colAlign] = value.split(COMMA), position = toPercentage(colPos); 531 if (position !== null) 532 this.a.position = position; 533 else 534 badValue = true; 535 if (colAlign && POS_ALIGN_RE.test(colAlign)) { 536 this.a.positionAlign = colAlign; 537 } else if (colAlign) { 538 badValue = true; 539 } 540 break; 541 case "size": 542 const size = toPercentage(value); 543 if (size !== null) { 544 this.a.size = size; 545 if (size < 100) 546 this.a.region = null; 547 } else { 548 badValue = true; 549 } 550 break; 551 case "align": 552 if (ALIGN_RE.test(value)) { 553 this.a.align = value; 554 } else { 555 badValue = true; 556 } 557 break; 558 default: 559 this.e(this.d?.v(name, value, line)); 560 } 561 if (badValue) { 562 this.e(this.d?.w(name, value, line)); 563 } 564 } 565 } 566 } 567 e(error) { 568 if (!error) 569 return; 570 this.k.push(error); 571 if (this.f.strict) { 572 this.f.cancel(); 573 throw error; 574 } else { 575 this.f.onError?.(error); 576 } 577 } 578} 579function parseVTTTimestamp(timestamp) { 580 const match = timestamp.match(TIMESTAMP_RE); 581 if (!match) 582 return null; 583 const hours = match[1] ? parseInt(match[1], 10) : 0, minutes = parseInt(match[2], 10), seconds = parseInt(match[3], 10), milliseconds = match[4] ? parseInt(match[4].padEnd(3, "0"), 10) : 0, total = hours * 3600 + minutes * 60 + seconds + milliseconds / 1e3; 584 if (hours < 0 || minutes < 0 || seconds < 0 || milliseconds < 0 || minutes > 59 || seconds > 59) { 585 return null; 586 } 587 return total; 588} 589function createVTTParser() { 590 return new VTTParser(); 591} 592 593var vttParser = /*#__PURE__*/Object.freeze({ 594 __proto__: null, 595 VTTBlock: VTTBlock, 596 VTTParser: VTTParser, 597 default: createVTTParser, 598 parseVTTTimestamp: parseVTTTimestamp 599}); 600 601const DIGIT_RE = /[0-9]/, MULTI_SPACE_RE = /[\s\t]+/, TAG_NAME = { 602 c: "span", 603 i: "i", 604 b: "b", 605 u: "u", 606 ruby: "ruby", 607 rt: "rt", 608 v: "span", 609 lang: "span", 610 timestamp: "span" 611}, HTML_ENTITIES = { 612 "&": "&", 613 "<": "<", 614 ">": ">", 615 """: '"', 616 "'": "'", 617 " ": "\xA0", 618 "‎": "\u200E", 619 "‏": "\u200F" 620}, HTML_ENTITY_RE = /&(?:amp|lt|gt|quot|#(0+)?39|nbsp|lrm|rlm);/g, COLORS = /* @__PURE__ */ new Set([ 621 "white", 622 "lime", 623 "cyan", 624 "red", 625 "yellow", 626 "magenta", 627 "blue", 628 "black" 629]), BLOCK_TYPES = /* @__PURE__ */ new Set(Object.keys(TAG_NAME)); 630function tokenizeVTTCue(cue) { 631 let buffer = "", mode = 1, result = [], stack = [], node; 632 for (let i = 0; i < cue.text.length; i++) { 633 const char = cue.text[i]; 634 switch (mode) { 635 case 1: 636 if (char === "<") { 637 addText(); 638 mode = 2; 639 } else { 640 buffer += char; 641 } 642 break; 643 case 2: 644 switch (char) { 645 case "\n": 646 case " ": 647 case " ": 648 addNode(); 649 mode = 4; 650 break; 651 case ".": 652 addNode(); 653 mode = 3; 654 break; 655 case "/": 656 mode = 5; 657 break; 658 case ">": 659 addNode(); 660 mode = 1; 661 break; 662 default: 663 if (!buffer && DIGIT_RE.test(char)) 664 mode = 6; 665 buffer += char; 666 break; 667 } 668 break; 669 case 3: 670 switch (char) { 671 case " ": 672 case " ": 673 case "\n": 674 addClass(); 675 if (node) 676 node.class?.trim(); 677 mode = 4; 678 break; 679 case ".": 680 addClass(); 681 break; 682 case ">": 683 addClass(); 684 if (node) 685 node.class?.trim(); 686 mode = 1; 687 break; 688 default: 689 buffer += char; 690 } 691 break; 692 case 4: 693 if (char === ">") { 694 buffer = buffer.replace(MULTI_SPACE_RE, " "); 695 if (node?.type === "v") 696 node.voice = replaceHTMLEntities(buffer); 697 else if (node?.type === "lang") 698 node.lang = replaceHTMLEntities(buffer); 699 buffer = ""; 700 mode = 1; 701 } else { 702 buffer += char; 703 } 704 break; 705 case 5: 706 if (char === ">") { 707 buffer = ""; 708 node = stack.pop(); 709 mode = 1; 710 } 711 break; 712 case 6: 713 if (char === ">") { 714 const time = parseVTTTimestamp(buffer); 715 if (time !== null && time >= cue.startTime && time <= cue.endTime) { 716 buffer = "timestamp"; 717 addNode(); 718 node.time = time; 719 } 720 buffer = ""; 721 mode = 1; 722 } else { 723 buffer += char; 724 } 725 break; 726 } 727 } 728 function addNode() { 729 if (BLOCK_TYPES.has(buffer)) { 730 const parent = node; 731 node = createBlockNode(buffer); 732 if (parent) { 733 if (stack[stack.length - 1] !== parent) 734 stack.push(parent); 735 parent.children.push(node); 736 } else 737 result.push(node); 738 } 739 buffer = ""; 740 mode = 1; 741 } 742 function addClass() { 743 if (node && buffer) { 744 const color = buffer.replace("bg_", ""); 745 if (COLORS.has(color)) { 746 node[buffer.startsWith("bg_") ? "bgColor" : "color"] = color; 747 } else { 748 node.class = !node.class ? buffer : node.class + " " + buffer; 749 } 750 } 751 buffer = ""; 752 } 753 function addText() { 754 if (!buffer) 755 return; 756 const text = { type: "text", data: replaceHTMLEntities(buffer) }; 757 node ? node.children.push(text) : result.push(text); 758 buffer = ""; 759 } 760 if (mode === 1) 761 addText(); 762 return result; 763} 764function createBlockNode(type) { 765 return { 766 tagName: TAG_NAME[type], 767 type, 768 children: [] 769 }; 770} 771function replaceHTMLEntities(text) { 772 return text.replace(HTML_ENTITY_RE, (entity) => HTML_ENTITIES[entity] || "'"); 773} 774 775function setCSSVar(el, name, value) { 776 el.style.setProperty(`--${name}`, value + ""); 777} 778function setDataAttr(el, name, value = true) { 779 el.setAttribute(`data-${name}`, value === true ? "" : value + ""); 780} 781function setPartAttr(el, name) { 782 el.setAttribute("
782data-part", name); 783} 784function getLineHeight(el) { 785 return parseFloat(getComputedStyle(el).lineHeight) || 0; 786} 787 788function createVTTCueTemplate(cue) { 789 if (IS_SERVER) { 790 throw Error( 791 "[media-captions] called `createVTTCueTemplate` on the server - use `renderVTTCueString`" 792 ); 793 } 794 const template = document.createElement("template"); 795 template.innerHTML = renderVTTCueString(cue); 796 return { cue, content: template.content }; 797} 798function renderVTTCueString(cue, currentTime = 0) { 799 return renderVTTTokensString(tokenizeVTTCue(cue), currentTime); 800} 801function renderVTTTokensString(tokens, currentTime = 0) { 802 let attrs, result = ""; 803 for (const token of tokens) { 804 if (token.type === "text") { 805 result += token.data; 806 } else { 807 const isTimestamp = token.type === "timestamp"; 808 attrs = {}; 809 attrs.class = token.class; 810 attrs.title = token.type === "v" && token.voice; 811 attrs.lang = token.type === "lang" && token.lang; 812 attrs["data-part"] = token.type === "v" && "voice"; 813 if (isTimestamp) { 814 attrs["data-part"] = "timed"; 815 attrs["data-time"] = token.time; 816 attrs["data-future"] = token.time > currentTime; 817 attrs["data-past"] = token.time < currentTime; 818 } 819 attrs.style = `${token.color ? `color: ${token.color};` : ""}${token.bgColor ? `background-color: ${token.bgColor};` : ""}`; 820 const attributes = Object.entries(attrs).filter((v) => v[1]).map((v) => `${v[0]}="${v[1] === true ? "" : v[1]}"`).join(" "); 821 result += `<${token.tagName}${attributes ? " " + attributes : ""}>${renderVTTTokensString( 822 token.children 823 )}</${token.tagName}>`; 824 } 825 } 826 return result; 827} 828function updateTimedVTTCueNodes(root, currentTime) { 829 if (IS_SERVER) 830 return; 831 for (const el of root.querySelectorAll('[data-
vendor: 7,564 bytes, lines 831-1090
831part="timed"]')) { 832 const time = Number(el.getAttribute("data-time")); 833 if (Number.isNaN(time)) 834 continue; 835 if (time > currentTime) 836 setDataAttr(el, "future"); 837 else 838 el.removeAttribute("data-future"); 839 if (time < currentTime) 840 setDataAttr(el, "past"); 841 else 842 el.removeAttribute("data-past"); 843 } 844} 845 846function debounce(fn, delay) { 847 let timeout = null, args; 848 function run() { 849 clear(); 850 fn(...args); 851 args = void 0; 852 } 853 function clear() { 854 clearTimeout(timeout); 855 timeout = null; 856 } 857 function debounce2() { 858 args = [].slice.call(arguments); 859 clear(); 860 timeout = setTimeout(run, delay); 861 } 862 return debounce2; 863} 864 865const STARTING_BOX = Symbol(0); 866function createBox(box) { 867 if (box instanceof HTMLElement) { 868 return { 869 top: box.offsetTop, 870 width: box.clientWidth, 871 height: box.clientHeight, 872 left: box.offsetLeft, 873 right: box.offsetLeft + box.clientWidth, 874 bottom: box.offsetTop + box.clientHeight 875 }; 876 } 877 return { ...box }; 878} 879function moveBox(box, axis, delta) { 880 switch (axis) { 881 case "+x": 882 box.left += delta; 883 box.right += delta; 884 break; 885 case "-x": 886 box.left -= delta; 887 box.right -= delta; 888 break; 889 case "+y": 890 box.top += delta; 891 box.bottom += delta; 892 break; 893 case "-y": 894 box.top -= delta; 895 box.bottom -= delta; 896 break; 897 } 898} 899function isBoxCollision(a, b) { 900 return a.left <= b.right && a.right >= b.left && a.top <= b.bottom && a.bottom >= b.top; 901} 902function isAnyBoxCollision(box, boxes) { 903 for (let i = 0; i < boxes.length; i++) 904 if (isBoxCollision(box, boxes[i])) 905 return boxes[i]; 906 return null; 907} 908function isWithinBox(container, box) { 909 return box.top >= 0 && box.bottom <= container.height && box.left >= 0 && box.right <= container.width; 910} 911function isBoxOutOfBounds(container, box, axis) { 912 switch (axis) { 913 case "+x": 914 return box.left < 0; 915 case "-x": 916 return box.right > container.width; 917 case "+y": 918 return box.top < 0; 919 case "-y": 920 return box.bottom > container.height; 921 } 922} 923function calcBoxIntersectPercentage(container, box) { 924 const x = Math.max(0, Math.min(container.width, box.right) - Math.max(0, box.left)), y = Math.max(0, Math.min(container.height, box.bottom) - Math.max(0, box.top)), intersectArea = x * y; 925 return intersectArea / (container.height * container.width); 926} 927function createCSSBox(container, box) { 928 return { 929 top: box.top / container.height, 930 left: box.left / container.width, 931 right: (container.width - box.right) / container.width, 932 bottom: (container.height - box.bottom) / container.height 933 }; 934} 935function resolveRelativeBox(container, box) { 936 box.top = box.top * container.height; 937 box.left = box.left * container.width; 938 box.right = container.width - box.right * container.width; 939 box.bottom = container.height - box.bottom * container.height; 940 return box; 941} 942const BOX_SIDES = ["top", "left", "right", "bottom"]; 943function setBoxCSSVars(el, container, box, prefix) { 944 const cssBox = createCSSBox(container, box); 945 for (const side of BOX_SIDES) { 946 setCSSVar(el, `${prefix}-${side}`, cssBox[side] * 100 + "%"); 947 } 948} 949function avoidBoxCollisions(container, box, boxes, axis) { 950 let percentage = 1, positionedBox, startBox = { ...box }; 951 for (let i = 0; i < axis.length; i++) { 952 while (isBoxOutOfBounds(container, box, axis[i]) || isWithinBox(container, box) && isAnyBoxCollision(box, boxes)) { 953 moveBox(box, axis[i], 1); 954 } 955 if (isWithinBox(container, box)) 956 return box; 957 const intersection = calcBoxIntersectPercentage(container, box); 958 if (percentage > intersection) { 959 positionedBox = { ...box }; 960 percentage = intersection; 961 } 962 box = { ...startBox }; 963 } 964 return positionedBox || startBox; 965} 966 967const POSITION_OVERRIDE = Symbol(0); 968function positionCue(container, cue, displayEl, boxes) { 969 let cueEl = displayEl.firstElementChild, line = computeCueLine(cue), displayBox, axis = []; 970 if (!displayEl[STARTING_BOX]) { 971 displayEl[STARTING_BOX] = createStartingBox(container, displayEl); 972 } 973 displayBox = resolveRelativeBox(container, { ...displayEl[STARTING_BOX] }); 974 if (displayEl[POSITION_OVERRIDE]) { 975 axis = [displayEl[POSITION_OVERRIDE] === "top" ? "+y" : "-y", "+x", "-x"]; 976 } else if (cue.snapToLines) { 977 let size; 978 switch (cue.vertical) { 979 case "": 980 axis = ["+y", "-y"]; 981 size = "height"; 982 break; 983 case "rl": 984 axis = ["+x", "-x"]; 985 size = "width"; 986 break; 987 case "lr": 988 axis = ["-x", "+x"]; 989 size = "width"; 990 break; 991 } 992 let step = getLineHeight(cueEl), position = step * Math.round(line), maxPosition = container[size] + step, initialAxis = axis[0]; 993 if (Math.abs(position) > maxPosition) { 994 position = position < 0 ? -1 : 1; 995 position *= Math.ceil(maxPosition / step) * step; 996 } 997 if (line < 0) { 998 position += cue.vertical === "" ? container.height : container.width; 999 axis = axis.reverse(); 1000 } 1001 moveBox(displayBox, initialAxis, position); 1002 } else { 1003 const isHorizontal = cue.vertical === "", posAxis = isHorizontal ? "+y" : "+x", size = isHorizontal ? displayBox.height : displayBox.width; 1004 moveBox( 1005 displayBox, 1006 posAxis, 1007 (isHorizontal ? container.height : container.width) * line / 100 1008 ); 1009 moveBox( 1010 displayBox, 1011 posAxis, 1012 cue.lineAlign === "center" ? size / 2 : cue.lineAlign === "end" ? size : 0 1013 ); 1014 axis = isHorizontal ? ["-y", "+y", "-x", "+x"] : ["-x", "+x", "-y", "+y"]; 1015 } 1016 displayBox = avoidBoxCollisions(container, displayBox, boxes, axis); 1017 setBoxCSSVars(displayEl, container, displayBox, "cue"); 1018 return displayBox; 1019} 1020function createStartingBox(container, cueEl) { 1021 const box = createBox(cueEl), pos = getStyledPositions(cueEl); 1022 cueEl[POSITION_OVERRIDE] = false; 1023 if (pos.top) { 1024 box.top = pos.top; 1025 box.bottom = pos.top + box.height; 1026 cueEl[POSITION_OVERRIDE] = "top"; 1027 } 1028 if (pos.bottom) { 1029 const bottom = container.height - pos.bottom; 1030 box.top = bottom - box.height; 1031 box.bottom = bottom; 1032 cueEl[POSITION_OVERRIDE] = "bottom"; 1033 } 1034 if (pos.left) 1035 box.left = pos.left; 1036 if (pos.right) 1037 box.right = container.width - pos.right; 1038 return createCSSBox(container, box); 1039} 1040function getStyledPositions(el) { 1041 const positions = {}; 1042 for (const side of BOX_SIDES) { 1043 positions[side] = parseFloat(el.style.getPropertyValue(`--cue-${side}`)); 1044 } 1045 return positions; 1046} 1047function computeCueLine(cue) { 1048 if (cue.line === "auto") { 1049 if (!cue.snapToLines) { 1050 return 100; 1051 } else { 1052 return -1; 1053 } 1054 } 1055 return cue.line; 1056} 1057function computeCuePosition(cue) { 1058 if (cue.position === "auto") { 1059 switch (cue.align) { 1060 case "start": 1061 case "left": 1062 return 0; 1063 case "right": 1064 case "end": 1065 return 100; 1066 default: 1067 return 50; 1068 } 1069 } 1070 return cue.position; 1071} 1072function computeCuePositionAlignment(cue, dir) { 1073 if (cue.positionAlign === "auto") { 1074 switch (cue.align) { 1075 case "start": 1076 return dir === "ltr" ? "line-left" : "line-right"; 1077 case "end": 1078 return dir === "ltr" ? "line-right" : "line-left"; 1079 case "center": 1080 return "center"; 1081 default: 1082 return `line-${cue.align}`; 1083 } 1084 } 1085 return cue.positionAlign; 1086} 1087 1088const REGION_AXIS = ["-y", "+y", "-x", "+x"]; 1089function positionRegion(container, region, regionEl, boxes) { 1090 let cues = Array.from(regionEl.querySelectorAll('[
1090data-
vendor: 7,440 bytes, lines 1090-1324
1090part="cue-display"]')), height = 0, limit = Math.max(0, cues.length - region.lines); 1091 for (let i = cues.length - 1; i >= limit; i--) { 1092 height += cues[i].offsetHeight; 1093 } 1094 setCSSVar(regionEl, "region-height", height + "px"); 1095 if (!regionEl[STARTING_BOX]) { 1096 regionEl[STARTING_BOX] = createCSSBox(container, createBox(regionEl)); 1097 } 1098 let box = { ...regionEl[STARTING_BOX] }; 1099 box = resolveRelativeBox(container, box); 1100 box.width = regionEl.clientWidth; 1101 box.height = height; 1102 box.right = box.left + box.width; 1103 box.bottom = box.top + height; 1104 box = avoidBoxCollisions(container, box, boxes, REGION_AXIS); 1105 setBoxCSSVars(regionEl, container, box, "region"); 1106 return box; 1107} 1108 1109class CaptionsRenderer { 1110 overlay; 1111 z; 1112 A = 0; 1113 C = "ltr"; 1114 B = []; 1115 D = false; 1116 E; 1117 h = /* @__PURE__ */ new Map(); 1118 j = /* @__PURE__ */ new Map(); 1119 /* Text direction. */ 1120 get dir() { 1121 return this.C; 1122 } 1123 set dir(dir) { 1124 this.C = dir; 1125 setDataAttr(this.overlay, "dir", dir); 1126 } 1127 get currentTime() { 1128 return this.A; 1129 } 1130 set currentTime(time) { 1131 this.A = time; 1132 this.update(); 1133 } 1134 constructor(overlay, init) { 1135 this.overlay = overlay; 1136 this.dir = init?.dir ?? "ltr"; 1137 overlay.setAttribute("translate", "yes"); 1138 overlay.setAttribute("aria-live", "off"); 1139 overlay.setAttribute("aria-atomic", "true"); 1140 setPartAttr(overlay, "captions"); 1141 this.G(); 1142 this.E = new ResizeObserver(this.I.bind(this)); 1143 this.E.observe(overlay); 1144 } 1145 changeTrack({ regions, cues }) { 1146 this.reset(); 1147 this.J(regions); 1148 for (const cue of cues) 1149 this.j.set(cue, null); 1150 this.update(); 1151 } 1152 addCue(cue) { 1153 this.j.set(cue, null); 1154 this.update(); 1155 } 1156 removeCue(cue) { 1157 this.j.delete(cue); 1158 this.update(); 1159 } 1160 update(forceUpdate = false) { 1161 this.H(forceUpdate); 1162 } 1163 reset() { 1164 this.j.clear(); 1165 this.h.clear(); 1166 this.B = []; 1167 this.overlay.textContent = ""; 1168 } 1169 destroy() { 1170 this.reset(); 1171 this.E.disconnect(); 1172 } 1173 I() { 1174 this.D = true; 1175 this.K(); 1176 } 1177 K = debounce(() => { 1178 this.D = false; 1179 this.G(); 1180 for (const el of this.h.values()) { 1181 el[STARTING_BOX] = null; 1182 } 1183 for (const el of this.j.values()) { 1184 if (el) 1185 el[STARTING_BOX] = null; 1186 } 1187 this.H(true); 1188 }, 50); 1189 G() { 1190 this.z = createBox(this.overlay); 1191 setCSSVar(this.overlay, "overlay-width", this.z.width + "px"); 1192 setCSSVar(this.overlay, "overlay-height", this.z.height + "px"); 1193 } 1194 H(forceUpdate = false) { 1195 if (!this.j.size || this.D) 1196 return; 1197 let cue, activeCues = [...this.j.keys()].filter((cue2) => this.A >= cue2.startTime && this.A <= cue2.endTime).sort( 1198 (cueA, cueB) => cueA.startTime !== cueB.startTime ? cueA.startTime - cueB.startTime : cueA.endTime - cueB.endTime 1199 ), activeRegions = activeCues.map((cue2) => cue2.region); 1200 for (let i = 0; i < this.B.length; i++) { 1201 cue = this.B[i]; 1202 if (activeCues[i] === cue) 1203 continue; 1204 if (cue.region && !activeRegions.includes(cue.region)) { 1205 const regionEl = this.h.get(cue.region.id); 1206 if (regionEl) { 1207 regionEl.removeAttribute("data-active"); 1208 forceUpdate = true; 1209 } 1210 } 1211 const cueEl = this.j.get(cue); 1212 if (cueEl) { 1213 cueEl.remove(); 1214 forceUpdate = true; 1215 } 1216 } 1217 for (let i = 0; i < activeCues.length; i++) { 1218 cue = activeCues[i]; 1219 let cueEl = this.j.get(cue); 1220 if (!cueEl) 1221 this.j.set(cue, cueEl = this.L(cue)); 1222 const regionEl = this.F(cue) && this.h.get(cue.region.id); 1223 if (regionEl && !regionEl.hasAttribute("data-active")) { 1224 requestAnimationFrame(() => setDataAttr(regionEl, "active")); 1225 forceUpdate = true; 1226 } 1227 if (!cueEl.isConnected) { 1228 (regionEl || this.overlay).append(cueEl); 1229 forceUpdate = true; 1230 } 1231 } 1232 if (forceUpdate) { 1233 const boxes = [], seen = /* @__PURE__ */ new Set(); 1234 for (let i = activeCues.length - 1; i >= 0; i--) { 1235 cue = activeCues[i]; 1236 if (seen.has(cue.region || cue)) 1237 continue; 1238 const isRegion = this.F(cue), el = isRegion ? this.h.get(cue.region.id) : this.j.get(cue); 1239 if (isRegion) { 1240 boxes.push(positionRegion(this.z, cue.region, el, boxes)); 1241 } else { 1242 boxes.push(positionCue(this.z, cue, el, boxes)); 1243 } 1244 seen.add(isRegion ? cue.region : cue); 1245 } 1246 } 1247 updateTimedVTTCueNodes(this.overlay, this.A); 1248 this.B = activeCues; 1249 } 1250 J(regions) { 1251 if (!regions) 1252 return; 1253 for (const region of regions) { 1254 const el = this.M(region); 1255 this.h.set(region.id, el); 1256 this.overlay.append(el); 1257 } 1258 } 1259 M(region) { 1260 const el = document.createElement("div"); 1261 setPartAttr(el, "region"); 1262 setDataAttr(el, "id", region.id); 1263 setDataAttr(el, "scroll", region.scroll); 1264 setCSSVar(el, "region-width", region.width + "%"); 1265 setCSSVar(el, "region-anchor-x", region.regionAnchorX); 1266 setCSSVar(el, "region-anchor-y", region.regionAnchorY); 1267 setCSSVar(el, "region-viewport-anchor-x", region.viewportAnchorX); 1268 setCSSVar(el, "region-viewport-anchor-y", region.viewportAnchorY); 1269 setCSSVar(el, "region-lines", region.lines); 1270 return el; 1271 } 1272 L(cue) { 1273 const display = document.createElement("div"), position = computeCuePosition(cue), positionAlignment = computeCuePositionAlignment(cue, this.C); 1274 setPartAttr(display, "cue-display"); 1275 if (cue.vertical !== "") 1276 setDataAttr(display, "vertical"); 1277 setCSSVar(display, "cue-text-align", cue.align); 1278 if (cue.style) { 1279 for (const prop of Object.keys(cue.style)) { 1280 display.style.setProperty(prop, cue.style[prop]); 1281 } 1282 } 1283 if (!this.F(cue)) { 1284 setCSSVar( 1285 display, 1286 "cue-writing-mode", 1287 cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl" 1288 ); 1289 if (!cue.style?.["--cue-width"]) { 1290 let maxSize = position; 1291 if (positionAlignment === "line-left") { 1292 maxSize = 100 - position; 1293 } else if (positionAlignment === "center" && position <= 50) { 1294 maxSize = position * 2; 1295 } else if (positionAlignment === "center" && position > 50) { 1296 maxSize = (100 - position) * 2; 1297 } 1298 const size = cue.size < maxSize ? cue.size : maxSize; 1299 if (cue.vertical === "") 1300 setCSSVar(display, "cue-width", size + "%"); 1301 else 1302 setCSSVar(display, "cue-height", size + "%"); 1303 } 1304 } else { 1305 setCSSVar( 1306 display, 1307 "cue-offset", 1308 `${position - (positionAlignment === "line-right" ? 100 : positionAlignment === "center" ? 50 : 0)}%` 1309 ); 1310 } 1311 const el = document.createElement("div"); 1312 setPartAttr(el, "cue"); 1313 if (cue.id) 1314 setDataAttr(el, "id", cue.id); 1315 el.innerHTML = renderVTTCueString(cue); 1316 display.append(el); 1317 return display; 1318 } 1319 F(cue) { 1320 return cue.region && cue.size === 100 && cue.vertical === "" && cue.line === "auto"; 1321 } 1322} 1323 1324export { CaptionsRenderer as C, ParseError as P, TextCue as T, VTTParser as V, VTTBlock as a, VTTCue as b, ParseErrorCode as c, parseResponse as d, parseByteStream as e, parseText as f, parseTextStream as g, VTTRegion as h, createVTTCueTemplate as i, renderVTTTokensString as j, parseVTTTimestamp as p, renderVTTCueString as r, tokenizeVTTCue as t, updateTimedVTTCueNodes as u };
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.