Skip to content

`ShowcaseTracker`

ShowcaseTracker is a thin facade over MovementTracker shipped in package:pose_flow_studio. It exists so trainer-facing surfaces (the PoseFlow Studio editor, any CMS that embeds it) can subscribe to per-frame state via a ValueNotifier<TrackingFrame> instead of wiring per-frame callbacks by hand, ValueListenableBuilder rebuilds only the widgets that bind to it, leaving the rest of the tree alone.

Consumer apps that just want “load a movement, count reps” don’t need ShowcaseTracker, use TrackedMovementView or MovementTracker directly. The underlying rep-counting math is identical.

import 'package:pose_flow_studio/pose_flow_studio.dart';
final tracker = ShowcaseTracker(preset: MovementTrackerPreset.standard);
tracker.loadMovement(movement);
// Per-frame UI bound via the notifier
ValueListenableBuilder<TrackingFrame>(
valueListenable: tracker.frame,
builder: (context, frame, _) {
return Text('Reps: ${frame.repCount}');
},
);
// Per-frame pose dispatch from your camera surface
tracker.processFrame(pose, videoWidth: w, videoHeight: h);

One instance per “tracking session”, create when a screen mounts, dispose() when it unmounts.

Constructor

ShowcaseTracker({
MovementTrackerPreset preset = MovementTrackerPreset.standard,
});

Internally instantiates a MovementTracker.withPreset(preset) and subscribes to its onRepCompleted + onFeedback streams so it can re-broadcast them through its own controllers.

Loading a movement

void loadMovement(
Movement movement, {
MovementConfig? configOverride,
});

Same semantics as MovementTracker.load, the optional MovementConfig override replaces the movement’s inlined values.

Per-frame dispatch

TrackingFrame? processFrame(
Pose pose, {
int? videoWidth,
int? videoHeight,
double? inferenceMs,
bool? usedDetector,
String? backboneBackend,
String? decoderBackend,
String? landmarkBackend,
});

Forwards pose straight to MovementTracker.processFrame, then wraps the resulting TrackingResult in a TrackingFrame (which adds the optional camera + timing fields above) and assigns it to frame.value so the ValueNotifier fires.

The optional fields are surfaced into TrackingFrame for the diagnostics HUD, they don’t affect tracking. Pass them when your camera surface knows the values; pass null otherwise.

What’s a TrackingFrame?

It’s a TrackingResult plus the optional perf / camera fields:

class TrackingFrame {
final Pose? pose;
final int repCount;
final double formScore;
final String? phaseId;
final List<String> feedback;
final RepQuality? lastRepQuality;
final bool repJustCompleted;
final Map<String, double> trackingValues;
final RepPipelineSnapshot pipeline;
final int frameNumber;
final int? videoWidth;
final int? videoHeight;
final double? inferenceMs;
final bool? usedDetector;
final String? backboneBackend;
final String? decoderBackend;
final String? landmarkBackend;
}

frameNumber increments monotonically on every processFrame call , useful for “skip every Nth frame” rendering optimisations.

”Pose lost” handling

void handlePoseLost();

Wire this to your camera’s onPoseLost callback (or whatever signals “the detector didn’t find a person this frame”). It nulls the pose on the notifier so HUDs can flip to “looking for you…” without per-frame callback complexity.

Events

Stream<RepCompletedEvent> get onRepCompleted;
Stream<FormFeedbackEvent> get onFeedback;

Re-broadcasts of the underlying MovementTracker streams. Subscribe to these for one-shot effects (haptic on rep, voice cue on feedback); use the notifier for sustained UI state.

Live tuning

void updateRepScoring(RepScoringConfig config);
RepScoringConfig get repScoring;
ScoringProfile get scoringProfile;

updateRepScoring mirrors MovementTracker.updateRepScoring, the Studio’s rep-scoring side-panel calls this on every slider movement so changes take effect on the next rep without restarting the session. It also writes the new gate into the in-memory Movement.repScoring so you can persist the change later.

scoringProfile exposes the per-dimension weights the ScoringProfileInferrer derived for the loaded movement.

Disposal

Future<void> dispose();

Always call this when the screen unmounts. Cancels the underlying tracker’s stream subscriptions, closes both controllers, disposes the MovementTracker, and disposes the notifier. Idempotent.

  • MovementTracker, the underlying tracker. ShowcaseTracker is a thin wrapper.
  • TrackedMovementView, the consumer-app convenience widget. Doesn’t depend on pose_flow_studio.
  • TrackingResult, the per-frame shape the wrapped TrackingFrame is built from.