Skip to content

Recording Sessions

The core recording API lives behind XRXPManager.Recorder.

Session lifecycle

Typical flow:

  1. initialize XRXP in the scene
  2. start a session (creates a User, Environment, and Session)
  3. add events and tracked data
  4. stop the session
  5. let pending traces flush before quit

Minimal example

using XRXP;
using System.Collections.Generic;

public class ExampleRecorder : UnityEngine.MonoBehaviour
{
    private string _sessionId;

    private void Start()
    {
        // StartSession(comments, userId, environmentProperties, environmentId)
        _sessionId = XRXPManager.Recorder.StartSession(
            comments: "Participant 001 - observer role",
            userId: "participant-001",
            environmentProperties: new Dictionary<string, string> {
                { "application_version", "1.2.0" },
                { "difficulty", "medium" }
            }
        );

        XRXPManager.Recorder.AddLogEvent("User", "clicked", "StartButton");
        XRXPManager.Recorder.AddQuestion("Comfort", "High");
    }

    private void OnApplicationQuit()
    {
        XRXPManager.Recorder.StopSession();
        XRXPManager.Recorder.EndTracing();
    }
}

Available recording methods

StartSession / StopSession

StartSession creates the User, Environment, and Session traces and returns a session ID.

// Start a new session
string sessionId = XRXPManager.Recorder.StartSession(
    comments: "Role: surgeon",           // optional, describes the user's role in this session
    userId: "user-042",                  // optional, auto-generated UUID if empty
    environmentProperties: new Dictionary<string, string> {
        { "scene", "OperatingRoom" },
        { "difficulty", "hard" }
    },
    environmentId: null                  // optional, reuse an existing environment
);

// Stop the current session (records end time)
XRXPManager.Recorder.StopSession();

Sessions can be nested. Starting a new session while one is already active creates a sub-session whose parent is the current session. Stopping always closes the most recent one. This is useful when a session represents an overall participation and sub-sessions represent individual exercises.

JoinSession

Join an existing multi-user session created by another participant.

XRXPManager.Recorder.JoinSession(
    sessionId: "session-abc-123",
    comments: "Observer role",
    userId: "observer-001",
    environmentProperties: new Dictionary<string, string> {
        { "device", "Quest3" }
    }
);

Log events

Record discrete actions using the Actor - Verb - Object pattern, with optional properties.

// Simple event
XRXPManager.Recorder.AddLogEvent("User", "grabbed", "Cube_01");

// Event with properties
XRXPManager.Recorder.AddLogEvent(
    "Bob", "eats", "apple",
    new Dictionary<string, string> {
        { "color", "red" },
        { "hand", "right" }
    }
);

Questions

Record survey-style answers with optional properties. Questions use a developer-defined questionId to group answers to the same question across sessions and users.

// Basic question (auto-generates questionId)
XRXPManager.Recorder.AddQuestion("How was your experience?", "Great!");

// Question with explicit questionId for grouping
XRXPManager.Recorder.AddQuestion(
    "experience-rating",     // questionId - groups answers to same question
    "How was your experience?",
    "8"
);

// Question with properties
XRXPManager.Recorder.AddQuestion(
    "ssq-nausea",
    "How nauseous do you feel?",
    "3",
    new Dictionary<string, string> {
        { "scale", "1-7" },
        { "timepoint", "post-exposure" }
    }
);

// Question linked to a specific user
XRXPManager.Recorder.AddQuestion(
    "comfort-level",
    "Rate your comfort level",
    "7",
    "participant-042"
);

// Standalone question (no session)
XRXPManager.Recorder.AddStandaloneQuestion(
    "demographics-age",
    "What is your age group?",
    "25-34",
    "user-042"
);

QuestionId Best Practices

The questionId is a developer-defined identifier that enables cross-session and cross-user analysis:

// Use consistent IDs for the same question across sessions
XRXPManager.Recorder.AddQuestion("ssq-nausea", "How nauseous do you feel?", "2");
XRXPManager.Recorder.AddQuestion("ssq-discomfort", "How uncomfortable do you feel?", "3");
XRXPManager.Recorder.AddQuestion("experience-rating", "Rate your experience", "8");

// Avoid: different IDs for the same question
// XRXPManager.Recorder.AddQuestion("nausea-q1", ...); // Don't do this for the same question

Session-linked vs Standalone

  • Session-linked: Use AddQuestion() during active sessions. The question is automatically linked to the current session.
  • Standalone: Use AddStandaloneQuestion() for questionnaires outside of session recording (e.g., pre-screening surveys).
// During session - automatically linked
XRXPManager.Recorder.StartSession("Experiment run");
XRXPManager.Recorder.AddQuestion("experience-rating", "Rate your experience", "8"); // session-linked
XRXPManager.Recorder.StopSession();

// Outside session - standalone
XRXPManager.Recorder.AddStandaloneQuestion(
    "screening-vision",
    "Do you have normal vision?",
    "yes",
    "user-042"
);

Media events

Record files produced during the experiment (screenshots, audio recordings, documents). Media events are linked to the current session and user.

// From a file path
XRXPManager.Recorder.AddMediaEvent(
    filePath: "/storage/screenshot_001.png",
    mimeType: "image/png",
    name: "Task1_Screenshot",
    duration: 0                          // in ms, relevant for audio/video
);

// From a byte array (auto-saved and uploaded)
byte[] imageBytes = File.ReadAllBytes("capture.png");
XRXPManager.Recorder.AddMediaEvent(
    bytes: imageBytes,
    mimeType: "image/png",
    name: "Task1_Capture",
    duration: 0                          // optional, in ms for audio/video
);

Internal events

Record continuous system data (positions, sensor values). See Tracking data for details.

using XRXP.Recorder.Models;

WorldPosition headPos = new WorldPosition(
    Camera.main.transform.position,
    Camera.main.transform.rotation
);
XRXPManager.Recorder.AddInternalEvent(
    SystemType.WorldPosition, "BodyTracking", "Head", headPos
);

User and Environment properties

Add metadata to the current user or environment after session start.

XRXPManager.Recorder.AddUserProperties(new Dictionary<string, string> {
    { "age", "25" },
    { "experience", "beginner" }
});

XRXPManager.Recorder.AddEnvironmentProperties(new Dictionary<string, string> {
    { "lighting", "dim" },
    { "temperature", "22C" }
});

Utility

// Check how many traces are still pending
int pending = XRXPManager.Recorder.TransfersState();

// Gracefully flush all remaining traces then stop services
XRXPManager.Recorder.EndTracing();

// Query state
bool active = XRXPManager.Recorder.IsRecording();
string sid  = XRXPManager.Recorder.GetSessionId();
string uid  = XRXPManager.Recorder.GetUserId();
string eid  = XRXPManager.Recorder.GetEnvironmentId();

Best practices

  • start the session before important interactions begin
  • keep event naming consistent across the project
  • record discrete actions as log events, not per-frame state
  • use TransfersState() before quitting to ensure no data is lost
  • always stop sessions cleanly in test and production builds