Голосовые: индекс opus/ogg со срезами страниц, общий аудиоконтроллер воспроизведения, переписанный ogg-энкодер. Кружки: предзагрузка видео, геометрия кольца прогресса. third_party/kolibri в .gitignore — локальный dev-клон, зависимость берётся с pub.dev. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VMWkmNx3Ns9SEuKeGEiaR4
157 lines
4.1 KiB
Dart
157 lines
4.1 KiB
Dart
import 'dart:typed_data';
|
|
|
|
class OggPageWriter {
|
|
static const int maxSegmentsPerPage = 255;
|
|
static const int continuedPacket = 0x01;
|
|
static const int beginningOfStream = 0x02;
|
|
static const int endOfStream = 0x04;
|
|
|
|
static const int headerSize = 27;
|
|
static const int _granuleOffset = 6;
|
|
static const int _serialOffset = 14;
|
|
static const int _sequenceOffset = 18;
|
|
static const int _crcOffset = 22;
|
|
static const int _segmentCountOffset = 26;
|
|
|
|
static int segmentsFor(int length) => (length ~/ 255) + 1;
|
|
|
|
static int lengthFor(List<Uint8List> packets) {
|
|
var segments = 0;
|
|
var body = 0;
|
|
for (final packet in packets) {
|
|
segments += segmentsFor(packet.length);
|
|
body += packet.length;
|
|
}
|
|
return headerSize + segments + body;
|
|
}
|
|
|
|
static Uint8List page({
|
|
required int headerType,
|
|
required int granulePos,
|
|
required int serial,
|
|
required int sequence,
|
|
required List<Uint8List> packets,
|
|
}) {
|
|
final out = Uint8List(lengthFor(packets));
|
|
writeInto(
|
|
out,
|
|
0,
|
|
headerType: headerType,
|
|
granulePos: granulePos,
|
|
serial: serial,
|
|
sequence: sequence,
|
|
packets: packets,
|
|
);
|
|
return out;
|
|
}
|
|
|
|
static int writeInto(
|
|
Uint8List out,
|
|
int offset, {
|
|
required int headerType,
|
|
required int granulePos,
|
|
required int serial,
|
|
required int sequence,
|
|
required List<Uint8List> packets,
|
|
}) {
|
|
final view = ByteData.sublistView(out);
|
|
out[offset] = 0x4f;
|
|
out[offset + 1] = 0x67;
|
|
out[offset + 2] = 0x67;
|
|
out[offset + 3] = 0x53;
|
|
view.setUint8(offset + 4, 0);
|
|
view.setUint8(offset + 5, headerType);
|
|
view.setInt64(offset + _granuleOffset, granulePos, Endian.little);
|
|
view.setUint32(offset + _serialOffset, serial, Endian.little);
|
|
view.setUint32(offset + _sequenceOffset, sequence, Endian.little);
|
|
view.setUint32(offset + _crcOffset, 0, Endian.little);
|
|
|
|
var table = offset + headerSize;
|
|
for (final packet in packets) {
|
|
var remaining = packet.length;
|
|
while (remaining >= 255) {
|
|
out[table++] = 255;
|
|
remaining -= 255;
|
|
}
|
|
out[table++] = remaining;
|
|
}
|
|
view.setUint8(offset + _segmentCountOffset, table - offset - headerSize);
|
|
|
|
var cursor = table;
|
|
for (final packet in packets) {
|
|
out.setRange(cursor, cursor + packet.length, packet);
|
|
cursor += packet.length;
|
|
}
|
|
|
|
view.setUint32(
|
|
offset + _crcOffset,
|
|
crc32(out, offset, cursor),
|
|
Endian.little,
|
|
);
|
|
return cursor;
|
|
}
|
|
|
|
static final List<Uint32List> _crcTables = _buildCrcTables();
|
|
|
|
static List<Uint32List> _buildCrcTables() {
|
|
final base = Uint32List(256);
|
|
for (var i = 0; i < 256; i++) {
|
|
var r = (i << 24) & 0xffffffff;
|
|
for (var j = 0; j < 8; j++) {
|
|
if ((r & 0x80000000) != 0) {
|
|
r = ((r << 1) ^ 0x04c11db7) & 0xffffffff;
|
|
} else {
|
|
r = (r << 1) & 0xffffffff;
|
|
}
|
|
}
|
|
base[i] = r;
|
|
}
|
|
|
|
final tables = <Uint32List>[base];
|
|
for (var slice = 1; slice < 4; slice++) {
|
|
final previous = tables[slice - 1];
|
|
final next = Uint32List(256);
|
|
for (var i = 0; i < 256; i++) {
|
|
next[i] =
|
|
(((previous[i] << 8) & 0xffffffff) ^
|
|
base[(previous[i] >> 24) & 0xff]) &
|
|
0xffffffff;
|
|
}
|
|
tables.add(next);
|
|
}
|
|
return tables;
|
|
}
|
|
|
|
static int crc32(Uint8List data, [int start = 0, int? end]) {
|
|
final stop = end ?? data.length;
|
|
final t0 = _crcTables[0];
|
|
final t1 = _crcTables[1];
|
|
final t2 = _crcTables[2];
|
|
final t3 = _crcTables[3];
|
|
|
|
var crc = 0;
|
|
var i = start;
|
|
final wordEnd = stop - ((stop - start) & 3);
|
|
while (i < wordEnd) {
|
|
crc ^=
|
|
(data[i] << 24) |
|
|
(data[i + 1] << 16) |
|
|
(data[i + 2] << 8) |
|
|
data[i + 3];
|
|
crc =
|
|
t3[(crc >> 24) & 0xff] ^
|
|
t2[(crc >> 16) & 0xff] ^
|
|
t1[(crc >> 8) & 0xff] ^
|
|
t0[crc & 0xff];
|
|
i += 4;
|
|
}
|
|
while (i < stop) {
|
|
crc =
|
|
(((crc << 8) & 0xffffffff) ^ t0[((crc >> 24) & 0xff) ^ data[i]]) &
|
|
0xffffffff;
|
|
i++;
|
|
}
|
|
return crc & 0xffffffff;
|
|
}
|
|
}
|