Skip to main content
The useOdyssey hook provides a convenient way to manage the Odyssey client lifecycle and state in React applications.

Usage

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

function useOdyssey(options: UseOdysseyOptions): OdysseyClient

Parameters

ParameterTypeRequiredDescription
options.apiKeystringYesYour Odyssey API key
options.handlersUseOdysseyHandlersNoEvent handlers

Return Value

The hook returns an OdysseyClient object with the following properties and methods:

Methods

MethodTypeDescription
connect() => Promise<MediaStream>Connect to a session (returns MediaStream when connected, throws on failure)
disconnect() => voidDisconnect from current session
startStream(options?: StartStreamOptions) => Promise<string>Start interactive stream. Supports broadcast: true to enable shared playback
interact(options: InteractOptions) => Promise<string>Send interaction prompt
endStream() => Promise<void>End current stream
attachToVideo`(el: HTMLVideoElementnull) => HTMLVideoElementnull`Attach stream to video element

State

PropertyTypeDescription
statusConnectionStatusCurrent connection status
errorstring nullCurrent error message
isConnectedbooleanWhether connected and ready
mediaStreamMediaStream| nullVideo/audio stream
sessionIdstring nullCurrent session ID
broadcastInfoBroadcastInfo nullBroadcast info for spectators (if broadcast enabled), or null when not broadcasting

Basic Example

import { useOdyssey } from '@odysseyml/odyssey/react';
import { useEffect, useRef } from 'react';

function VideoPlayer() {
  const videoRef = useRef<HTMLVideoElement>(null);

  const odyssey = useOdyssey({
    apiKey: 'ody_your_api_key_here',
    handlers: {
      onConnected: (mediaStream) => {
        if (videoRef.current) {
          videoRef.current.srcObject = mediaStream;
        }
      },
      onError: (error, fatal) => {
        console.error('Error:', error.message, 'Fatal:', fatal);
      },
      onStreamStarted: (id) => console.log('Started:', id),
      onInteractAcknowledged: (prompt) => console.log('Ack:', prompt),
    },
  });

  useEffect(() => {
    odyssey.connect()
      .then(stream => console.log('Connected with stream:', stream.id))
      .catch(err => console.error('Connection failed:', err.message));

    return () => odyssey.disconnect();  
  }, []);

  return (
    <div>
      <video ref={videoRef} autoPlay playsInline muted />
      <p>Status: {odyssey.status}</p>
      {odyssey.error && <p>Error: {odyssey.error}</p>}
      <button onClick={() => odyssey.startStream({ prompt: 'A cat' })}>
        Start
      </button>
      <button onClick={() => odyssey.interact({ prompt: 'Pet the cat' })}>
        Interact
      </button>
    </div>
  );
}

Complete Example

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

function App() {
  const videoRef = useRef<HTMLVideoElement>(null);
  const [prompt, setPrompt] = useState('');

  const odyssey = useOdyssey({
    apiKey: 'ody_your_api_key_here',
    handlers: {
      onConnected: (mediaStream) => {
        if (videoRef.current) {
          videoRef.current.srcObject = mediaStream;
          videoRef.current.play();
        }
      },
      onDisconnected: () => {
        if (videoRef.current) {
          videoRef.current.srcObject = null;
        }
      },
      onError: (error, fatal) => {
        console.error('Error:', error.message, 'Fatal:', fatal);
      },
      onStreamStarted: (streamId) => {
        console.log('Stream started:', streamId);
      },
      onInteractAcknowledged: (ackPrompt) => {
        console.log('Interaction acknowledged:', ackPrompt);
      },
      onStreamError: (reason, message) => {
        console.error('Stream error:', reason, message);
      },
    },
  });

  useEffect(() => {
    odyssey.connect()
      .then(stream => console.log('Connected with stream:', stream.id))
      .catch(err => console.error('Connection failed:', err.message));
    return () => odyssey.disconnect();
  }, []);

  const handleStart = async () => {
    await odyssey.startStream({ prompt: 'A cat', portrait: true });
  };

  const handleInteract = async () => {
    if (prompt.trim()) {
      await odyssey.interact({ prompt });
      setPrompt('');
    }
  };

  const handleEnd = async () => {
    await odyssey.endStream();
  };

  return (
    <div>
      <video ref={videoRef} autoPlay playsInline muted />

      <div>
        <p>Status: {odyssey.status}</p>
        {odyssey.error && <p style={{ color: 'red' }}>{odyssey.error}</p>}
      </div>

      <div>
        <button onClick={handleStart} disabled={!odyssey.isConnected}>
          Start Stream
        </button>

        <input
          value={prompt}
          onChange={(e) => setPrompt(e.target.value)}
          placeholder="Enter interaction prompt..."
        />
        <button onClick={handleInteract} disabled={!odyssey.isConnected}>
          Send
        </button>

        <button onClick={handleEnd} disabled={!odyssey.isConnected}>
          End Stream
        </button>
      </div>
    </div>
  );
}

Tips

The hook automatically manages cleanup when the component unmounts, but it’s good practice to explicitly call disconnect() in your cleanup function.
  • Always call connect() in a useEffect hook
  • Use the returned status and error properties to show connection state to users
  • The isConnected property is useful for disabling buttons until the connection is ready

Broadcast

The React hook supports Broadcast mode, allowing a single running stream to be shared with multiple connected clients. To enable Broadcast, pass broadcast: true when starting a stream. Playback details will be delivered through the onBroadcastReady handler.

Streamer Example

import { useOdyssey } from "@odysseyml/odyssey/react";

export function BroadcastExample() {
 const odyssey = useOdyssey({
   apiKey: process.env.NEXT_PUBLIC_ODYSSEY_API_KEY!,
   handlers: {
     onConnected: () => {
       console.log("Connected");
     },
     onBroadcastReady: ({ webrtcUrl, spectatorToken, hlsUrl }) => {
       console.log("Broadcast ready");
       console.log("WebRTC URL:", webrtcUrl);
       console.log("Spectator Token:", spectatorToken);
       console.log("HLS URL:", hlsUrl);
     },
     onError: (error, fatal) => {
       console.error("Error:", error, "Fatal:", fatal);
     },
   },
 });

 const startBroadcast = async () => {
   await odyssey.connect();

   await odyssey.startStream({
     prompt: "A glowing alien ruin at dusk",
     broadcast: true,
   });
 };

 return (
   <button onClick={startBroadcast}>
     Start Broadcast
   </button>
 );
}

Spectator Example

To connect as a spectator, use the WebRTC URL and spectator token provided by onBroadcastReady.
import { Odyssey } from "@odysseyml/odyssey";

const spectator = await Odyssey.connectToStream(
 webrtcUrl,
 spectatorToken
);

videoElement.srcObject = spectator.stream;

spectator.onDisconnect(() => {
 console.log("Broadcast ended");
});
Broadcast provides shared playback of a single running stream.If your application allows multiple users to send prompts to the same stream, that behavior is implemented through a custom orchestration layer in your application. Broadcast itself does not coordinate or arbitrate user input.