PageSourceSearch

https://gls.ai/assets/controllers/transcript_highlight_controller-…5ea510ae9edfa6c01e4d57858e232e9fcd3ca323.js

js gls.ai collected 2026-10-05 14:59:13 UTC 1,834 bytes, 65 lines download raw bytes

1import { Controller } from '@hotwired/stimulus';
2
3// Highlights the transcript sentence that matches the current audio position.
4// The sentence whose `start_time` is the latest one at or before the playhead
5// stays lit until the next sentence's `start_time` is reached.
6export default class extends Controller {
7  static targets = ['sentence'];
8  static classes = ['active'];
9
10  connect() {
11    this.audio = this.element.querySelector('audio');
12    if (!this.audio) return;
13
14    // Sentence start times, ascending (sentences are rendered in time order).
15    this.starts = this.sentenceTargets.map((el) => parseFloat(el.dataset.startTime) || 0);
16    this.activeIndex = -1;
17
18    this.refresh = this.refresh.bind(this);
19    this.audio.addEventListener('timeupdate', this.refresh);
20    this.audio.addEventListener('seeked', this.refresh);
21  }
22
23  disconnect() {
24    if (!this.audio) return;
25
26    this.audio.removeEventListener('timeupdate', this.refresh);
27    this.audio.removeEventListener('seeked', this.refresh);
28  }
29
30  refresh() {
31    const index = this.indexForTime(this.audio.currentTime);
32    if (index === this.activeIndex) return;
33
34    this.setActive(index);
35  }
36
37  // Index of the last sentence whose start time is <= t (binary search).
38  indexForTime(t) {
39    let lo = 0;
40    let hi = this.starts.length - 1;
41    let result = -1;
42
43    while (lo <= hi) {
44      const mid = (lo + hi) >> 1;
45      if (this.starts[mid] <= t) {
46        result = mid;
47        lo = mid + 1;
48      } else {
49        hi = mid - 1;
50      }
51    }
52
53    return result;
54  }
55
56  setActive(index) {
57    if (this.activeIndex >= 0) {
58      this.sentenceTargets[this.activeIndex].classList.remove(...this.activeClasses);
59    }
60    if (index >= 0) {
61      this.sentenceTargets[index].classList.add(...this.activeClasses);
62    }
63    this.activeIndex = index;
64  }
65};

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.