codecWorker function
Hosts one decoder — video or audio — for the life of the worker.
One decoder per worker, matching the seam it implements: a PlatformDecoder
is created, used and closed as a unit, so its thread can be too. An A/V
stream therefore gets two workers, which is the right trade: they decode
concurrently instead of interleaving on one thread, and the payload is
fetched once and cached by the browser either way.
Implementation
Future<void> codecWorker(WorkerChannel channel) async {
final init = channel.initialMessage! as Map<String, Object?>;
final role = init['role'] as String?;
PlatformDecoder? video;
PlatformAudioDecoder? audio;
channel.handleRequests((Object? request) async {
switch (request) {
case 'open':
// Configure here rather than at spawn time so a codec the browser
// declines surfaces as a failed request the host can fall back from,
// not as a worker that started and is quietly useless.
switch (role) {
case kRoleVideo:
video = await WebCodecsVideoDecoder.create(_videoConfig(init));
case kRoleAudio:
audio = await WebCodecsAudioDecoder.create(_audioConfig(init));
default:
throw CodecInitException(_kBackend, 'unknown worker role: $role');
}
return null;
case ['decode', final Map<String, Object?> packet]:
final encoded = _packet(packet);
if (video != null) return _sendFrame(await video!.decode(encoded));
return <Object?>[
for (final chunk in await audio!.decode(encoded)) _chunk(chunk),
];
case 'flush':
if (video != null) {
return <Object?>[
for (final frame in await video!.flush()) _sendFrame(frame),
];
}
return <Object?>[
for (final chunk in await audio!.flush()) _chunk(chunk),
];
default:
throw StateError('unknown op: $request');
}
});
await channel.onClose;
try {
await video?.close();
await audio?.close();
} on Object {
// Teardown is best effort; the host is already gone.
}
}