# 📻 TinyRadioFm Documentation

`TinyRadioFm` is a powerful, deterministic, and seed-based radio management system. It is designed to handle complex playback sequences, including music and voice playlists, scheduled timeline mutations, and custom content injections, all while maintaining absolute reproducibility through a mathematical seed. 🎯

> **Beta Status:** This engine is currently in beta.

---

## ✨ Key Features

* **🎯 Deterministic Playback:** Uses a `Mulberry32` PRNG to ensure that the same seed always produces the exact same radio sequence.
* **🎵 Dual-Stream Logic:** Manages separate playlists for `music` and `voice`, with configurable interaction rules (e.g., playing voices after music).
* **📅 Scheduled Mutations:** Schedule tasks to `add`, `remove`, or `move` items in the playlist at specific future timestamps.
* **⚡ Custom Injections:** Inject "Custom Positions" into the timeline that interrupt the standard cycle.
* **🧠 Smart Queuing:** Intelligent logic to prevent audio overlaps by automatically adjusting timestamps to the end of the currently playing content.
* **💾 State Persistence:** Full export/import capabilities, including automatic conversion of Blob URLs to Base64 for easy storage.
* **🧹 Memory Management:** Proactive cleanup of Blob URLs and event listeners to prevent memory leaks, with configurable optimization for media and pictures.

---

## ⚙️ Configuration

The behavior of the engine is controlled via the `RadioConfig` object.

| Property | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `mode` | `'playlist' \| 'random'` | `'playlist'` | Sequence logic for the music playlist. |
| `voiceMode` | `'playlist' \| 'random'` | `'playlist'` | Sequence logic for the voice playlist. |
| `silenceDuration` | `number` | `0` | Gap in milliseconds between tracks. |
| `queryLimit` | `number` | `100000` | Safety lock for maximum items processed during queries. |
| `voiceAfterMusic` | `boolean` | `true` | If `true`, voice messages play after music tracks. |
| `voiceMin` | `number` | `0` | Minimum number of voice messages to play in a cycle. |
| `voiceMax` | `number` | `1` | Maximum number of voice messages to play in a cycle. |
| `voiceWeight` | `number` | `1` | Weight used to calculate the probability of a voice block occurring ($P = W / (W + 1)$). |
| `musicMinConsecutive`| `number` | `0` | Min times a music track must repeat consecutively. |
| `musicMaxConsecutive`| `number` | `0` | Max consecutive music repeats (`-1` = unlimited, `0` = no repeats). |
| `voiceMinConsecutive`| `number` | `0` | Min times a voice track must repeat consecutively. |
| `voiceMaxConsecutive`| `number` | `0` | Max consecutive voice repeats (`-1` = unlimited, `0` = no repeats). |

---

## 🛠️ Core API Reference

### 🏗️ Static Methods

#### `static parseContent(source, defaultMetadata, metadata, parseFile, callbacks, convertBase64toBlob)`
Prepares a `MediaContent` object by extracting metadata from an audio source.
* **Parameters:**
    * `source`: A URL string or an existing `HTMLMediaElement`.
    * `defaultMetadata`: Optional base metadata.
    * `metadata`: Optional manual overrides.
    * `parseFile`: Internal helper for file parsing.
    * `callbacks`: Object containing `onProgress` and `onError` handlers.
    * `convertBase64toBlob`: If true, the image content will be converted directly into a high-performance Blob URL.
* **Returns:** `Promise<MediaContent>`
* **Throws:** `MediaLoadingError`

#### `static get/set unknownArtist(value)`
Gets or sets the identifier used when an artist cannot be determined.
* **Value:** A `string` or a `function` that returns a `string`.
* **Throws:** `TypeError` if the value is not a string or a function.

### 📋 Properties (Getters & Setters)

#### **System State & Lifecycle**
* `destroyed`: Returns `true` if the instance has been destroyed.
* `size`: Total count of all content items (music + voice).
* `seed`: Gets/Sets the core randomness seed.
* `anchorDate`: Gets the absolute timestamp used as the timeline anchor.
* `cycleCacheSize`: Returns the number of items currently stored in the cycle cache.

#### **Playlist Access (Returns Deep Clones)**
* `musicList`: Returns a deep clone of the music playlist.
* `voiceList`: Returns a deep clone of the voice playlist.
* `allList`: Returns a deep clone of the combined music and voice playlists.
* `customPositions`: Returns a deep clone of the custom position injections.
* `scheduledTasks`: Returns a deep clone of the pending scheduled tasks.

#### **Counters**
* `musicSize`: Total number of items in the music playlist.
* `voiceSize`: Total number of items in the voice playlist.
* `customPosSize`: Number of active custom position injections.
* `tasksSize`: Number of pending scheduled tasks.

#### **Configuration & Optimization**
* `config`: Gets/Sets the current `RadioConfig` object.
* `optimizeMediaMemory`: Gets/Sets whether Blob URLs for media should be converted to Base64 during export.
* `optimizePictureMemory`: Gets/Sets whether Blob URLs for pictures should be converted to Base64 during export.

### 📥 Content Management

#### `add(type, data, smartQueue = true)`
Adds new content to the engine immediately.
* **Parameters:**
    * `type`: `'music' | 'voice' | 'custom'`.
    * `data`: The `MediaContent` object (must include `id` and `duration`).
    * `smartQueue`: If `true`, delays insertion until the end of the content playing at that timestamp (primarily for `custom`).
* **Emits:** `contentAdded`

#### `remove(id)`
Instantly removes content by its unique ID across all lists, custom positions, and pending tasks. It also revokes associated Blob URLs to free memory.
* **Parameters:** `id` (string)
* **Emits:** `contentRemoved`

### 📅 Task & Config Management

#### `scheduleTask(timestamp, action, type, payload, smartQueue = true)`
Schedules a modification to the playlists at a specific point in time.
* **Parameters:**
    * `timestamp`: Target epoch timestamp (ms).
    * `action`: `'add' | 'remove' | 'move'`.
    * `type`: `'music' | 'voice'`.
    * `payload`: Data required for the action (`MediaContent` for add, `string` for remove, or `ScheduledMovePayload` for move).
    * `smartQueue`: If `true`, aligns the task to the end of the current event.
* **Emits:** `taskScheduled`

#### `setConfig(config)`
Performs a partial or full update of the engine configuration.
* **Parameters:** `config` (Partial or full `RadioConfig` object).
* **Emits:** `configChanged`

### 🔍 Timeline & Querying

#### `getCurrentEvent()`
Retrieves the exact event currently playing at the system's current time.
* **Returns:** `RadioEvent | null`

#### `queryTimeline(targetDate, limit = 10)`
Predicts upcoming events from a specific date forward using a virtual sandbox to prevent state mutation.
* **Parameters:**
    * `targetDate`: Starting epoch timestamp.
    * `limit`: Maximum number of events to return.
* **Returns:** `RadioEvent[]`
* **Throws:** `TypeError` or `RangeError` if limit is invalid.

#### `searchCustomPositions()`
Returns a shallow copy of all active custom positions currently injected into the timeline.
* **Returns:** `CustomPosition[]`

### 💾 Persistence & Lifecycle

#### `async exportState()`
Exports the entire radio state (playlists, tasks, seed, config) as a JSON string. Automatically converts all image Blob URLs to **Base64** for easy storage.
* **Returns:** `Promise<string>`

#### `importState(json)`
Hydrates the engine with a previously exported state.
* **Parameters:** `json` (A JSON string or a state object).
* **Emits:** `stateImported`

#### `destroy(destroyThumbs = true)`
Performs a complete cleanup. It wipes all lists, clears the cache, removes all listeners, and revokes all Blob URLs to free up system memory.
* **Parameters:** `destroyThumbs` (boolean) - Whether to revoke image Blob URLs.
* **Emits:** `destroyed`

---

## 📊 Data Models (Summary)

### `RadioEvent` 📡
The standardized object returned when querying the timeline.
* `id`, `title`, `artist`, `url`, `duration`
* `absoluteStart`, `absoluteEnd`
* `elapsedTime`, `remainingTime`
* `progress` (0 to 1)
* `isCustom` (boolean)

### `ScheduledTask` 📝
An instruction to change the radio state.
* `timestamp`: Execution time.
* `action`: `'add' | 'remove' | 'move'`.
* `type`: `'music' | 'voice'`.
* `payload`: Action-specific data (`MediaContent`, `string`, or `ScheduledMovePayload`).

### `CustomPosition` 📍
Represents content injected at a specific absolute timestamp.
* `content`: The `MediaContent` object.
* `intendedTimestamp`: The target absolute timestamp.
* `originalTimestamp`: The timestamp preserved for intelligent repositioning.

### `ScheduledMovePayload` 🚚
Data required to relocate an existing item within a playlist.
* `id`: Content ID to move.
* `newIndex`: The target index in the playlist.

### `CycleBlock` 🔄
A structural block representing a single full iteration of the radio sequence.
* `items`: Items belonging to this cycle.
* `duration`: Total duration of the cycle block in ms.
* `lastMusicId`: ID of the last music played in this block.
* `musicConsecutiveCount`: Current consecutive count for music.
* `lastVoiceId`: ID of the last voice played in this block.
* `voiceConsecutiveCount`: Current consecutive count for voice.
