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_webrtc MediaStreamTrack.captureFrame() polled in a timer
  • camera plugin's startImageStream after 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 / targetFps ms 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 ScanResult without 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 scanBytes call.
final
dedupeCache PerceptualCache?
Optional perceptual dedup cache. When set, visually identical frames re-use the prior ScanResult instead 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 null if timeout elapses (or the source closes) first.

Operators

operator ==(Object other) bool
The equality operator.
inherited