> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.api.odyssey.ml/llms.txt
> Use this file to discover all available pages before exploring further.

# React Hook

> useOdyssey hook for managing Odyssey client lifecycle in React.

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

## Usage

```typescript theme={null}
import { useOdyssey } from '@odysseyml/odyssey/react';

function useOdyssey(options: UseOdysseyOptions): OdysseyClient
```

## Parameters

| Parameter          | Type                                                             | Required | Description          |
| ------------------ | ---------------------------------------------------------------- | -------- | -------------------- |
| `options.apiKey`   | `string`                                                         | Yes      | Your Odyssey API key |
| `options.handlers` | [`UseOdysseyHandlers`](/sdk/javascript/types#useodysseyhandlers) | No       | Event handlers       |

## Return Value

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

### Methods

| Method          | Type                                                         | Description                                                                    |
| --------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------ |
| `connect`       | `() => Promise<MediaStream>`                                 | Connect to a session (returns MediaStream when connected, throws on failure)   |
| `disconnect`    | `() => void`                                                 | Disconnect 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: HTMLVideoElement \| null) => HTMLVideoElement \| null` | Attach stream to video element                                                 |

### State

| Property        | Type                                                         | Description                                                                         |
| --------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
| `status`        | [`ConnectionStatus`](/sdk/javascript/types#connectionstatus) | Current connection status                                                           |
| `error`         | `string \| null`                                             | Current error message                                                               |
| `isConnected`   | `boolean`                                                    | Whether connected and ready                                                         |
| `mediaStream`   | `MediaStream \| null`                                        | Video/audio stream                                                                  |
| `sessionId`     | `string \| null`                                             | Current session ID                                                                  |
| `broadcastInfo` | `BroadcastInfo \| null`                                      | Broadcast info for spectators (if broadcast enabled), or null when not broadcasting |

## Basic Example

```tsx theme={null}
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

```tsx theme={null}
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

<Note>
  The hook automatically manages cleanup when the component unmounts, but it's good practice to explicitly call `disconnect()` in your cleanup function.
</Note>

* 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

```tsx theme={null}
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`.

```tsx theme={null}
import { Odyssey } from "@odysseyml/odyssey";

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

videoElement.srcObject = spectator.stream;

spectator.onDisconnect(() => {
 console.log("Broadcast ended");
});
```

<Note>
  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.
</Note>
