FrameStreamScanner class
Adapter that turns an arbitrary Stream<Uint8List> of encoded image frames
(JPEG / PNG / WebP — anything Flutter's image codecs can decode) into a
throttled Stream<ScanResult>.
Designed for live video pipelines where the source emits frames faster than the model can classify them — common examples:
flutter_webrtcMediaStreamTrack.captureFrame()polled in a timercameraplugin'sstartImageStreamafter JPEG encoding- Custom WebSocket / RTSP / HLS frame producers
flutter_webrtc integration
FrameStreamScanner deliberately does NOT depend on flutter_webrtc —
glue the two together caller-side:
import 'package:flutter_webrtc/flutter_webrtc.dart';
Stream<Uint8List> webRtcFrameStream(
MediaStreamTrack track, {
int pollHz = 4,
}) {
final controller = StreamController<Uint8List>();
final period = Duration(milliseconds: (1000 / pollHz).round());
late final Timer timer;
timer = Timer.periodic(period, (_) async {
try {
final buffer = await track.captureFrame();
controller.add(buffer.asUint8List());
} catch (_) {/* track stopped or capture failed — skip */}
});
controller.onCancel = () => timer.cancel();
return controller.stream;
}
final scanner = NsfwDetector.instance.scanFrameStream(
frames: webRtcFrameStream(remoteVideoTrack, pollHz: 4),
targetFps: 2,
earlyExitOnNsfw: true,
dedupeCache: NsfwDetector.instance.perceptualCache,
);
final firstNsfw = await scanner.waitForNsfw(
timeout: const Duration(seconds: 30),
);
if (firstNsfw != null) {
// moderate the remote peer
}
await scanner.stop();
Backpressure semantics
- Frames that arrive sooner than
1000 / targetFpsms after the last accepted frame are dropped silently. - If a scan is still in flight when a new frame is accepted, the new frame is also dropped — the scanner never queues more than one in-flight scan.
- When dedupeCache is provided, accepted frames that match a recent
hash replay the cached
ScanResultwithout re-running the model.
Constructors
-
FrameStreamScanner({required Stream<
Uint8List> frames, double confidenceThreshold = 0.7, int targetFps = 2, bool earlyExitOnNsfw = false, String? modelId, PerceptualCache? dedupeCache})
Properties
- confidenceThreshold → double
-
Confidence threshold forwarded to each underlying
scanBytescall.final - dedupeCache → PerceptualCache?
-
Optional perceptual dedup cache. When set, visually identical frames
re-use the prior
ScanResultinstead of re-running the model.final - earlyExitOnNsfw → bool
-
If
true, the input subscription is cancelled and results is closed the first time a ScanResult crosses the NSFW threshold.final - hashCode → int
-
The hash code for this object.
no setterinherited
- modelId → String?
-
Optional model id; falls back to the detector's default model.
final
-
results
→ Stream<
ScanResult> -
Broadcast stream of classification results. Closes when stop is called
or when the input stream completes.
no setter
- runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
- targetFps → int
-
Maximum frames classified per second. Source frames arriving faster than
this are dropped.
final
Methods
-
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
stop(
) → Future< void> - Cancels the input subscription and closes the result stream. Idempotent.
-
toString(
) → String -
A string representation of this object.
inherited
-
waitForNsfw(
{Duration? timeout}) → Future< ScanResult?> -
Resolves with the first ScanResult that crosses the NSFW threshold, or
nulliftimeoutelapses (or the source closes) first.
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited