Free now2sdk Core is completely free — no license key required   See what's included →
SDK Modules Docs Release Notes Pricing GitHub
Get the SDK →

LReplay Reference Manual & Instant Replay Guide

The LReplay class is the high-performance multi-channel instant replay and slow-motion playout engine of the now2sdk framework. Because it acts as both a downstream stream listener (LSink) and an active playout stream source (LObject), it supports simultaneous raw A/V ingestion and slow-motion playout rendering.

LReplay writes incoming video frames to custom binary container formats using high-bitrate MJPEG (Intra-only) compression, enabling instant $O(1)$ seeks, jog-stepping, reverse plays, and fast exports without transcoding.


1. Dual Ingest-Playout Architecture

graph TD
    subgraph Raw Ingestion Phase (LSink)
        A[Live Video Source] -->|pushVideoFrame| B(MJPEG Encoding)
        B -->|Licensing check| C{Licensed?}
        C -->|No| D[Apply Watermark & 120s limit]
        C -->|Yes| E[Write to _v.bin]
        E --> F[Register FrameIndex offset in RAM]
        
        G[Live Audio Source] -->|pushAudioFrame| H[Write PCM to _a.bin]
        H --> I[Register AudioIndex offset in RAM]
    end
    
    subgraph Playback & Navigation Phase (LObject)
        J[playbackLoop Thread] -->|Read FrameIndex| K{Mode?}
        K -->|Live Mode| L[Read latest frame index]
        K -->|Trim Mode| M[Navigate using Pos / Jog Steps]
        
        L --> N[Seek offset on disk & read MJPEG chunk]
        M --> N
        N --> O(MJPEG Decoding)
        O --> P[Scale / Convert to YUVA420P]
        P --> Q[Call distributeVideoFrame to Sinks]
        
        J -->|Read AudioIndex| R{Speed == 1.0?}
        R -->|Yes| S[Fseek & read PCM chunk]
        S --> T[Call distributeAudioFrame to Sinks]
    end

Ingestion & Indexing Details:


2. API Reference & Public Methods

📌 Licensing Controls

bool setLicense(const std::string& licenseKey);

Registers a license key for the replay instance. Returns true if valid.

bool isLicensed() const;

Checks if the replay module is licensed on the host system.


📌 Recording & Ingest Setup

void setRecordPath(const std::string& directory, const std::string& prefix = "replay");

Configures the directory and name prefix for recording files (e.g. path/replay_v.bin and path/replay_a.bin).

void setExportPath(const std::string& directory, const std::string& prefix = "export");

Configures the directory and file prefix for exported video clips.

void setVideoFormat(const videoFormatProps& props);

Sets target resolution, frame rate, and colorspace specifications.

void setAudioFormat(const audioFormatProps& props);

Sets target audio format sample rates and channel layout.

bool startRecording();

Creates .bin files, initializes encoders, starts tracking timestamp offsets, and begins writing incoming A/V streams. Returns true if successful.

void stopRecording();

Stops writing streams, flushes encoder caches, and closes write file descriptors.

void clearRecording();

Deletes physical .bin temp files from the disk and resets internal indexing vectors.


📌 Playback & Navigation Controls

void play();

Starts playout timeline rendering at the current speed factor.

void pause();

Pauses playout on the current frame.

void setModeLive();

Switches playout to live monitoring mode, rendering frames as soon as they are written to disk.

void setModeTrim();

Switches playout to trim/navigational mode, allowing manual timeline seeks.

void setSpeed(double speed);

Configures playback speed. Supports slow-motion and reverse seeks (e.g. 1.0 for normal, 0.5 for half-speed, -1.0 for reverse).

void posSet(int frameIndex);

Seeks directly to a specific frame number.

void posSetMs(double ms);

Seeks to a position in milliseconds relative to the start of the recording.

void posSetTC(const std::string& tc);

Seeks to a position using a target SMPTE timecode string (HH:MM:SS:FF). Search is performed backwards from the most recent frame.

void stepForward(int frames = 1);
void stepBackward(int frames = 1);

Steps the playback head forward or backward by a specific frame count.


📌 Playout Status Queries

int getTotalFrames();

Returns the total number of video frames recorded in the active session.

double getDurationMs();

Returns the total recorded timeline duration in milliseconds.

int getCurrentFrame();

Returns the index of the frame currently being rendered.

double getPosMs();

Returns the current playhead position in milliseconds.

std::string getPosTC();

Returns the SMPTE timecode string (HH:MM:SS:FF) of the current frame.

void statusGet(int& status);

Returns: 1 if playing, 2 if recording (but paused/trim playout), 0 if idle.


📌 Zero-Transcoding Export

bool exportRange(int inFrame, int outFrame, const std::string& outFilePath = "");

Muxes recorded MJPEG/PCM frames between inFrame and outFrame directly into a QuickTime MOV file without re-encoding. If outFilePath is empty, it generates a file automatically using the export prefix and timestamp.

bool exportRangeMs(double inMs, double outMs, const std::string& outFilePath = "");

Muxes recorded frames using millisecond boundaries.


3. Properties & Configuration Options

All options listed below are configured at runtime via the setProps(const std::string& key, const std::string& value) interface:

Key Value Type Default Value Description
quality int "86" MJPEG compression quality percentage (1 to 100). High quality requires more disk storage but reduces compression artifacts.
timecode bool "false" Toggles timecode track creation/rendering.
timecodeSource int "0" Selects timecode timing clock source:
0: Current local system clock timecode.
1: Inherits metadata timecode from the input source.
2: Offsets timecode using the value set in timecodeStart.
timecodeStart string "01:00:00:00" Starting timecode offset value used when timecodeSource = 2 (format: HH:MM:SS:FF).
name string "" Configures a unique suffix string appended to .bin file names on disk. Useful for preventing file write conflicts when running multiple LReplay instances.
audio.preview bool "true" Toggles audio playout during playback. Playout is only enabled during 1.0x forward speed or Live mode.

4. Multi-Channel Ingest & Playout Replay Example

This Qt C++ window dashboard implementation (adapted from the Replay Example) shows how to configure LReplay alongside LLive and LPreview widgets. It demonstrates connecting live video captures, toggling instant recording loops, managing playback speeds, jog-stepping, and triggering fast range exports.

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "LLive.h"
#include "LPreview.h"
#include "LReplay.h"
#include <QDir>
#include <QMessageBox>
#include <QTimer>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      ui(new Ui::MainWindow),
      m_live(new LLive()),
      m_replay(new LReplay()),
      m_livePreview(new LPreview()),
      m_replayPreview(new LPreview())
{
    ui->setupUi(this);

    // 1. Setup Live Camera Stream
    m_live->setProps("gpu", "true");
    m_live->setProps("timecode", "true");

    // Connect Live source monitor
    m_livePreview->previewEnable(ui->livePreviewWidget, true, true);
    m_livePreview->previewObject(m_live);

    // 2. Setup Instant Replay Engine
    // Configure directories to current working path
    m_replay->setRecordPath(QDir::currentPath().toStdString());
    m_replay->setExportPath(QDir::currentPath().toStdString(), "replay_clip");
    
    // Set recording properties
    m_replay->setProps("quality", "85");
    m_replay->setProps("timecode", "true");
    m_replay->setProps("timecodeSource", "0"); // System clock source
    m_replay->setProps("name", "REPLAY_CH1");

    // Connect replay playout monitor
    m_replayPreview->previewEnable(ui->replayPreviewWidget, true, true);
    m_replayPreview->previewObject(m_replay);

    // IMPORTANT: Attach replay engine as a sink to the live stream!
    m_live->addSink(m_replay);

    // 3. Connect navigation button events
    connect(ui->liveBtn, &QPushButton::clicked, this, &MainWindow::onLiveClicked);
    connect(ui->recordBtn, &QPushButton::clicked, this, &MainWindow::onRecordClicked);
    connect(ui->playPauseBtn, &QPushButton::clicked, this, &MainWindow::onPlayPauseClicked);
    connect(ui->forwardBtn, &QPushButton::clicked, this, &MainWindow::onForwardClicked);
    connect(ui->backwardBtn, &QPushButton::clicked, this, &MainWindow::onBackwardClicked);
    connect(ui->exportBtn, &QPushButton::clicked, this, &MainWindow::onExportClicked);
    
    // Connect speed sliders (Slow-Mo controls)
    connect(ui->speedSlider, &QSlider::valueChanged, this, &MainWindow::onSpeedChanged);

    // Start UI update timer at 100ms
    m_timer = new QTimer(this);
    connect(m_timer, &QTimer::timeout, this, &MainWindow::onUpdateUI);
    m_timer->start(100);
}

MainWindow::~MainWindow() {
    m_live->Stop();
    m_replay->stopRecording();
    delete m_timer;
    delete m_livePreview;
    delete m_replayPreview;
    delete m_replay;
    delete m_live;
    delete ui;
}

// ─── Step 1: Ingest Live Source ───
void MainWindow::onLiveClicked() {
    int status = 0;
    m_live->statusGet(status);
    if (status != 1) {
        if (m_live->Start()) {
            ui->liveBtn->setText("Stop Live Ingest");
        }
    } else {
        m_live->Stop();
        ui->liveBtn->setText("Start Live Ingest");
    }
}

// ─── Step 2: Toggle Ingest Recording ───
void MainWindow::onRecordClicked() {
    int status = 0;
    m_replay->statusGet(status);
    
    if (status != 2) { // Not recording/trim mode
        if (m_replay->startRecording()) {
            ui->recordBtn->setText("🛑 STOP RECORD");
            ui->recordBtn->setStyleSheet("background-color: #dc2626; color: white;");
        }
    } else {
        m_replay->stopRecording();
        ui->recordBtn->setText("⚪ START RECORD");
        ui->recordBtn->setStyleSheet("");
    }
}

// ─── Step 3: Toggle Playout Modes (Live vs Trim Seeks) ───
void MainWindow::onPlayPauseClicked() {
    int status = 0;
    m_replay->statusGet(status);
    
    if (status == 1) { // Playout active, pause it
        m_replay->pause();
        ui->playPauseBtn->setText("Play Playout");
    } else {
        // Resume playout in standard speed
        m_replay->setSpeed(1.0);
        m_replay->play();
        ui->playPauseBtn->setText("⏸ PAUSE PLAYOUT");
    }
}

void MainWindow::onSpeedChanged(int value) {
    // Map slider to playback speeds: e.g. 50 -> 0.5x, 100 -> 1.0x, -100 -> -1.0x (Reverse)
    double targetSpeed = (double)value / 100.0;
    m_replay->setSpeed(targetSpeed);
    m_replay->play();
}

// ─── Step 4: Jog-Step Frame Controls ───
void MainWindow::onForwardClicked() {
    m_replay->stepForward(1); // Step forward 1 frame
}

void MainWindow::onBackwardClicked() {
    m_replay->stepBackward(1); // Step backward 1 frame
}

// ─── Step 5: Fast Zero-Transcoding Trim Export ───
void MainWindow::onExportClicked() {
    double totalDuration = m_replay->getDurationMs();
    if (totalDuration < 5000.0) {
        QMessageBox::warning(this, "Export", "Capture at least 5 seconds of footage before exporting.");
        return;
    }

    // Export the last 4 seconds of recorded footage using millisecond bounds
    double endMs = totalDuration;
    double startMs = endMs - 4000.0;
    if (startMs < 0.0) startMs = 0.0;

    if (m_replay->exportRangeMs(startMs, endMs)) {
        QMessageBox::information(this, "Export Success", 
                                 QString("Clip exported (last 4s) to QuickTime MOV in folder:\n%1")
                                 .arg(QDir::currentPath()));
    } else {
        QMessageBox::critical(this, "Export Failed", "Muxer failed to export selected range.");
    }
}

// ─── Step 6: Monitor Statistics ───
void MainWindow::onUpdateUI() {
    int total = m_replay->getTotalFrames();
    int current = m_replay->getCurrentFrame();
    double duration = m_replay->getDurationMs();
    std::string timecode = m_replay->getPosTC();

    ui->infoLabel->setText(QString("Index Size: %1 frames\nPlayout head: %2\nTimeline: %3 ms\nSMPTE TC: %4")
                           .arg(total)
                           .arg(current)
                           .arg(duration, 0, 'f', 2)
                           .arg(QString::fromStdString(timecode)));
}