diff --git a/.well-known/ard.json b/.well-known/ard.json index 7e6e09b..ed8d91a 100644 --- a/.well-known/ard.json +++ b/.well-known/ard.json @@ -3,9 +3,9 @@ { "identifier": "urn:air:vyla.cc:service:stream-api", "displayName": "Vyla Stream API", - "type": "text/html", - "url": "https://vyla.cc/stream-api/overview", - "description": "Self-hosted HTTP/SSE proxy for stream resolution, subtitle extraction, and download links.", + "type": "application/ai-catalog+json", + "url": "https://vyla.cc/ai-catalog.json", + "description": "Self-hosted HTTP/SSE proxy for progressive stream resolution, subtitle extraction, and direct media downloads.", "representativeQueries": [ "resolve a streaming link for a movie by id", "get subtitles for a tv episode", @@ -15,8 +15,9 @@ "media", "streaming", "sdk", - "self-hosted" + "self-hosted", + "developer-api" ] } ] -} \ No newline at end of file +} diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..e0db006 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,30 @@ +# Vyla Ecosystem Architecture + +> High-level architecture, module boundaries, communication protocols, and design principles of the Vyla Ecosystem. + +## System Topology + +| Ecosystem Layer | Core Technologies | Primary Responsibilities | +| :--- | :--- | :--- | +| **Client Interfaces** | Vyla Home (Tauri v2), Vyla Go (Mobile), Vyla TV (ExoPlayer), Web Players | Render UI, manage local user state, initiate playback via SSE, handle failover queues. | +| **Stream API Gateway** | Node.js Cluster, SSE, HTTP Proxy | Multi-core IPC caching (1,500 entries, 5-min TTL), HLS/AES key rewriting, 120-byte fake image mask stripping. | +| **Vyla SDK Resolver** | `@vyla-entertainment/sdk` (ESM) | 60+ provider scrapers, randomized jitter scheduling, two-phase stream validation, manga scraping with Chrome 131 TLS fingerprinting. | +| **Upstream Hosts** | Third-party CDNs & Video Hosts | Remote video segment delivery, HLS manifest endpoints, subtitle hosts. | + +## Projects Overview + +| Directory / Package | Language / Framework | Purpose | +| :--- | :--- | :--- | +| `Documentation` | HTML5, Vanilla JS, CSS3 | Official developer documentation platform hosted at [vyla.cc](https://vyla.cc). | +| `Stream API` | Node.js (Cluster, HTTP, SSE) | Self-hosted streaming microservice gateway with caching and reverse proxying. | +| `SDK` | Node.js (ESM), TLS Client | Core multi-provider scraping library (`@vyla-entertainment/sdk`). | +| `Home` | Tauri v2, Rust, Node.js Sidecar | Native desktop application for Windows, macOS, and Linux. | +| `TV` | Kotlin, Jetpack Compose, ExoPlayer | Native 10-foot Leanback streaming application for Android TV and Fire TV. | +| `Go` | Tauri v2 Mobile, WebGL Shaders | Native mobile client for Android devices. | + +## Core Architectural Invariants + +1. **Decoupled Resolution**: Client interfaces never scrape providers directly; they interact through `@vyla-entertainment/sdk` or the Stream API gateway. +2. **Progressive Playback**: Streaming links are emitted incrementally over Server-Sent Events (SSE). Players start playback on the first candidate (3-8s) and queue subsequent sources as fallbacks. +3. **Transparent Proxying**: All upstream HLS playlists are rewritten to route segments and AES encryption keys through the local gateway, bypassing browser CORS restrictions and stripping 120-byte image obfuscation masks. +4. **Resilience by Default**: Provider queries are scheduled with randomized jitter and timeout budgets to prevent burst rate limits. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..153b54e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,30 @@ +# Contributing to the Vyla Ecosystem + +Thank you for your interest in contributing to Vyla! We welcome contributions across all ecosystem repositories: frontend applications, developer tooling, documentation, and scraper modules. + +## How to Contribute + +### 1. Contributing Source Scrapers (`SDK`) +The most common contribution is adding or updating a provider scraper in `@vyla-entertainment/sdk`: +- Follow the [Contributing a Source Guide](https://vyla.cc/sdk/vyla-sdk/contributing). +- Author modules inside `src/sources/.js`. +- Ensure your module exports `async function getStream(args)` returning a valid `StreamResult`. +- Register the new source in `src/config.js`. +- Test against known movie (TMDB 155) and TV (TMDB 1396) identifiers. + +### 2. Contributing to Applications +- **Vyla Home**: Native desktop client built on Tauri v2. Review [Home Documentation](https://vyla.cc/products/home). +- **Vyla Go**: Native Android mobile client built on Tauri v2 Mobile. Review [Go Documentation](https://vyla.cc/products/go). +- **Vyla TV**: Android TV Leanback client built with Jetpack Compose & ExoPlayer. Review [TV Documentation](https://vyla.cc/products/tv). + +### 3. Contributing to Documentation +- Edit or add markdown files in the `Documentation` repository. +- Ensure semantic Markdown formatting, accurate code snippets, and WCAG accessibility standards. +- Check that all internal links resolve cleanly. + +## Code Standards & Guidelines + +- **ES Modules**: Write modern, clean ES Module syntax (`import`/`export`). +- **No Secrets**: Never commit private API keys, tokens, or personal server endpoints. +- **Error Handling**: Throw informative, descriptive errors rather than silently swallowing exceptions. +- **Verification**: Ensure all stream candidates pass two-phase verification (`verifyStream` and `verifyPlayable`). diff --git a/FORDEVELOPER/README.md b/FORDEVELOPER/README.md new file mode 100644 index 0000000..0c5b779 --- /dev/null +++ b/FORDEVELOPER/README.md @@ -0,0 +1,5 @@ +Run this whenever you want to compile your changes: + +```bash +npm run build:css +``` \ No newline at end of file diff --git a/GLOSSARY.md b/GLOSSARY.md new file mode 100644 index 0000000..63384bf --- /dev/null +++ b/GLOSSARY.md @@ -0,0 +1,17 @@ +# Vyla Ecosystem Glossary + +Authoritative reference glossary of terms, abbreviations, and architectural patterns used across the Vyla ecosystem. + +- **AES-128 Key Rewriting**: The process in Stream API where HLS encryption key URIs (`#EXT-X-KEY`) are rewritten to route through the local proxy with permissive CORS headers. +- **Cluster Primary**: The master Node.js process in Stream API that manages inter-process IPC, shared LRU caching, and worker lifecycles. +- **Cluster Worker**: A child process in Stream API handling incoming HTTP requests and executing scraper resolution. +- **ExoPlayer (Media3)**: Google's media playback library for Android, powering Vyla TV's native Leanback interface. +- **Fallback Queue**: A client-side array holding secondary video candidates emitted by Stream API to provide instant recovery if the primary stream fails. +- **HLS (HTTP Live Streaming)**: Adaptive streaming protocol using M3U8 playlists and MPEG-TS / fragmented MP4 chunks. +- **HLS.js**: JavaScript library enabling HLS playback in standard HTML5 video elements via Media Source Extensions (MSE). +- **Jitter Staggering**: A randomized delay (0–800ms) injected into scraper requests to prevent concurrent burst requests against upstream CDNs. +- **Manga Module**: A module in `@vyla-entertainment/sdk` powered by `atsu.moe` and `node-tls-client` that bypasses TLS anti-bot protections via Chrome 131 fingerprinting. +- **Server-Sent Events (SSE)**: Standardized persistent HTTP streaming protocol (`text/event-stream`) used by `/movie` and `/tv` to emit metadata and sources progressively. +- **Sidecar Process**: A companion Node.js background process bundled inside Vyla Home (Tauri v2) to supervise local services and file updates. +- **TMDB ID**: The numeric identifier assigned by The Movie Database (themoviedb.org) used across all Vyla movie and television endpoints. +- **TS Segment De-Masking**: The routine in Stream API that strips the first 120 bytes off obfuscated video chunks disguised as fake PNG or JPEG files. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..09a0774 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +## Supported Versions + +Security updates and bug fixes are applied to the latest releases of the Vyla ecosystem projects: + +| Project | Supported Versions | +| :--- | :--- | +| **Vyla Home** | Latest 1.1.x release | +| **Vyla Go** | Latest 1.0.x release | +| **Vyla TV** | Latest 1.0.x release | +| **Stream API** | Latest 2.0.x release | +| **Vyla SDK** | Latest 1.28.x release | + +## Reporting a Vulnerability + +We take the security of our users and infrastructure seriously. If you discover a vulnerability, memory safety issue, or data leak within any Vyla project: + +1. **Do not create a public issue** on GitLab or GitHub. +2. Contact the core maintainers privately via Discord or by emailing our security team. +3. Provide detailed steps to reproduce the issue, including environment details and proof-of-concept scripts where applicable. + +We will review, patch, and publish a coordinated advisory and release promptly. diff --git a/_redirects b/_redirects index b0031a2..0cc0046 100644 --- a/_redirects +++ b/_redirects @@ -1,4 +1,9 @@ -/robots.txt /robots.txt 200 -/ai-catalog.json /ai-catalog.json 200 -/sitemap.xml /sitemap.xml 200 -/* /index.html 200 \ No newline at end of file +/robots.txt /robots.txt 200 +/ai-catalog.json /ai-catalog.json 200 +/sitemap.xml /sitemap.xml 200 +/llms.txt /llms.txt 200 +/llms-full.txt /llms-full.txt 200 +/.well-known/ard.json /.well-known/ard.json 200 +/manifest.webmanifest /manifest.webmanifest 200 +/site.webmanifest /manifest.webmanifest 301 +/* /index.html 200 diff --git a/ai-catalog.json b/ai-catalog.json index 217bc04..452c64e 100644 --- a/ai-catalog.json +++ b/ai-catalog.json @@ -9,25 +9,30 @@ { "identifier": "urn:air:vyla.cc:service:stream-api", "displayName": "Vyla Stream API", - "type": "application/mcp-server-card+json", - "description": "Self-hosted HTTP/SSE proxy wrapping the Vyla SDK for remote stream resolution, subtitles, and downloads.", + "type": "application/ai-catalog+json", + "description": "Self-hosted HTTP/SSE progressive streaming gateway wrapping the Vyla SDK for remote stream resolution, subtitle parsing, and media downloads.", "url": "https://vyla.cc/stream-api/overview", "tags": [ "media", "streaming", - "self-hosted" + "self-hosted", + "sse", + "hls-proxy" ] }, { "identifier": "urn:air:vyla.cc:library:sdk", "displayName": "Vyla SDK", "type": "application/ai-catalog+json", - "description": "Node.js resolver library for stream links, subtitle extraction, and download links.", + "description": "Node.js resolver library aggregating 60+ upstream streaming providers, subtitle tracks, download packages, and manga reading archives.", "url": "https://vyla.cc/sdk", "tags": [ "sdk", - "node.js" + "node.js", + "scraper", + "manga", + "video-resolver" ] } ] -} \ No newline at end of file +} diff --git a/build.mjs b/build.mjs new file mode 100644 index 0000000..933eb74 --- /dev/null +++ b/build.mjs @@ -0,0 +1,5 @@ +import { bundleCss } from './scripts/bundle-css.mjs'; + +console.log('==> Starting Vyla Documentation Build...'); +bundleCss(); +console.log('==> Build completed successfully.'); diff --git a/concepts/providers.md b/concepts/providers.md index 1a552a7..5c8d5aa 100644 --- a/concepts/providers.md +++ b/concepts/providers.md @@ -1,87 +1,117 @@ -# Providers +# Provider Architecture & Mechanics -

How Vyla SDK sources are configured, selected, and verified.

+

How Vyla SDK sources are structured, scheduled, verified, proxied, and cached across the ecosystem.

-Vyla SDK aggregates multiple independent providers. Each has different internal mechanisms, reliability profiles, and timeout characteristics. This page explains the source layer that powers both direct SDK use and the optional Stream API. +## Overview -:::callout-note -Provider availability and performance can vary based on your network environment. When self-hosting, providers may work differently than on residential connections due to IP-based blocking from upstream sources. -::: +At the heart of the Vyla ecosystem is the **Provider Layer**. Rather than relying on a single upstream video host, Vyla SDK aggregates over 60 independent, community-maintained scraping resolvers. + +Each provider operates with distinct upstream mechanics, domain rotations, rate-limiting policies, and security defenses. This document explains how Vyla orchestrates these disparate sources into a unified, high-reliability streaming pipeline. --- -## How Providers Are Used +## Provider Configuration Interface -When your app calls `getStream()`-directly or through Stream API: +In `@vyla-entertainment/sdk`, each provider is declared in `src/config.js` via the `SourceConfig` interface: -1. All active (non-disabled) providers are queried **simultaneously** with per-source random jitter to stagger upstream requests -2. Each provider has its own **timeout**, **retry count**, and **backoff delay** -3. Every returned URL is **verified** - first via a HEAD request to the raw upstream URL, then via a proxied HLS playability check -4. Only URLs that pass both verification steps appear in the response as `source` events - -Because the endpoint is SSE, **verified sources are emitted immediately as each provider resolves** - your player receives them progressively. The stream closes once every provider has either resolved or timed out. - -:::callout-info -Call `sdk.getSources(true)` to inspect enabled providers and `sdk.probeAllSources()` for a live availability signal. If you use Stream API, its `/health` endpoint exposes probes over HTTP. -::: - ---- - -## Provider Configuration - -Each provider is defined with the following properties: - -| Field | Description | -| --- | --- | -| `key` | Internal identifier used in API responses and the `source` field | -| `label` | Human-readable name shown in the `label` field | -| `proxyParam` | Short query parameter used to route proxied requests | -| `timeout` | Maximum time (ms) to wait for a stream URL | -| `sourcesTimeout` | Optional secondary timeout (ms) for an internal source-listing step | -| `jitter` | Max random delay (ms) before the request fires | -| `retries` | Number of retry attempts on failure | -| `skipProxy` | If `true`, stream URLs are returned as-is without proxy wrapping | -| `disabled` | If `true`, the provider is excluded from all requests | - ---- - -## Response Time - -The first `source` event typically arrives within **3–8 seconds**. Subsequent sources stream in as each provider resolves. The stream closes once the **slowest configured provider** times out. - -**Best practice:** always show a loading state, begin playback on the first `source` event, and implement a client-side timeout for the overall stream. See the [Error Handling](/guides/error-handling) guide. - ---- - -## Caching - -Results are cached in-memory with a **5-minute TTL**. The per-source stream cache is keyed by `source_key + tmdb_id + season + episode`. - -``` -CACHE_TTL = 5 minutes +```typescript +export interface SourceConfig { + key: string; // Unique source identifier (e.g. "vidlink", "vixsrc") + label: string; // Human-readable display label + sourceFile: string; // Scraper module filename in src/sources/ + proxyParam: string; // Query parameter used for HLS proxy routing + timeout: number; // Total milliseconds allowed before source abort + sourcesTimeout?: number; // Secondary timeout for multi-server extraction + jitter: number; // Maximum randomized startup delay in ms + retries: number; // Number of automatic retry attempts on error + multiUrl: boolean; // Returns multiple stream candidate URLs if true + skipProxy?: boolean; // If true, returns direct upstream URLs without proxy + skipVerify?: boolean; // If true, bypasses verification checks + verifyHeaders?: boolean; // Injects custom headers into verification probes + disabled?: boolean; // If true, provider is completely excluded + cdnHeaders?: { // Custom headers injected for specific CDN patterns + pattern: RegExp; + headers: { Referer: string; Origin: string; }; + }[]; +} ``` --- -## The HLS Proxy +## The Query Pipeline -All provider stream URLs are wrapped in the server proxy (unless `skipProxy: true`): +When a client requests a title (`getStream()` in SDK or `/movie` / `/tv` in Stream API), the engine executes the following pipeline: -``` -http://YOUR_API_URL/api?url=&=1 -``` - -When your player fetches this path from your self-hosted instance, the server: - -1. Fetches the upstream M3U8 with the correct `Referer`, `Origin`, and any provider-specific headers -2. Rewrites all segment URLs and encryption key URIs in the playlist to route through the same proxy -3. Returns the rewritten playlist with `Access-Control-Allow-Origin: *` +| Pipeline Stage | Operation | Purpose & Verification | +| :--- | :--- | :--- | +| **1. Source Filtering** | `SOURCES.filter(!disabled)` | Excludes disabled scrapers and selects active providers based on configuration. | +| **2. Jitter Staggering** | Sleep between `0` and `cfg.jitter` ms | Disperses outbound connection spikes across time to prevent DDoS protection triggers. | +| **3. Concurrent Scraping** | Parallel async scraper execution | Scrapers parse HTML/JSON endpoints using customized `User-Agent`, `Referer`, and `Origin` headers. | +| **4. Two-Phase Verification** | `verifyStream()` then `verifyPlayable()` | Phase 1 validates HTTP status ($< 400$ via HEAD). Phase 2 parses HLS manifests and verifies segment availability. Dead streams are discarded. | +| **5. Proxy URL Rewriting** | Encodes target URL into `/api?url=...` | Adds proxy flags, sets required playback headers, and prepares segment de-obfuscation. | +| **6. Progressive Emission** | Pushes SSE `source` event | Delivers playable stream candidates to the client player immediately without waiting for slower providers. | --- -## Stream Verification +## Jitter Staggering & Rate-Limit Defense -Before a URL is emitted as a `source` event, it passes two verification steps: +If a client triggers 60 scrapers instantaneously, your client IP address could send hundreds of simultaneous HTTP requests in the same millisecond, triggering upstream Cloudflare or DDoS rate limits. -1. **`verifyStream`** - a HEAD request to the raw upstream URL. Must return HTTP `< 400`. -2. **`verifyPlayable`** - fetches the proxied URL, validates `#EXTM3U`, checks for `#EXTINF` or `#EXT-X-STREAM-INF`, then fetches the first segment. \ No newline at end of file +To mitigate this, Vyla introduces **per-source jitter**: +```javascript +const jitterDelay = Math.floor(Math.random() * (cfg.jitter || 400)); +await new Promise(resolve => setTimeout(resolve, jitterDelay)); +``` +By staggering outbound requests across a 100ms–800ms window, upstream hosts perceive natural traffic patterns, dramatically reducing HTTP 429 (Too Many Requests) errors. + +--- + +## Two-Phase Playback Verification + +Many streaming extractors successfully return a URL, only for that URL to return an HTTP 403 Forbidden, an empty manifest, or a dead video chunk. To protect players from stalling, Vyla validates every candidate through two independent verification stages before emitting it: + +### Phase 1: `verifyStream()` +- Dispatches a lightweight HTTP `HEAD` request to the upstream target URL. +- Enforces custom `User-Agent`, `Referer`, and `Origin` headers. +- Requires HTTP status code `< 400` within 4,000 milliseconds. + +### Phase 2: `verifyPlayable()` +- Fetches the initial playlist bytes through the local proxy. +- Validates the presence of the `#EXTM3U` header tag. +- Checks for stream definitions: + - If a Master Playlist: ensures valid `#EXT-X-STREAM-INF` entries exist. + - If a Media Playlist: ensures valid `#EXTINF` duration tags and playable segment URIs exist. +- Attempts a test read on the first media segment or key URI. +- Only candidates that pass both phases are deemed **verified**. + +--- + +## The Universal HLS Proxy & Byte-Mask Stripper + +Modern streaming hosts often protect their video segments by adding CORS restrictions, token signatures, or binary header masking. Stream API handles this transparently: + +### 1. M3U8 Playlist Rewriting +When an HLS stream is proxied, Stream API fetches the upstream playlist and rewrites every internal URI: +- **AES-128 Key URIs**: `#EXT-X-KEY:METHOD=AES-128,URI="/api?url=&=1"` +- **Video Chunks**: Segment URIs are rewritten to route back through the `/api` proxy endpoint. +- **CORS Headers**: Returned with `Access-Control-Allow-Origin: *` and `Access-Control-Allow-Headers: *`. + +### 2. Segment Byte-Mask Stripping +Certain major CDNs mask MPEG-TS chunks by prepending a 120-byte fake image header (`image/png`, `image/jpeg`, TikTok CDN, or `letsgocdn` signatures) so firewalls treat them as images. + +When Stream API proxies these segments, it inspects the leading bytes and automatically strips the 120-byte image wrapper: +```javascript +const needsStrip = searchParams.has('tt') || STRIP_REGEX.test(cleanUrl); +if (needsStrip && (chunk[0] === 0x89 || chunk[0] === 0xFF || chunk[0] === 0x00)) { + return chunk.subarray(120); // Strips fake header, leaving clean MPEG-TS +} +``` + +--- + +## Inter-Process Caching + +Verified stream results are cached in-memory with a **5-minute Time-To-Live (TTL)**: +- **Cache Key**: `source_key + tmdb_id + season + episode` +- In multi-core cluster deployments, cache entries are coordinated across workers via inter-process communication (IPC) messages (`cache:get`, `cache:hit`, `cache:set`). +- Automatic prune routines run every 30 seconds to evict expired items and enforce maximum cache limits (default: 1,500 entries). diff --git a/guides/error-handling.md b/guides/error-handling.md new file mode 100644 index 0000000..8e77e52 --- /dev/null +++ b/guides/error-handling.md @@ -0,0 +1,112 @@ +# Error Handling & Resilience Guide + +

Strategies, error classification, and recovery patterns for handling network stalls, upstream rate limits, and streaming failures.

+ +## Overview + +Streaming from distributed upstream web providers is inherently dynamic. Upstream CDN nodes rotate domains, regional ISPs implement traffic shaping, and token signatures expire. + +Building a resilient client requires planning for failure at every step. This guide explains how Vyla handles errors internally and how client players should respond. + +--- + +## Error Classification Matrix + +| Error Type | Common Cause | Stream API / SDK Behavior | Recommended Player Action | +| :--- | :--- | :--- | :--- | +| **HTTP 400 Bad Request** | Missing or malformed TMDB ID, invalid season/episode | Returns immediate JSON error payload without querying providers. | Validate input parameters client-side before sending requests. | +| **HTTP 429 Too Many Requests** | Upstream host rate-limiting client IP | SDK provider applies backoff delay and retries up to `retries` limit. | If proxy returns 429, respect the `retry_after` header and back off. | +| **HTTP 502 Bad Gateway** | Upstream provider host is unreachable or timed out | Provider is discarded; other scrapers continue executing. | Fall back to the next available stream candidate in your queue. | +| **SSE Disconnect** | Network hiccup or client tab backgrounded | Connection drops; `EventSource.onerror` fires. | If video is already playing, do not interrupt. Otherwise, reconnect after 2 seconds. | +| **HLS Fatal Media Error** | Segment buffer stall, corrupt chunk, or missing decryption key | Player triggers `Hls.Events.ERROR` with `fatal: true`. | Call `hls.destroy()` and load the next candidate from your fallback queue. | + +--- + +## Client Fallback Queue Strategy + +Always treat streaming candidate resolution as a prioritized queue rather than a single static link: + +### Queue Priority Assignment + +| Candidate Stream | Player State | Fallback Precedence | +| :--- | :--- | :--- | +| **Candidate 1** (`vidlink`) | **Active Playback** | Mounted into HLS.js or ExoPlayer on arrival. | +| **Candidate 2** (`vixsrc`) | **Standby Backup** | Enqueued as Priority #1 fallback stream. | +| **Candidate 3** (`cinecat`) | **Standby Backup** | Enqueued as Priority #2 fallback stream. | + +### Recovery Sequence on Stream Stall +1. **Error Interception**: Player detects a fatal network or decode event (`data.fatal === true`). +2. **Timestamp Preservation**: Record current playback offset (e.g. `currentTime = 142.5s`). +3. **Buffer Teardown**: Call `hls.destroy()` or `player.release()` to release media pipelines and decoders. +4. **Queue Advancement**: Dequeue the next available verified candidate from the standby queue. +5. **Seamless Seek**: Mount the fallback source and seek immediately to the preserved timestamp upon `loadedmetadata`. + +### Implementing Fallback Recovery in JavaScript + +```javascript +function attachHlsPlayer(videoElement, candidate, fallbackQueue) { + const hls = new Hls({ + maxBufferLength: 30, + maxMaxBufferLength: 60, + enableWorker: true + }); + + hls.loadSource(candidate.url); + hls.attachMedia(videoElement); + + hls.on(Hls.Events.ERROR, (event, data) => { + if (!data.fatal) return; + + console.warn(`Encountered fatal error on ${candidate.source}:`, data.type); + + switch (data.type) { + case Hls.ErrorTypes.NETWORK_ERROR: + console.log("Attempting network recovery..."); + hls.startLoad(); + break; + case Hls.ErrorTypes.MEDIA_ERROR: + console.log("Attempting media recovery..."); + hls.recoverMediaError(); + break; + default: + hls.destroy(); + if (fallbackQueue.length > 0) { + const nextCandidate = fallbackQueue.shift(); + const lastTime = videoElement.currentTime; + console.warn(`Switching to fallback candidate: ${nextCandidate.label}`); + + attachHlsPlayer(videoElement, nextCandidate, fallbackQueue); + videoElement.addEventListener('loadedmetadata', () => { + videoElement.currentTime = lastTime; + videoElement.play(); + }, { once: true }); + } else { + showErrorOverlay("All available streaming providers have failed."); + } + break; + } + }); + + return hls; +} +``` + +--- + +## Client-Side Timeout Budgets + +Never leave users hanging on an indefinite spinner. Implement a two-tiered timeout budget: + +1. **Initial Candidate Timeout (8–10 seconds)**: If no `type: "source"` event has arrived within 8 seconds, update the UI to indicate extended searching. +2. **Hard Timeout (20 seconds)**: If the stream has not completed or emitted a viable candidate within 20 seconds: + - Call `eventSource.close()`. + - Render a helpful empty-state UI with a "Retry" button or an option to change the server URL. + +--- + +## Decoupled Subtitle Fallback + +Subtitles originate from independent subtitle repositories (e.g. OpenSubtitles, SubDL). If a subtitle track fails to load: +- The video playback must continue uninterrupted. +- Log a warning in the console. +- Offer the viewer an in-player menu to toggle alternative subtitle providers or upload a local `.srt`/`.vtt` file. diff --git a/guides/integrations.md b/guides/integrations.md new file mode 100644 index 0000000..7aa1539 --- /dev/null +++ b/guides/integrations.md @@ -0,0 +1,213 @@ +# Client Integration Guide + +

Frontend and native player implementation patterns for integrating Vyla Stream API with HLS.js, React, Vue, Android ExoPlayer, and iOS AVPlayer.

+ +## Overview + +Stream API exposes standard HTTP and Server-Sent Events (SSE) interfaces, allowing it to integrate with any modern video player library. This guide provides copyable, production-ready integration patterns across multiple platforms and frameworks. + +--- + +## Pattern 1: React Player Hook + +Below is a complete custom React hook (`useVylaStream`) managing SSE event consumption, fallback candidate queuing, and playback state: + +```tsx +import { useState, useEffect, useRef } from 'react'; + +interface StreamSource { + source: string; + label: string; + url: string; + quality: string; + type: 'hls' | 'mp4'; +} + +interface StreamMeta { + title: string; + year: number; + poster: string; +} + +export function useVylaStream(apiBase: string, tmdbId: number, season?: number, episode?: number) { + const [meta, setMeta] = useState(null); + const [activeSource, setActiveSource] = useState(null); + const [subtitles, setSubtitles] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + const fallbackQueue = useRef([]); + const eventSourceRef = useRef(null); + + useEffect(() => { + setIsLoading(true); + setError(null); + fallbackQueue.current = []; + + const endpoint = season != null && episode != null + ? `${apiBase}/tv?id=${tmdbId}&season=${season}&episode=${episode}` + : `${apiBase}/movie?id=${tmdbId}`; + + const es = new EventSource(endpoint); + eventSourceRef.current = es; + + es.onmessage = (event) => { + const data = JSON.parse(event.data); + + if (data.type === 'meta') { + setMeta(data.meta); + if (data.subtitles) setSubtitles(data.subtitles); + } + else if (data.type === 'source') { + if (!activeSource) { + setActiveSource(data); + setIsLoading(false); + } else { + fallbackQueue.current.push(data); + } + } + else if (data.type === 'done') { + es.close(); + if (!activeSource && fallbackQueue.current.length === 0) { + setError('No available streaming sources found.'); + setIsLoading(false); + } + } + }; + + es.onerror = () => { + es.close(); + if (!activeSource) { + setError('Failed to connect to Stream API.'); + setIsLoading(false); + } + }; + + return () => { + es.close(); + }; + }, [apiBase, tmdbId, season, episode]); + + const triggerNextFallback = () => { + if (fallbackQueue.current.length > 0) { + const next = fallbackQueue.current.shift()!; + console.warn(`Switching to fallback candidate: ${next.label}`); + setActiveSource(next); + } else { + setError('All provider stream candidates exhausted.'); + } + }; + + return { meta, activeSource, subtitles, isLoading, error, triggerNextFallback }; +} +``` + +--- + +## Pattern 2: Vue 3 Composable Component + +```vue + + + +``` + +--- + +## Pattern 3: Android Native (Kotlin & ExoPlayer) + +In Android applications, connect to the Stream API proxy using Google's `MediaItem`: + +```kotlin +import androidx.media3.common.MediaItem +import androidx.media3.common.MimeTypes +import androidx.media3.exoplayer.ExoPlayer + +fun playStream(player: ExoPlayer, proxiedHlsUrl: String) { + val mediaItem = MediaItem.Builder() + .setUri(proxiedHlsUrl) + .setMimeType(MimeTypes.APPLICATION_M3U8) + .build() + + player.setMediaItem(mediaItem) + player.prepare() + player.playWhenReady = true +} +``` + +--- + +## Pattern 4: iOS / macOS Native (Swift & AVPlayer) + +Because iOS AVPlayer natively handles HLS streams and respects cookies/redirects, assigning the proxied URL is straightforward: + +```swift +import AVKit + +func playVylaStream(proxiedUrlString: String) { + guard let url = URL(string: proxiedUrlString) else { return } + let player = AVPlayer(url: url) + let playerViewController = AVPlayerViewController() + playerViewController.player = player + + present(playerViewController, animated: true) { + player.play() + } +} +``` diff --git a/home.txt b/home.txt index 8276bbd..52bc417 100644 --- a/home.txt +++ b/home.txt @@ -90,7 +90,7 @@ gap: 10px; } - .btn-primary { + .btn-primary { display: inline-flex; align-items: center; gap: 6px; @@ -353,41 +353,34 @@
- - + +
- -
- - + +
- Vyla + Vyla Entertainment Logo

Vyla Entertainment

-

A community-built media project focused on freedom, privacy, and giving people - control over their own experience.

+

A community-built media project focused on freedom, privacy, and giving people control over their own experience.

-
@@ -399,10 +392,9 @@
- -
- + +