69 lines
1.5 KiB
Dart
69 lines
1.5 KiB
Dart
|
|
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<TimedSubtitle> _cues = [];
|
|
|
|
bool get isEmpty => _cues.isEmpty;
|
|
|
|
|
|
void add(double startSec, double endSec, List<String> 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<String> textAt(Duration position) {
|
|
final out = <String>[];
|
|
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;
|
|
}
|
|
}
|