npm package: @newgameplusinc/odyssey-stream-sdk
Description: Odyssey Stream SDK - Receives space-live-broadcast via Socket.IO and forwards RTP to Unreal Engine
Author: New Game Plus Inc
Latest version: 1.0.0

# Odyssey Stream SDK

RTP-based video stream forwarder for Unreal Engine. Receives video from MediaSoup server via PlainTransport and forwards to Unreal MediaPlayer.

## Architecture

```
┌──────────────────────────────────────────────────────────────────┐
│                         VUE.JS (Frontend)                         │
│   User clicks "Cast to Space Screens"                            │
│   Emits 'start-space-live-broadcast' via Socket.IO               │
└──────────────────────────────────────────────────────────────────┘
                              │
                              │ WebRTC + Socket.IO
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│                       MEDIASOUP SERVER                            │
│   Receives screen share as Producer                              │
│   Emits 'space-live-broadcast-available'                         │
│   Creates PlainTransport for SDK                                 │
│   Sends RTP packets to SDK                                       │
└──────────────────────────────────────────────────────────────────┘
                              │
                              │ RTP (UDP)
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│                     ODYSSEY STREAM SDK                            │
│                    (Runs alongside Unreal)                        │
│                                                                   │
│   1. Connects via Socket.IO to MediaSoup server                  │
│   2. Listens for 'space-live-broadcast-available' event          │
│   3. Requests PlainTransport from MediaSoup                      │
│   4. Receives RTP packets (no decoding)                          │
│   5. Forwards RTP to localhost:5006                              │
│   6. Generates SDP file for Unreal                               │
│                                                                   │
│   Output: RTP on udp://127.0.0.1:5006 + stream.sdp               │
└──────────────────────────────────────────────────────────────────┘
                              │
                              │ RTP (localhost)
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│                       UNREAL ENGINE                               │
│   MediaPlayer->OpenUrl("file:///path/to/stream.sdp")             │
│   Receives raw RTP video on UDP port 5006                        │
└──────────────────────────────────────────────────────────────────┘
```

## Latency

~100ms end-to-end (Vue → Unreal)

## Setup

### 1. Install Dependencies

```bash
npm install
```

### 2. Build

```bash
npm run build
```

### 3. Run

```bash
npm start <serverUrl> <roomId>
```

**Example:**
```bash
npm start wss://api.odyssey.dev.example.com room-abc-123
```

**Development with ts-node:**
```bash
npm run dev wss://api.odyssey.dev.example.com room-abc-123
```

### Environment-Specific Server URLs

The SDK does **not** auto-detect environment. You must provide the correct `serverUrl` for your environment:

| Environment | Server URL (example) |
|-------------|---------------------|
| Development | `wss://api.odyssey.dev.example.com` |
| Testing | `wss://api.odyssey.testing.example.com` |
| Production | `wss://api.odyssey.example.com` |

> **Note:** Get the actual server URL from your deployment config or the frontend's environment config (`odyssey-v2/src/config/`).

### Where Data Comes From

| Data | Source | How |
|------|--------|-----|
| `serverUrl` | **You provide** | Command line arg or programmatic |
| `roomId` | **You provide** | Command line arg or programmatic |
| `producerId` | **Socket.IO event** | `space-live-broadcast-available` event |
| Stream status | **Socket.IO events** | `available` / `stopped` events |
| RTP params | **Socket.IO response** | `consume-plain-transport` response |

> **No Firebase access required** - all stream info comes from MediaSoup server via Socket.IO.

### Configuration Options

Environment variables for runtime overrides:

| Variable | Default | Description |
|----------|---------|-------------|
| `UNREAL_RTP_HOST` | `127.0.0.1` | Host to forward RTP packets to |
| `UNREAL_RTP_PORT` | `5006` | Port to forward RTP packets to |
| `SDP_OUTPUT_PATH` | `./stream.sdp` | Where to write the SDP file |
| `RTP_LISTEN_PORT_MIN` | `10000` | Minimum port for RTP reception |
| `RTP_LISTEN_PORT_MAX` | `10100` | Maximum port for RTP reception |

## How It Works

1. **SDK connects to MediaSoup server** via Socket.IO
2. **SDK listens for `space-live-broadcast-available`** event
3. **When broadcast starts:**
   - Creates PlainTransport on server
   - Starts receiving RTP packets
   - Forwards RTP to Unreal (localhost:5006)
   - Generates SDP file for Unreal
4. **When broadcast stops** (`space-live-broadcast-stopped` event):
   - Closes MediaSoup resources
   - Stops RTP forwarding
   - Deletes SDP file

## Programmatic Usage

```typescript
import { OdysseyStreamSDK } from 'odyssey-stream-sdk';

const sdk = new OdysseyStreamSDK({
  serverUrl: 'wss://api.odyssey.example.com',
  roomId: 'room-abc-123',
  unrealHost: '127.0.0.1',  // optional
  unrealPort: 5006,          // optional
  sdpOutputPath: './stream.sdp',  // optional
});

sdk.on('streamStarted', ({ sdpPath }) => {
  console.log(`Stream ready! Open in Unreal: ${sdpPath}`);
});

sdk.on('streamStopped', () => {
  console.log('Stream stopped');
});

await sdk.start();
```

## Unreal Integration

In Unreal, open the SDP file with MediaPlayer:

```cpp
UMediaPlayer* Player = NewObject<UMediaPlayer>();
Player->OpenUrl(TEXT("file:///path/to/stream.sdp"));
```

The SDP file location is printed when stream starts.

## Files

```
odyssey-stream-sdk/
├── src/
│   ├── index.ts              # Entry point
│   ├── config/               # RTP/Unreal config
│   ├── core/                 # OdysseyStreamSDK, StreamManager
│   ├── services/
│   │   ├── mediasoup/        # MediaSoupClient
│   │   ├── rtp/              # RtpForwarder (UDP)
│   │   └── sdp/              # SdpGenerator
│   ├── types/                # TypeScript types
│   └── utils/                # Logger, network utils
├── package.json
├── tsconfig.json
└── README.md
```

## Server Requirements

MediaSoup server needs these socket handlers:

| Event | Direction | Purpose |
|-------|-----------|---------|
| `space-live-broadcast-available` | Server → SDK | Notify when broadcast starts |
| `space-live-broadcast-stopped` | Server → SDK | Notify when broadcast stops |
| `get-space-live-broadcast-status` | SDK → Server | Check for active broadcast |
| `create-plain-transport-consumer` | SDK → Server | Create PlainTransport for SDK |
| `consume-plain-transport` | SDK → Server | Create consumer on transport |
| `close-consumer` | SDK → Server | Close consumer |
| `close-plain-transport` | SDK → Server | Close transport |

### Socket.IO Event Payloads

**`space-live-broadcast-available`** (Server → SDK):
```typescript
{
  roomId: string;
  producerId: string;
  producerUserId: string;
}
```

**`space-live-broadcast-stopped`** (Server → SDK):
```typescript
{
  roomId: string;
}
```

**`get-space-live-broadcast-status`** (SDK → Server):
```typescript
// Request:
{ roomId: string }

// Response:
{ 
  isActive: boolean;
  producerId?: string;
  producerUserId?: string;
}
```

**`create-plain-transport-consumer`** (SDK → Server):
```typescript
// Request:
{
  roomId: string;
  clientIp: string;
  rtpPort: number;
  rtcpPort: number;
}

// Response:
{
  success: boolean;
  transportId: string;
  serverRtpPort: number;
  serverRtcpPort: number;
}
```

**`consume-plain-transport`** (SDK → Server):
```typescript
// Request:
{
  roomId: string;
  transportId: string;
}

// Response:
{
  success: boolean;
  consumerId: string;
  producerId: string;
  kind: 'video';
  rtpParameters: object;
  ssrc: number;
  payloadType: number;
}
```

## Troubleshooting

### "Connection timeout" or "Connection error"
- Check serverUrl is correct (wss:// for HTTPS servers)
- Check server is running and accessible
- Check firewall allows WebSocket connections

### "No RTP packets received"
- Check firewall allows UDP on configured ports (10000-10100)
- Check MediaSoup server has PlainTransport handlers
- Verify producer is active with `get-space-live-broadcast-status`

### Unreal not showing video
- Check SDP file is generated (look for log message)
- Check Unreal MediaPlayer opened the SDP file
- Verify RTP packets are being received (check SDK logs)