Skip to main content

Recording & playback

namespace · AR51.Unity.SDK.Recorder / .Playback Unity / C#

The recording & playback surface lets you persist incoming mocap frames to a file and replay them back through the live consumers. A recording is a sequence of timed keyframes; each parsed line becomes a KeyframeCache holding one or more KeyframeTasks, and executing a task feeds its payload into the matching consumer (SkeletonConsumer, ObjectTransformConsumer, ObjectDetectionConsumer) exactly as if it had arrived live. SkeletonWebStreamer is the related component that pushes visible bone transforms out to a browser viewer.

Units: Unity world-space positions are Vector3 in meters, rotations are Quaternion, and all keyframe/cache times are seconds (capture times are seconds since the Unix epoch, UTC).

Meters, not centimeters

Every position/length on this page is in meters (Unity convention). AR 51 mocap data is already meters — no conversion is applied on the Unity side.

Vector3Keyframe / QuaternionKeyframe

struct Namespace: AR51.Unity.SDK.Recorder.

Simple timed sample structs for recorded animation curves — one timestamped value each.

public struct Vector3Keyframe { public float Time; public Vector3 Value; public Vector3Keyframe(float time, Vector3 value); }
public struct QuaternionKeyframe { public float Time; public Quaternion Value; public QuaternionKeyframe(float time, Quaternion value); }
FieldTypeDescription
Timefloatsample time, in seconds
ValueVector3 / Quaternionthe sampled value — positions in meters, rotations as a Unity quaternion

IKeyframeSource

interface Namespace: AR51.Unity.SDK.Playback.

Implement this to remap recorded keyframes during playback — prepend a skeleton-id prefix, apply a spatial transform to joint positions, and offset capture timestamps. Use it to relocate or retime a recorded performance (e.g. replay a recording into a different anchor space, or merge several recordings under distinct id prefixes). A KeyframeTask applies its assigned Source (when set) before executing.

MemberReturnsDescription
Prefixstringprepended to skeleton ids coming from this source
Transform(Vector3[] positions)Vector3[]remap an array of joint positions (meters)
Transform(Vector3 position)Vector3remap a single position (meters)
Offset(double time)doubleremap a capture timestamp (seconds)
string Prefix { get; }
Vector3[] Transform(Vector3[] positions);
Vector3 Transform(Vector3 position);
double Offset(double time);

KeyframeTask

abstract class Namespace: AR51.Unity.SDK.Playback.

A single unit of playback work. Execute() replays the task into the live consumers — applying its optional Source remap first. The concrete subclasses each wrap one payload type.

public abstract class KeyframeTask
{
public IKeyframeSource Source { get; set; }
public abstract void Execute();
}
PropertyTypeDescription
SourceIKeyframeSourceoptional remap applied before Execute() (null = replay as recorded)

Subclasses

Each subclass holds one payload and routes it to the matching consumer on Execute().

TaskPayloadExecute() replays into
ActionKeyframeTaskActionruns an arbitrary delegate (markers, side-effects)
SkeletonTaskSkeletonSkeletonConsumer.Instance.OnSkeleton
ObjectTransformTaskObjectTransformObjectTransformConsumer.Instance.OnObject
ObjectDetectionTaskObjectDetectionReply (transport)ObjectDetectionConsumer.Instance.OnObjectDetected
public class ActionKeyframeTask : KeyframeTask { public ActionKeyframeTask(Action action); public override void Execute(); }
public sealed class SkeletonTask : KeyframeTask { public Skeleton Skeleton { get; } public SkeletonTask(Skeleton s); public override void Execute(); }
public sealed class ObjectTransformTask : KeyframeTask { public ObjectTransform ObjectTransform { get; } public ObjectTransformTask(ObjectTransform t); public override void Execute(); }
public sealed class ObjectDetectionTask : KeyframeTask { public ObjectDetectionReply ObjectDetectionReply { get; } public ObjectDetectionTask(ObjectDetectionReply t); public override void Execute(); }

ActionKeyframeTask

Wraps an arbitrary Action; Execute() invokes it. Use to schedule custom side-effects (logging, marker callbacks) on the playback timeline.

SkeletonTask

Holds a Skeleton. Execute() applies the (optionally Source-transformed) skeleton to SkeletonConsumer.Instance.OnSkeleton, driving characters exactly as a live frame would.

ObjectTransformTask

Holds an ObjectTransform. Execute() feeds it to ObjectTransformConsumer.Instance.OnObject.

ObjectDetectionTask

Holds an ObjectDetectionReply (transport/proto type). Execute() feeds it to ObjectDetectionConsumer.Instance.OnObjectDetected.

KeyframeCache

sealed class Namespace: AR51.Unity.SDK.Playback.

One parsed line of a recording: timing, source device, the raw JSON, and the decoded KeyframeTasks. KeyframeParser produces these when loading a recording; you can also build one directly from a Skeleton, an ObjectDetectionReply, or a timestamped Action.

MemberTypeDescription
Timing
timedoubleabsolute capture time, seconds since the Unix epoch
timeSinceStartdoubleseconds since the recording started
Source
SourceIKeyframeSourceoptional remap applied to this line's tasks
device_namestringoriginating device name
device_ipstringoriginating device IP
jsonstringthe raw recorded JSON for this line
Decoded payload
TasksKeyframeTask[]the decoded tasks (populated by ParseTasks)
SkeletonsSkeleton[]skeletons decoded from this line
ObjectDetectionRepliesObjectDetectionReply[]detection replies decoded from this line (transport, read-only)
public KeyframeCache(ReadOnlySpan<char> line);
public KeyframeCache(string line);
public KeyframeCache(Skeleton s, double? overrideTimestamp = null);
public KeyframeCache(ObjectDetectionReply objectDetection, double? overrideTimestamp = null);
public KeyframeCache(double timestamp, Action action);

public void ParseTasks(bool parseBinary = false); // decode json → Tasks
public override string ToString();

ParseTasks(parseBinary) decodes the line's json into the Tasks array; pass parseBinary: true to also decode packed binary payloads.

KeyframeParser

static class Namespace: AR51.Unity.SDK.Playback.

Loads and parses recording files into KeyframeCache lists — multi-threaded, sorting the result by time.

MethodReturnsDescription
EstimateKeyframeCountlongrough keyframe count from a byte length (pre-size buffers)
ParseList<KeyframeCache>parse streams synchronously
ParseAsyncIEnumeratorcoroutine wrapper around Parse with progress reporting

EstimateKeyframeCount

public static long EstimateKeyframeCount(long length);

Estimates how many keyframes a recording of length bytes contains — use it to pre-size lists/progress bars before parsing.

Parse

public static List<KeyframeCache> Parse(Stream[] fileStreams, int length,
CancellationToken? token = null, Action<string, float> onProgress = null, IKeyframeSource[] sources = null);

Parses the given file streams into a time-sorted list of KeyframeCache. Runs multi-threaded.

Parameters
fileStreamsStream[]the recording file stream(s) to read
lengthintexpected keyframe count (e.g. from EstimateKeyframeCount)
tokenCancellationToken?optional cancellation
onProgressAction<string, float>progress callback — message + a 0–1 fraction
sourcesIKeyframeSource[]optional per-stream IKeyframeSource remaps

ParseAsync

public static IEnumerator ParseAsync(Stream[] fileStreams, int length, List<KeyframeCache> keyframes,
CancellationToken? token = null, Action<string, float> onProgress = null, IKeyframeSource[] sources = null);

Coroutine wrapper around Parse: yield-return it from a Unity coroutine and read the results from the keyframes list you pass in. onProgress reports a 0–1 fraction so you can drive a loading bar.

Exampleinferred
var keyframes = new List<KeyframeCache>();
using var fs = File.OpenRead(recordingPath);
int count = (int)KeyframeParser.EstimateKeyframeCount(fs.Length);
StartCoroutine(KeyframeParser.ParseAsync(
new Stream[] { fs }, count, keyframes,
onProgress: (msg, f) => Debug.Log($"{msg} {f:P0}"))); // f is 0..1
note

On WebGL the async signature differs: ParseAsync(StringReader reader, int length, List<KeyframeCache> keyframes, CancellationToken? token, Action<float> onProgress) — a StringReader source and a float-only progress callback.

SkeletonWebStreamer

class · Singleton Unity component singleton
public class SkeletonWebStreamer : Singleton<SkeletonWebStreamer>

Namespace: global (no namespace). A Unity component (singleton) that streams the currently-visible characters' bone transforms over TCP to a web skeleton server, for browser-based viewers. Enable it — and configure the target — via SkeletonConsumer.StreamToWeb / StreamToWebUri, or set the fields directly and enable the component.

MemberTypeDefaultDescription
HostUristring"skeleton-web-server-aci.westeurope.azurecontainer.io"target web skeleton server host
Portint13000target TCP port
public string HostUri = "skeleton-web-server-aci.westeurope.azurecontainer.io";
public int Port = 13000;
public Task<bool> TryConnectAsync(string hostname = "127.0.0.1", int port = 13000);

Enable the component to start streaming; set HostUri / Port first. The usual path is to flip SkeletonConsumer.StreamToWeb on, which configures and enables this streamer for you.

caution

⚠️ needs confirmation: serialization runs automatically while the component is enabled — TryConnectAsync is normally driven internally, so app code rarely calls it directly.

See also

Was this page helpful?