class TimedSubtitle { final Duration start; final Duration end; final String text; const TimedSubtitle({ required this.start, required this.end, required this.text, }); } class EmbeddedSubtitleCues { final List _cues = []; bool get isEmpty => _cues.isEmpty; void add(double startSec, double endSec, List lines) { final text = lines .map((s) => s.trim()) .where((s) => s.isNotEmpty) .join('\n'); if (text.isEmpty) return; final start = _toDuration(startSec); final end = _toDuration(endSec); if (end < start) return; final at = _lowerBound(start); for (var i = at; i < _cues.length && _cues[i].start == start; i++) { if (_cues[i].end == end && _cues[i].text == text) return; } _cues.insert(at, TimedSubtitle(start: start, end: end, text: text)); } List textAt(Duration position) { final out = []; for (final cue in _cues) { if (position < cue.start) break; if (position <= cue.end) out.addAll(cue.text.split('\n')); } return out; } void clear() => _cues.clear(); static Duration _toDuration(double seconds) => Duration(milliseconds: (seconds * 1000).round()); int _lowerBound(Duration start) { int lo = 0; int hi = _cues.length; while (lo < hi) { final mid = (lo + hi) >> 1; if (_cues[mid].start < start) { lo = mid + 1; } else { hi = mid; } } return lo; } }