# Module: streaming_buffer

## Purpose

File-like object backed by a temp file that supports concurrent append (write) and read+seek (read) from separate threads. Designed for true streaming video detection: the HTTP handler appends incoming chunks while the inference thread reads and decodes frames via PyAV — simultaneously, without buffering the entire file in memory.

## Public Interface

### Class: StreamingBuffer

| Method | Signature | Description |
|--------|-----------|-------------|
| `__init__` | `(temp_dir: str \| None = None)` | Creates a temp file in `temp_dir`; opens separate write and read handles |
| `append` | `(data: bytes) -> None` | Writes data to temp file, flushes, notifies waiting readers |
| `close_writer` | `() -> None` | Signals EOF — wakes all blocked readers |
| `read` | `(size: int = -1) -> bytes` | Reads up to `size` bytes; blocks if data not yet available; returns `b""` on EOF |
| `seek` | `(offset: int, whence: int = 0) -> int` | Seeks reader position; SEEK_END blocks until EOF is signaled |
| `tell` | `() -> int` | Returns current reader position |
| `readable` | `() -> bool` | Always returns `True` |
| `seekable` | `() -> bool` | Always returns `True` |
| `writable` | `() -> bool` | Always returns `False` |
| `close` | `() -> None` | Closes both file handles |

### Properties

| Property | Type | Description |
|----------|------|-------------|
| `path` | `str` | Absolute path to the backing temp file |
| `written` | `int` | Total bytes appended so far |

## Internal Logic

### Thread Coordination

Uses `threading.Condition` to synchronize one writer (HTTP handler) and one reader (PyAV/inference thread):

- **append()**: acquires lock → writes to file → flushes → increments `_written` → `notify_all()` → releases lock
- **read(size)**: acquires lock → checks if data available → if not and not EOF, calls `wait()` (releases lock, sleeps) → woken by `notify_all()` → calculates bytes to read → releases lock → reads from file (outside lock)
- **seek(0, 2)** (SEEK_END): acquires lock → if EOF not signaled, calls `wait()` in loop → once EOF, delegates to `_reader.seek(offset, 2)`

The file read itself happens **outside** the lock to avoid holding the lock during I/O.

### File Handle Separation

Two independent file descriptors on the same temp file:
- `_writer` opened with `"wb"` — append-only, used by the HTTP handler
- `_reader` opened with `"rb"` — seekable, used by PyAV

On POSIX systems, writes flushed by one fd are immediately visible to reads on another fd of the same inode (shared kernel page cache). `os.rename()` on the path while the reader fd is open is safe — the fd retains access to the underlying inode.

### SEEK_END Behavior

When PyAV tries to seek to the end of the file (e.g. to find MP4 moov atom), `seek(0, 2)` blocks until `close_writer()` is called. This provides graceful degradation for non-faststart MP4 files: the decoder waits for the full upload, then processes normally. For faststart MP4/MKV/WebM, SEEK_END is never called and frames are decoded immediately.

## Dependencies

- **External**: `os`, `tempfile`, `threading`
- **Internal**: none (leaf module)

## Consumers

- `main` — creates `StreamingBuffer` in `POST /detect/video`, feeds chunks via `append()`, passes buffer to inference

## Data Models

None.

## Configuration

None.

## External Integrations

None.

## Security

None. Temp file permissions follow OS defaults (`tempfile.mkstemp`).

## Tests

- `tests/test_az178_streaming_video.py::TestStreamingBuffer` — sequential write/read, blocking read, EOF, concurrent chunked read/write, seek set, seek end blocking, tell, file persistence, written property, seekable/readable flags
