Skip to main content
Recording features require v1.0.0 or later.
After a stream session ends, you can retrieve recording artifacts including the full video, events log, thumbnail, and preview.

Capturing the Stream ID

The stream ID is provided in the onStreamStarted callback. Save this ID to retrieve recordings later:
import { Odyssey } from '@odysseyml/odyssey';

const client = new Odyssey({ apiKey: 'ody_your_api_key_here' });

let currentStreamId: string | null = null;

const connected = await client.connect({
  onConnected: (mediaStream) => {
    videoElement.srcObject = mediaStream;
  },
  onStreamStarted: (streamId) => {
    // Save the stream ID for later recording retrieval
    currentStreamId = streamId;
    console.log('Stream started:', streamId);
  },
});

if (connected) {
  await client.startStream({ prompt: 'A cat' });
  // ... interact with the stream ...
  await client.endStream();
}
client.disconnect();

Retrieving a Recording

Use getRecording() with the stream ID to get presigned URLs for the recording artifacts:
if (currentStreamId) {
  const recording = await client.getRecording(currentStreamId);

  if (recording.video_url) {
    // Play back the recorded video
    const playbackVideo = document.getElementById('playback') as HTMLVideoElement;
    playbackVideo.src = recording.video_url;
  }

  if (recording.events_url) {
    // Fetch and parse the events log (JSONL format)
    const response = await fetch(recording.events_url);
    const text = await response.text();
    const events = text.trim().split('\n').map(line => JSON.parse(line));
    console.log('Session events:', events);
  }

  console.log('Duration:', recording.duration_seconds, 'seconds');
  console.log('Frames:', recording.frame_count);
}
getRecording() can be called without an active connection. It only requires a valid API key.

Recording Properties

PropertyTypeDescription
stream_idstringThe stream ID
video_url`stringnull`Presigned URL for full recording (MP4)
events_url`stringnull`Presigned URL for events log (JSONL)
thumbnail_url`stringnull`Presigned URL for thumbnail (JPEG)
preview_url`stringnull`Presigned URL for preview video (MP4)
frame_count`numbernull`Total frames in recording
duration_seconds`numbernull`Recording duration in seconds
URLs are valid for a limited time (typically 1 hour).

Listing All Recordings

Use listStreamRecordings() to get a paginated list of all your recordings:
// Get recent recordings
const { recordings, total } = await client.listStreamRecordings({ limit: 10 });
console.log(`Found ${total} recordings`);

for (const rec of recordings) {
  console.log(`Stream ${rec.stream_id}: ${rec.duration_seconds}s at ${rec.width}x${rec.height}`);
}

// Paginate through results
const page2 = await client.listStreamRecordings({ limit: 10, offset: 10 });

Pagination Options

OptionTypeDefaultDescription
limitnumber50Maximum recordings to return (max 100)
offsetnumber0Number of recordings to skip

React Example

import { useOdyssey } from '@odysseyml/odyssey/react';
import { useState } from 'react';

function RecordingsViewer() {
  const odyssey = useOdyssey({ apiKey: 'ody_your_api_key_here' });
  const [recordings, setRecordings] = useState([]);

  const loadRecordings = async () => {
    const result = await odyssey.listStreamRecordings({ limit: 20 });
    setRecordings(result.recordings);
  };

  const playRecording = async (streamId: string) => {
    const recording = await odyssey.getRecording(streamId);
    if (recording.video_url) {
      window.open(recording.video_url, '_blank');
    }
  };

  return (
    <div>
      <button onClick={loadRecordings}>Load My Recordings</button>
      <ul>
        {recordings.map((rec) => (
          <li key={rec.stream_id}>
            {rec.stream_id} - {rec.duration_seconds}s
            <button onClick={() => playRecording(rec.stream_id)}>Play</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

Complete Workflow Example

This example shows the full workflow: starting a stream, interacting with it, ending it, and then retrieving the recording:
import { Odyssey } from '@odysseyml/odyssey';

const client = new Odyssey({ apiKey: 'ody_your_api_key_here' });

let currentStreamId: string | null = null;

const connected = await client.connect({
  onConnected: (mediaStream) => {
    videoElement.srcObject = mediaStream;
  },
  onStreamStarted: (streamId) => {
    currentStreamId = streamId;
    console.log('Stream started:', streamId);
  },
});

if (connected) {
  // Start an interactive stream
  await client.startStream({ prompt: 'A cat sitting on a windowsill', portrait: true });

  // Send some interactions
  await client.interact({ prompt: 'The cat stretches and yawns' });
  await client.interact({ prompt: 'The cat looks outside at birds' });

  // End the stream
  await client.endStream();
}

client.disconnect();

// Retrieve the recording
if (currentStreamId) {
  const recording = await client.getRecording(currentStreamId);

  if (recording.video_url) {
    console.log('Recording available at:', recording.video_url);
    console.log('Duration:', recording.duration_seconds, 'seconds');
  }
}