huge rewrite
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
30
ARCHITECTURE.md
Normal file
@@ -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.
|
||||
30
CONTRIBUTING.md
Normal file
@@ -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/<source_name>.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`).
|
||||
5
FORDEVELOPER/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
Run this whenever you want to compile your changes:
|
||||
|
||||
```bash
|
||||
npm run build:css
|
||||
```
|
||||
17
GLOSSARY.md
Normal file
@@ -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.
|
||||
23
SECURITY.md
Normal file
@@ -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.
|
||||
13
_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
|
||||
/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
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
5
build.mjs
Normal file
@@ -0,0 +1,5 @@
|
||||
import { bundleCss } from './scripts/bundle-css.mjs';
|
||||
|
||||
console.log('==> Starting Vyla Documentation Build...');
|
||||
bundleCss();
|
||||
console.log('==> Build completed successfully.');
|
||||
@@ -1,87 +1,117 @@
|
||||
# Providers
|
||||
# Provider Architecture & Mechanics
|
||||
|
||||
<p class="page-description">How Vyla SDK sources are configured, selected, and verified.</p>
|
||||
<p class="page-description">How Vyla SDK sources are structured, scheduled, verified, proxied, and cached across the ecosystem.</p>
|
||||
|
||||
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=<encoded_stream_url>&<proxyParam>=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.
|
||||
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=<key>&<param>=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).
|
||||
|
||||
112
guides/error-handling.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Error Handling & Resilience Guide
|
||||
|
||||
<p class="page-description">Strategies, error classification, and recovery patterns for handling network stalls, upstream rate limits, and streaming failures.</p>
|
||||
|
||||
## 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.
|
||||
213
guides/integrations.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# Client Integration Guide
|
||||
|
||||
<p class="page-description">Frontend and native player implementation patterns for integrating Vyla Stream API with HLS.js, React, Vue, Android ExoPlayer, and iOS AVPlayer.</p>
|
||||
|
||||
## 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<StreamMeta | null>(null);
|
||||
const [activeSource, setActiveSource] = useState<StreamSource | null>(null);
|
||||
const [subtitles, setSubtitles] = useState<any[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fallbackQueue = useRef<StreamSource[]>([]);
|
||||
const eventSourceRef = useRef<EventSource | null>(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
|
||||
<template>
|
||||
<div class="player-wrapper">
|
||||
<video ref="videoRef" controls playsinline></video>
|
||||
<div v-if="isLoading" class="loading-overlay">Discovering streams...</div>
|
||||
<div v-if="error" class="error-banner">{{ error }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue';
|
||||
import Hls from 'hls.js';
|
||||
|
||||
const props = defineProps<{
|
||||
apiBase: string;
|
||||
tmdbId: number;
|
||||
}>();
|
||||
|
||||
const videoRef = ref<HTMLVideoElement | null>(null);
|
||||
const isLoading = ref(true);
|
||||
const error = ref<string | null>(null);
|
||||
let hls: Hls | null = null;
|
||||
let es: EventSource | null = null;
|
||||
|
||||
function loadStream(url: string) {
|
||||
if (!videoRef.value) return;
|
||||
|
||||
if (Hls.isSupported()) {
|
||||
if (hls) hls.destroy();
|
||||
hls = new Hls({ maxBufferLength: 30 });
|
||||
hls.loadSource(url);
|
||||
hls.attachMedia(videoRef.value);
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, () => videoRef.value?.play());
|
||||
} else if (videoRef.value.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
videoRef.value.src = url;
|
||||
videoRef.value.play();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
es = new EventSource(`${props.apiBase}/movie?id=${props.tmdbId}`);
|
||||
let firstSourceReceived = false;
|
||||
|
||||
es.onmessage = (e) => {
|
||||
const data = JSON.parse(e.data);
|
||||
if (data.type === 'source' && !firstSourceReceived) {
|
||||
firstSourceReceived = true;
|
||||
isLoading.value = false;
|
||||
loadStream(data.url);
|
||||
} else if (data.type === 'done') {
|
||||
es?.close();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
es?.close();
|
||||
hls?.destroy();
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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()
|
||||
}
|
||||
}
|
||||
```
|
||||
92
home.txt
@@ -90,7 +90,7 @@
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
.btn-primary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
@@ -353,41 +353,34 @@
|
||||
|
||||
<div class="home-container">
|
||||
<div class="body-blurred-backdrop">
|
||||
<video class="body-blurred-video" autoplay muted loop playsinline
|
||||
src=""></video>
|
||||
<img class="body-blurred-image" alt="" />
|
||||
<video class="body-blurred-video" autoplay muted loop playsinline src=""></video>
|
||||
<img class="body-blurred-image" alt="" width="1920" height="1080" />
|
||||
</div>
|
||||
|
||||
<link rel="preload" href="" as="video" type="video/mp4"
|
||||
crossorigin="anonymous">
|
||||
|
||||
<header class="home-header">
|
||||
<video class="header-video" autoplay muted loop playsinline preload="auto"
|
||||
src=""></video>
|
||||
<img class="header-image" alt="" />
|
||||
<video class="header-video" autoplay muted loop playsinline preload="none" src=""></video>
|
||||
<img class="header-image" alt="" width="1920" height="1080" />
|
||||
|
||||
<div class="header-tint"></div>
|
||||
|
||||
<div class="header-content">
|
||||
<div class="header-logo-badge">
|
||||
<img src="/src/assets/images/logo.png" alt="Vyla" />
|
||||
<img src="/src/assets/images/logo.png" alt="Vyla Entertainment Logo" width="58" height="58" decoding="async" />
|
||||
</div>
|
||||
<h1 class="header-title">Vyla Entertainment</h1>
|
||||
<p class="header-desc">A community-built media project focused on freedom, privacy, and giving people
|
||||
control over their own experience.</p>
|
||||
<p class="header-desc">A community-built media project focused on freedom, privacy, and giving people control over their own experience.</p>
|
||||
|
||||
<div class="header-actions">
|
||||
<div class="header-actions">
|
||||
<div class="header-actions-row">
|
||||
<a href="/start-here/introduction" class="btn-primary">
|
||||
<a href="/start-here/introduction" class="btn-primary" aria-label="Get Started with Vyla Ecosystem">
|
||||
Get Started
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M5 12h14M12 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="/sdk" class="btn-secondary">Vyla SDK</a>
|
||||
<a href="/sdk" class="btn-secondary" aria-label="Explore Vyla SDK Documentation">Vyla SDK</a>
|
||||
</div>
|
||||
<a href="/stream-api/overview" class="btn-secondary">Stream API</a>
|
||||
<a href="/stream-api/overview" class="btn-secondary" aria-label="Explore Stream API Documentation">Stream API</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -399,10 +392,9 @@
|
||||
</div>
|
||||
|
||||
<div class="vyla-grid">
|
||||
<a href="/products/home" class="feature-box">
|
||||
<div class="box-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<a href="/products/home" class="feature-box" aria-label="Explore Vyla Home desktop application for Windows, macOS, and Linux">
|
||||
<div class="box-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="3" width="20" height="14" rx="2" />
|
||||
<line x1="8" y1="21" x2="16" y2="21" />
|
||||
<line x1="12" y1="17" x2="12" y2="21" />
|
||||
@@ -413,30 +405,27 @@
|
||||
<div class="box-meta">Explore Desktop →</div>
|
||||
</a>
|
||||
|
||||
<a href="/products/go" class="feature-box">
|
||||
<div class="box-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
<a href="/products/go" class="feature-box" aria-label="Explore Vyla Go mobile application for Android">
|
||||
<div class="box-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="5" y="2" width="14" height="20" rx="2" ry="2" />
|
||||
<line x1="12" y1="18" x2="12.01" y2="18" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="box-title">Vyla Go</div>
|
||||
<p class="box-desc">Android mobile application managing local service lifecycles and background
|
||||
playback.</p>
|
||||
<p class="box-desc">Android mobile application managing local service lifecycles and background playback.</p>
|
||||
<div class="box-meta">Explore Mobile →</div>
|
||||
</a>
|
||||
|
||||
<a href="/products/tv" class="feature-box">
|
||||
<div class="box-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<a href="/products/tv" class="feature-box" aria-label="Explore Vyla TV application for Android TV and Fire TV">
|
||||
<div class="box-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="7" width="20" height="15" rx="2" ry="2" />
|
||||
<polyline points="17 2 12 7 7 2" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="box-title">Vyla TV</div>
|
||||
<p class="box-desc">Leanback streaming client for Android TV built with Jetpack Compose and ExoPlayer.
|
||||
</p>
|
||||
<p class="box-desc">Leanback streaming client for Android TV built with Jetpack Compose and ExoPlayer.</p>
|
||||
<div class="box-meta">Explore Android TV →</div>
|
||||
</a>
|
||||
</div>
|
||||
@@ -449,48 +438,41 @@
|
||||
</div>
|
||||
|
||||
<div class="vyla-grid">
|
||||
<a href="/sdk" class="feature-box">
|
||||
<div class="box-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<a href="/sdk" class="feature-box" aria-label="Explore Vyla SDK scraping library and API reference">
|
||||
<div class="box-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="16 18 22 12 16 6" />
|
||||
<polyline points="8 6 2 12 8 18" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="box-title">Vyla SDK</div>
|
||||
<p class="box-desc">Node.js scraper resolver package providing resilient provider extraction and
|
||||
subtitle parsing.</p>
|
||||
<p class="box-desc">Node.js scraper resolver package providing resilient provider extraction and subtitle parsing.</p>
|
||||
<div class="box-meta">View SDK Reference →</div>
|
||||
</a>
|
||||
|
||||
<a href="/stream-api/overview" class="feature-box">
|
||||
<div class="box-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<a href="/stream-api/overview" class="feature-box" aria-label="Explore Stream API progressive SSE streaming gateway">
|
||||
<div class="box-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" />
|
||||
<path d="M12 12v9" />
|
||||
<path d="m8 17 4 4 4-4" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="box-title">Stream API</div>
|
||||
<p class="box-desc">Self-hosted HTTP and SSE proxy emitting real-time discovery events directly to
|
||||
players.</p>
|
||||
<p class="box-desc">Self-hosted HTTP and SSE proxy emitting real-time discovery events directly to players.</p>
|
||||
<div class="box-meta">View API Endpoints →</div>
|
||||
</a>
|
||||
|
||||
<a href="/concepts/providers" class="feature-box">
|
||||
<div class="box-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<a href="/concepts/providers" class="feature-box" aria-label="Explore Provider architecture, jitter buffering, and verification">
|
||||
<div class="box-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<line x1="2" y1="12" x2="22" y2="12" />
|
||||
<path
|
||||
d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
|
||||
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="box-title">Provider Architecture</div>
|
||||
<p class="box-desc">Jitter buffering, failover priority arrays, proxy validation, and verification
|
||||
mechanics.</p>
|
||||
<p class="box-desc">Jitter buffering, failover priority arrays, proxy validation, and verification mechanics.</p>
|
||||
<div class="box-meta">Read Mechanics →</div>
|
||||
</a>
|
||||
</div>
|
||||
@@ -508,4 +490,4 @@
|
||||
window.addEventListener('scroll', onScroll, { passive: true });
|
||||
onScroll();
|
||||
})();
|
||||
</script>
|
||||
</script>
|
||||
|
||||
837
index.html
@@ -3,11 +3,15 @@
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<meta name="theme-color" content="#070709" />
|
||||
<meta name="color-scheme" content="dark" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="Vyla Docs" />
|
||||
<meta name="application-name" content="Vyla Docs" />
|
||||
|
||||
<title>Vyla Entertainment - Vyla</title>
|
||||
<title>Vyla Entertainment - Free & Decentralized Media Platform</title>
|
||||
<meta name="description"
|
||||
content="Vyla Entertainment is a community-built, decentralized media platform and developer ecosystem. Featuring native client apps for Desktop, Mobile, and Android TV, a scraping SDK, and a self-hosted Stream API." />
|
||||
<meta name="keywords"
|
||||
@@ -18,78 +22,113 @@
|
||||
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="Vyla Entertainment - Free & Decentralized Media Platform" />
|
||||
<meta property="og:title" content="Vyla Entertainment - Free & Decentralized Media Platform" />
|
||||
<meta property="og:description"
|
||||
content="A community-built media ecosystem focused on freedom, privacy, and control. Featuring native Desktop, Android, and TV apps backed by modular developer tools." />
|
||||
<meta property="og:url" content="https://vyla.cc/" />
|
||||
<meta property="og:site_name" content="Vyla Entertainment" />
|
||||
<meta property="og:image" content="/src/assets/images/devices.png" />
|
||||
<meta property="og:image" content="https://vyla.cc/src/assets/images/thumbnail.png" />
|
||||
<meta property="og:image:secure_url" content="https://vyla.cc/src/assets/images/thumbnail.png" />
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:image:alt" content="Vyla Entertainment Ecosystem" />
|
||||
<meta property="og:image:alt" content="Vyla Entertainment Ecosystem Overview" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="Vyla Entertainment - Free & Decentralized Media Platform" />
|
||||
<meta name="twitter:title" content="Vyla Entertainment - Free & Decentralized Media Platform" />
|
||||
<meta name="twitter:description"
|
||||
content="Explore Vyla Home, Go, TV, and the developer SDK. Built for privacy, performance, and complete streaming control." />
|
||||
<meta name="twitter:image" content="/src/assets/images/devices.png" />
|
||||
<meta name="twitter:image" content="https://vyla.cc/src/assets/images/thumbnail.png" />
|
||||
<meta name="twitter:image:alt" content="Vyla Entertainment Ecosystem Overview" />
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@graph": [
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@graph": [
|
||||
{
|
||||
"@type": "WebSite",
|
||||
"@id": "/#website",
|
||||
"url": "/",
|
||||
"name": "Vyla Entertainment",
|
||||
"description": "A community-built media project focused on freedom, privacy, and decentralized control.",
|
||||
"publisher": {
|
||||
"@id": "/#organization"
|
||||
}
|
||||
"@type": "WebSite",
|
||||
"@id": "https://vyla.cc/#website",
|
||||
"url": "https://vyla.cc/",
|
||||
"name": "Vyla Entertainment",
|
||||
"description": "A community-built media project focused on freedom, privacy, and decentralized control.",
|
||||
"publisher": {
|
||||
"@id": "https://vyla.cc/#organization"
|
||||
},
|
||||
"potentialAction": {
|
||||
"@type": "SearchAction",
|
||||
"target": {
|
||||
"@type": "EntryPoint",
|
||||
"urlTemplate": "https://vyla.cc/?q={search_term_string}"
|
||||
},
|
||||
"query-input": "required name=search_term_string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "Organization",
|
||||
"@id": "https://vyla.cc/#organization",
|
||||
"name": "Vyla Entertainment",
|
||||
"url": "https://vyla.cc/",
|
||||
"logo": {
|
||||
"@type": "ImageObject",
|
||||
"url": "https://vyla.cc/src/assets/images/logo.png",
|
||||
"width": 512,
|
||||
"height": 512
|
||||
},
|
||||
"image": "https://vyla.cc/src/assets/images/thumbnail.png",
|
||||
"sameAs": [
|
||||
"https://gitlab.com/vyla-entertainment/",
|
||||
"https://discord.gg/5eqRhKeywG",
|
||||
"https://www.reddit.com/r/Vyla/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"@type": "SoftwareApplication",
|
||||
"@id": "https://vyla.cc/#software",
|
||||
"name": "Vyla",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux, Android, Android TV",
|
||||
"downloadUrl": "https://gitlab.com/vyla-entertainment/",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "BreadcrumbList",
|
||||
"@id": "https://vyla.cc/#breadcrumbs",
|
||||
"itemListElement": [
|
||||
{
|
||||
"@type": "Organization",
|
||||
"@id": "/#organization",
|
||||
"name": "Vyla Entertainment",
|
||||
"url": "/",
|
||||
"logo": "/src/assets/images/logo.png",
|
||||
"sameAs": [
|
||||
"https://gitlab.com/vyla-entertainment/",
|
||||
"https://discord.gg/5eqRhKeywG"
|
||||
]
|
||||
},
|
||||
{
|
||||
"@type": "SoftwareApplication",
|
||||
"@id": "/#software",
|
||||
"name": "Vyla",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux, Android, Android TV",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
"@type": "ListItem",
|
||||
"position": 1,
|
||||
"name": "Home",
|
||||
"item": "https://vyla.cc/"
|
||||
}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="preload" href="/src/css/roots.css" as="style" />
|
||||
<link rel="stylesheet" href="/src/css/roots.css" />
|
||||
<link rel="stylesheet" href="/src/css/layout.css" />
|
||||
<link rel="stylesheet" href="/src/css/prose.css" />
|
||||
<link rel="stylesheet" href="/src/css/components.css" />
|
||||
<link rel="stylesheet" href="/src/css/themes.css" />
|
||||
<link rel="stylesheet" href="/src/css/overlay.css" />
|
||||
<link rel="stylesheet" href="/src/css/features.css" />
|
||||
<link rel="stylesheet" href="/src/css/highlight.css" />
|
||||
<link rel="stylesheet" href="/src/css/404.css" />
|
||||
]
|
||||
}
|
||||
</script>
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/src/assets/images/favicon-16x16.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/src/assets/images/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="/src/assets/images/favicon-96x96.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/src/assets/images/apple-touch-icon.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
|
||||
media="print" onload="this.media='all'" />
|
||||
<noscript>
|
||||
<link rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" />
|
||||
</noscript>
|
||||
<link rel="stylesheet" href="/src/css/bundle.min.css" />
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="preload" as="image" href="/src/assets/images/wallpaper-media/tree-mobile.webp" type="image/webp"
|
||||
media="(max-width: 768px)" fetchpriority="high" />
|
||||
<link rel="ard" href="/.well-known/ard.json" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script>
|
||||
<script
|
||||
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script>
|
||||
<script defer
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
@@ -104,92 +143,692 @@
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body class="is-home">
|
||||
<video class="video-bg" autoplay muted loop playsinline></video>
|
||||
<div id="banner">
|
||||
<span>
|
||||
Rights holders can request source removal - <a href="/misc/project/dmca">DMCA policy</a>.
|
||||
</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss banner">×</button>
|
||||
</div>
|
||||
<div id="header-container">
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/src/assets/images/logo.png" alt="Vyla" id="brand-logo" width="28"
|
||||
height="28" decoding="async" /></a>
|
||||
<a class="brand" href="/" aria-label="Vyla Homepage"><img src="/src/assets/images/logo.png"
|
||||
alt="Vyla Logo" id="brand-logo" width="28" height="28" decoding="async" /></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="settings-trigger" type="button" onclick="openSettings()"
|
||||
aria-label="Personalize"></button>
|
||||
aria-label="Personalize preferences"></button>
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)"
|
||||
aria-label="Theme"></button>
|
||||
aria-label="Toggle theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs" id="nav-tabs"></nav>
|
||||
<nav class="tabs" id="nav-tabs" aria-label="Main Navigation"></nav>
|
||||
</header>
|
||||
</div>
|
||||
<div id="layout">
|
||||
<nav id="sidebar"></nav>
|
||||
<nav id="sidebar" aria-label="Documentation Navigation"></nav>
|
||||
<div id="content-wrapper">
|
||||
<div id="content-col">
|
||||
<div id="main-wrap">
|
||||
<main class="prose" id="main-content"></main>
|
||||
<main class="prose" id="main-content">
|
||||
<style>
|
||||
.home-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.home-header {
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: auto;
|
||||
margin-top: calc(var(--header-height) + max(10px, env(safe-area-inset-top)) + 12px);
|
||||
margin-left: calc(-50vw + 50%);
|
||||
margin-right: calc(-50vw + 50%);
|
||||
padding: 40px 0 64px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.header-video,
|
||||
.header-image {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 80%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
z-index: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-top-left-radius: 50px !important;
|
||||
border-top-right-radius: 50px !important;
|
||||
will-change: transform, opacity;
|
||||
backface-visibility: hidden;
|
||||
transform-origin: center top;
|
||||
mask-image: linear-gradient(rgb(0, 0, 0) 0%, rgb(0, 0, 0) 30%, rgba(0, 0, 0, 0.65) 55%, rgba(0, 0, 0, 0.2) 72%, transparent 88%);
|
||||
background-position: center top;
|
||||
transition: opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1), filter 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.header-tint {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
max-width: 680px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.header-logo-badge {
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-logo-badge img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.header-actions-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 10px 22px;
|
||||
border-radius: 100px;
|
||||
background: var(--btn-primary-bg);
|
||||
color: var(--btn-primary-text) !important;
|
||||
font-size: 13.5px;
|
||||
font-weight: 600;
|
||||
text-decoration: none !important;
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
border-radius: 100px;
|
||||
font-size: 13.5px;
|
||||
font-weight: 600;
|
||||
text-decoration: none !important;
|
||||
backdrop-filter: blur(25px) saturate(2);
|
||||
-webkit-backdrop-filter: blur(25px) saturate(2);
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeVideoBg {
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.body-blurred-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
animation: fadeVideoBg linear both;
|
||||
animation-timeline: scroll();
|
||||
animation-range: 0 800px;
|
||||
will-change: opacity;
|
||||
}
|
||||
|
||||
.body-blurred-video,
|
||||
.body-blurred-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
filter: blur(25px);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.body-blurred-video:not([src]),
|
||||
.body-blurred-video[src=""],
|
||||
.body-blurred-image:not([src]),
|
||||
.body-blurred-image[src=""],
|
||||
.header-video:not([src]),
|
||||
.header-video[src=""],
|
||||
.header-image:not([src]),
|
||||
.header-image[src=""] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.content-section {
|
||||
width: 100%;
|
||||
max-width: 1040px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 48px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 14.5px;
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.section-head {
|
||||
margin-bottom: 22px;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.section-head h2 {
|
||||
font-size: 22px !important;
|
||||
font-weight: 700 !important;
|
||||
margin: 0 0 6px !important;
|
||||
color: var(--text) !important;
|
||||
}
|
||||
|
||||
.section-head p {
|
||||
font-size: 14.5px;
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.vyla-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.feature-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 24px;
|
||||
border-radius: 30px;
|
||||
background: rgba(187, 187, 187, 0.6) !important;
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
text-decoration: none !important;
|
||||
color: inherit !important;
|
||||
transition: border-color 0.2s ease, transform 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.feature-box::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.12), transparent 45%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
html.dark .feature-box {
|
||||
background: rgba(25, 25, 25, 0.2) !important;
|
||||
}
|
||||
|
||||
.feature-box:hover {
|
||||
background: rgba(150, 150, 150, 1);
|
||||
}
|
||||
|
||||
.feature-box:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.box-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 12px;
|
||||
background: var(--bg-elevated);
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.box-icon svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.box-title {
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 8px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.box-desc {
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
margin: 0;
|
||||
color: var(--text-muted) !important;
|
||||
}
|
||||
|
||||
.box-meta {
|
||||
margin-top: auto;
|
||||
padding-top: 18px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: var(--text-muted);
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.hero-greeting {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
margin: 0 0 14px;
|
||||
border-radius: 100px;
|
||||
color: var(--text);
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.hero-resume {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--text);
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
#header-container {
|
||||
border-radius: 100px;
|
||||
top: max(6px, env(safe-area-inset-top));
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.header-video,
|
||||
.header-image {
|
||||
border-top-left-radius: 55px !important;
|
||||
border-top-right-radius: 55px !important;
|
||||
width: 97% !important;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (orientation: landscape) and (max-height: 550px) {
|
||||
.home-header {
|
||||
padding: 20px 0 40px;
|
||||
}
|
||||
|
||||
.header-logo-badge {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 26px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 13.5px;
|
||||
margin-bottom: 14px;
|
||||
max-width: 540px;
|
||||
}
|
||||
|
||||
.content-section {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.vyla-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="home-container">
|
||||
<div class="body-blurred-backdrop">
|
||||
<video class="body-blurred-video" autoplay muted loop playsinline src=""></video>
|
||||
<img class="body-blurred-image" alt="" width="1920" height="1080" />
|
||||
</div>
|
||||
|
||||
<header class="home-header">
|
||||
<video class="header-video" autoplay muted loop playsinline preload="none"
|
||||
src=""></video>
|
||||
<img class="header-image" alt="Vyla tree wallpaper backdrop" width="1920" height="1080"
|
||||
src="/src/assets/images/wallpaper-media/tree-mobile.webp" fetchpriority="high"
|
||||
decoding="async" />
|
||||
|
||||
<div class="header-tint"></div>
|
||||
|
||||
<div class="header-content">
|
||||
<div class="header-logo-badge">
|
||||
<img src="/src/assets/images/logo.png" alt="Vyla Entertainment Logo" width="58"
|
||||
height="58" decoding="async" />
|
||||
</div>
|
||||
<h1 class="header-title">Vyla Entertainment</h1>
|
||||
<p class="header-desc">A community-built media project focused on freedom, privacy,
|
||||
and giving people control over their own experience.</p>
|
||||
|
||||
<div class="header-actions">
|
||||
<div class="header-actions-row">
|
||||
<a href="/start-here/introduction" class="btn-primary"
|
||||
aria-label="Get Started with Vyla Ecosystem">
|
||||
Get Started
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2.5" stroke-linecap="round"
|
||||
stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M5 12h14M12 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="/sdk" class="btn-secondary"
|
||||
aria-label="Explore Vyla SDK Documentation">Vyla SDK</a>
|
||||
</div>
|
||||
<a href="/stream-api/overview" class="btn-secondary"
|
||||
aria-label="Explore Stream API Documentation">Stream API</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="content-section">
|
||||
<div class="section-head">
|
||||
<h2>Ecosystem & Products</h2>
|
||||
<p>Native frontends running on desktop, mobile, and television form factors.</p>
|
||||
</div>
|
||||
|
||||
<div class="vyla-grid">
|
||||
<a href="/products/home" class="feature-box"
|
||||
aria-label="Explore Vyla Home desktop application for Windows, macOS, and Linux">
|
||||
<div class="box-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="3" width="20" height="14" rx="2" />
|
||||
<line x1="8" y1="21" x2="16" y2="21" />
|
||||
<line x1="12" y1="17" x2="12" y2="21" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="box-title">Vyla Home</div>
|
||||
<p class="box-desc">Desktop control center and runtime designed for Windows,
|
||||
macOS, and Linux.</p>
|
||||
<div class="box-meta">Explore Desktop →</div>
|
||||
</a>
|
||||
|
||||
<a href="/products/go" class="feature-box"
|
||||
aria-label="Explore Vyla Go mobile application for Android">
|
||||
<div class="box-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="5" y="2" width="14" height="20" rx="2" ry="2" />
|
||||
<line x1="12" y1="18" x2="12.01" y2="18" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="box-title">Vyla Go</div>
|
||||
<p class="box-desc">Android mobile application managing local service lifecycles
|
||||
and background playback.</p>
|
||||
<div class="box-meta">Explore Mobile →</div>
|
||||
</a>
|
||||
|
||||
<a href="/products/tv" class="feature-box"
|
||||
aria-label="Explore Vyla TV application for Android TV and Fire TV">
|
||||
<div class="box-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="7" width="20" height="15" rx="2" ry="2" />
|
||||
<polyline points="17 2 12 7 7 2" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="box-title">Vyla TV</div>
|
||||
<p class="box-desc">Leanback streaming client for Android TV built with Jetpack
|
||||
Compose and ExoPlayer.</p>
|
||||
<div class="box-meta">Explore Android TV →</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-section">
|
||||
<div class="section-head">
|
||||
<h2>Developer Services</h2>
|
||||
<p>Self-hostable tools and scraper libraries for distributed playback resolution.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="vyla-grid">
|
||||
<a href="/sdk" class="feature-box"
|
||||
aria-label="Explore Vyla SDK scraping library and API reference">
|
||||
<div class="box-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round"
|
||||
stroke-linejoin="round"></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path d="M9 8L5 11.6923L9 16M15 8L19 11.6923L15 16"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="box-title">Vyla SDK</div>
|
||||
<p class="box-desc">Node.js scraper resolver package providing resilient
|
||||
provider extraction and subtitle parsing.</p>
|
||||
<div class="box-meta">View SDK Reference →</div>
|
||||
</a>
|
||||
|
||||
<a href="/stream-api/overview" class="feature-box"
|
||||
aria-label="Explore Stream API progressive SSE streaming gateway">
|
||||
<div class="box-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" />
|
||||
<path d="M12 12v9" />
|
||||
<path d="m8 17 4 4 4-4" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="box-title">Stream API</div>
|
||||
<p class="box-desc">Self-hosted HTTP and SSE proxy emitting real-time discovery
|
||||
events directly to players.</p>
|
||||
<div class="box-meta">View API Endpoints →</div>
|
||||
</a>
|
||||
|
||||
<a href="/concepts/providers" class="feature-box"
|
||||
aria-label="Explore Provider architecture, jitter buffering, and verification">
|
||||
<div class="box-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round"
|
||||
stroke-linejoin="round"></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path
|
||||
d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z"
|
||||
stroke="currentColor" stroke-width="2"></path>
|
||||
<path
|
||||
d="M3.5 11H6C7.10457 11 8 11.8954 8 13V13C8 14.1046 8.89543 15 10 15V15C11.1046 15 12 15.8954 12 17V20.5"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round"></path>
|
||||
<path
|
||||
d="M8 4V5C8 6.10457 8.89543 7 10 7H10.1459C11.1699 7 12 7.83011 12 8.8541V8.8541C12 9.55638 12.3968 10.1984 13.0249 10.5125L13.1056 10.5528C13.6686 10.8343 14.3314 10.8343 14.8944 10.5528L14.9751 10.5125C15.6032 10.1984 16 9.55638 16 8.8541V8.8541C16 7.83011 16.8301 7 17.8541 7H19.5"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round"></path>
|
||||
<path d="M16 19.5V17C16 15.8954 16.8954 15 18 15H20"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="box-title">Provider Architecture</div>
|
||||
<p class="box-desc">Jitter buffering, failover priority arrays, proxy
|
||||
validation, and verification mechanics.</p>
|
||||
<div class="box-meta">Read Mechanics →</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const bg = document.querySelector('.body-blurred-backdrop');
|
||||
if (!bg || CSS.supports?.('animation-timeline', 'scroll()')) return;
|
||||
const onScroll = () => {
|
||||
const y = window.scrollY || document.documentElement.scrollTop;
|
||||
bg.style.opacity = Math.max(0, 1 - y / 450);
|
||||
};
|
||||
window.addEventListener('scroll', onScroll, { passive: true });
|
||||
onScroll();
|
||||
})();
|
||||
</script>
|
||||
|
||||
</main>
|
||||
<footer id="site-footer">
|
||||
<div class="footer-nav">
|
||||
<a id="footer-prev" class="footer-link prev" href="#"></a>
|
||||
<a id="footer-next" class="footer-link next" href="#"></a>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p id="footer-note"></p>
|
||||
<div class="footer-socials">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener"
|
||||
aria-label="Discord">
|
||||
<svg class="footer-icon" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994.021-.041.001-.09-.041-.106a13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.929 1.793 8.18 1.793 12.061 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.028zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z" />
|
||||
</svg>
|
||||
|
||||
<div class="footer-grid">
|
||||
<div class="footer-brand-col">
|
||||
<a href="/" class="footer-brand-link" aria-label="Vyla Homepage">
|
||||
<img src="/src/assets/images/logo.png" alt="Vyla Logo" width="24" height="24"
|
||||
loading="lazy" decoding="async" />
|
||||
<span class="footer-brand-name">Vyla</span>
|
||||
</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener"
|
||||
aria-label="GitLab">
|
||||
<svg class="footer-icon" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round">
|
||||
</g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<p class="footer-brand-desc">A community-built, decentralized media platform and
|
||||
developer ecosystem focused on freedom, privacy, and control.</p>
|
||||
<div class="footer-socials">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener"
|
||||
aria-label="Discord Community">
|
||||
<svg class="footer-icon" viewBox="0 0 24 24" fill="currentColor"
|
||||
aria-hidden="true">
|
||||
<path
|
||||
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994.021-.041.001-.09-.041-.106a13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.929 1.793 8.18 1.793 12.061 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.028zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener"
|
||||
aria-label="GitLab Repository">
|
||||
<svg class="footer-icon" viewBox="0 0 16 16" fill="currentColor"
|
||||
aria-hidden="true">
|
||||
<path fill="currentColor"
|
||||
d="M14.975 8.904L14.19 6.55l-1.552-4.67a.268.268 0 00-.255-.18.268.268 0 00-.254.18l-1.552 4.667H5.422L3.87 1.879a.267.267 0 00-.254-.179.267.267 0 00-.254.18l-1.55 4.667-.784 2.357a.515.515 0 00.193.583l6.78 4.812 6.778-4.812a.516.516 0 00.196-.583z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://www.reddit.com/r/Vyla/" target="_blank" rel="noopener"
|
||||
aria-label="Reddit">
|
||||
<svg class="footer-icon" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.702zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.197-2.512-.73a.326.326 0 0 0-.232-.095z" />
|
||||
</svg>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://www.reddit.com/r/Vyla/" target="_blank" rel="noopener"
|
||||
aria-label="Reddit Community">
|
||||
<svg class="footer-icon" viewBox="0 0 24 24" fill="currentColor"
|
||||
aria-hidden="true">
|
||||
<path
|
||||
d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.702zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.197-2.512-.73a.326.326 0 0 0-.232-.095z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-links-col">
|
||||
<div class="footer-col-title">Applications</div>
|
||||
<ul class="footer-col-list">
|
||||
<li><a href="/products/home">Vyla Home (Desktop)</a></li>
|
||||
<li><a href="/products/go">Vyla Go (Android Mobile)</a></li>
|
||||
<li><a href="/products/tv">Vyla TV (Android TV)</a></li>
|
||||
<li><a href="/products/player">Web Player</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-links-col">
|
||||
<div class="footer-col-title">Developers</div>
|
||||
<ul class="footer-col-list">
|
||||
<li><a href="/stream-api/overview">Stream API Gateway</a></li>
|
||||
<li><a href="/sdk/vyla-sdk">Vyla SDK Resolver</a></li>
|
||||
<li><a href="/stream-api/reference/paths">API Endpoints</a></li>
|
||||
<li><a href="/start-here/architecture">Architecture Guide</a></li>
|
||||
<li><a href="/guides/integrations">Player Integration</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-links-col">
|
||||
<div class="footer-col-title">Resources & Legal</div>
|
||||
<ul class="footer-col-list">
|
||||
<li><a href="/resources/faq">FAQ</a></li>
|
||||
<li><a href="/resources/troubleshooting">Troubleshooting</a></li>
|
||||
<li><a href="/resources/glossary">Ecosystem Glossary</a></li>
|
||||
<li><a href="/misc/project/dmca">DMCA Policy</a></li>
|
||||
<li><a href="/misc/project/story">Project Story</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-bottom">
|
||||
<div class="footer-bottom-info">
|
||||
<p id="footer-note">© 2026 Vyla Entertainment. Open-source under MIT & GPL-3.0
|
||||
licenses.</p>
|
||||
<p class="footer-subtext">Free and decentralized media streaming engineered for user
|
||||
privacy.</p>
|
||||
</div>
|
||||
<div class="footer-badges">
|
||||
<a href="https://www.thegreenwebfoundation.org" target="_blank" rel="noopener"
|
||||
class="footer-green-badge"
|
||||
aria-label="Verified green hosting by The Green Web Foundation">
|
||||
<img src="https://app.greenweb.org/api/v3/greencheckimage/vyla.cc?nocache=true"
|
||||
alt="This website runs on green hosting - verified by thegreenwebfoundation.org"
|
||||
width="200" height="95" loading="lazy" decoding="async" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<img src="https://app.greenweb.org/api/v3/greencheckimage/vyla.cc?nocache=true"
|
||||
alt="This website runs on green hosting - verified by thegreenwebfoundation.org"
|
||||
width="200px" height="95px" style="margin-top: 30px;">
|
||||
</footer>
|
||||
</div>
|
||||
<aside id="toc">
|
||||
<aside id="toc" aria-label="Table of Contents">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul id="toc-list"></ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/src/js/main.js"></script>
|
||||
<script src="/src/js/api/contribute.js"></script>
|
||||
<script src="/src/js/api/donations.js"></script>
|
||||
<script src="/src/js/api/download.js"></script>
|
||||
<script src="/src/js/api/release.js"></script>
|
||||
<script src="/src/js/main/settings.js"></script>
|
||||
|
||||
</div>
|
||||
<script defer src="/src/js/main.js"></script>
|
||||
<script defer src="/src/js/api/contribute.js"></script>
|
||||
<script defer src="/src/js/api/donations.js"></script>
|
||||
<script defer src="/src/js/api/download.js"></script>
|
||||
<script defer src="/src/js/api/release.js"></script>
|
||||
<script defer src="/src/js/main/settings.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
3250
llms-full.txt
Normal file
65
llms.txt
@@ -1,44 +1,49 @@
|
||||
--- START OF FILE llms.txt ---
|
||||
# Vyla Entertainment
|
||||
|
||||
> Vyla is a community-built, self-hosted media platform and developer ecosystem focused on user freedom, privacy, and decentralized control. It provides native client applications (Home, Go, TV), a Node.js scraper SDK, and an optional HTTP/SSE Stream API proxy.
|
||||
> Vyla is a community-built, decentralized media ecosystem and developer platform focused on privacy, user sovereignty, and self-hosted streaming infrastructure. It provides native client applications (Home, Go, TV), a Node.js scraper SDK aggregating 60+ upstream providers, and a self-hosted HTTP/SSE Stream API proxy.
|
||||
|
||||
## Overview & Architecture
|
||||
## Architecture & Ecosystem Overview
|
||||
|
||||
- [Introduction](https://vyla.cc/): Community ethos, mission, platforms, and getting started overview.
|
||||
- [Our Architecture](https://vyla.cc/start-here/architecture): System decoupling between local runtimes, provider resolution, and playback delivery.
|
||||
- [Introduction](https://vyla.cc/start-here/introduction): Philosophy, system components, platform support, and getting started overview.
|
||||
- [Architecture & System Design](https://vyla.cc/start-here/architecture): High-level system topology, module boundaries, video delivery pipelines, HLS proxy mechanics, and deployment options.
|
||||
- [Provider Mechanics](https://vyla.cc/concepts/providers): Source configurations, jitter staggering, two-phase stream verification, M3U8 rewriting, and 120-byte TS de-masking.
|
||||
|
||||
## Products
|
||||
## Native Client Applications
|
||||
|
||||
- [Vyla Home](https://vyla.cc/products/home): Desktop control center and application runtime for Windows, macOS, and Linux.
|
||||
- [Vyla Go](https://vyla.cc/products/go): Android mobile client managing local service lifecycles and embedded playback.
|
||||
- [Vyla TV](https://vyla.cc/products/tv): Android TV Leanback streaming client built with Jetpack Compose and ExoPlayer.
|
||||
- [Build a Player](https://vyla.cc/products/player): Architectural guidelines for building resilient client media players.
|
||||
- [Vyla Home (Desktop)](https://vyla.cc/products/home): Desktop control center and runtime for Windows, macOS, and Linux built on Tauri v2 and an embedded Node.js sidecar.
|
||||
- [Vyla Go (Android Mobile)](https://vyla.cc/products/go): Android mobile client with hardware WebGL shaders, automated runtime payload downloads, and background playback.
|
||||
- [Vyla TV (Android TV)](https://vyla.cc/products/tv): Native 10-foot Leanback streaming client built with Jetpack Compose and Google ExoPlayer (Media3).
|
||||
- [Building a Custom Player](https://vyla.cc/products/player): Architectural guidelines, HLS vs direct MP4/MKV handling, and resilient player design rules.
|
||||
|
||||
## Core Concepts
|
||||
## Developer SDK (`@vyla-entertainment/sdk`)
|
||||
|
||||
- [Providers](https://vyla.cc/concepts/providers): Source configurations, jitter, timeout policies, HLS proxy mechanics, and verification pipelines.
|
||||
- [SDK Overview](https://vyla.cc/sdk): Installation, capabilities, module architecture, and initial configuration.
|
||||
- [SDK Quickstart](https://vyla.cc/sdk/vyla-sdk/quickstart): 5-minute setup, resolving movie and TV streams, concurrent queries, and subtitle extraction.
|
||||
- [Complete API Reference](https://vyla.cc/sdk/vyla-sdk/reference): Comprehensive documentation of the VylaSDK class, methods, parameters, and TypeScript interfaces.
|
||||
- [Manga Resolution Module](https://vyla.cc/sdk/vyla-sdk/manga): Title search, chapter listings, and page image extraction via atsu.moe with Chrome 131 TLS fingerprinting.
|
||||
- [Contributing a Source Scraper](https://vyla.cc/sdk/vyla-sdk/contributing): How to author, test, and submit a new provider scraper module to the SDK.
|
||||
|
||||
## Developer SDK
|
||||
## Stream API Gateway
|
||||
|
||||
- [SDK Reference](https://vyla.cc/sdk): Node.js package (@vyla-entertainment/sdk) for direct stream resolution, subtitles, downloads, and health probing.
|
||||
- [SDK Quickstart](https://vyla.cc/sdk/vyla-sdk/quickstart): Rapid installation, provider selection, and server-side integration guide.
|
||||
- [Stream API Overview](https://vyla.cc/stream-api/overview): Self-hosted HTTP/SSE streaming gateway, multi-core cluster supervisor, and shared LRU cache.
|
||||
- [Paths & Playback Events](https://vyla.cc/stream-api/reference/paths): SSE event specifications for `/movie` and `/tv` routes (`meta`, `source`, `done`), plus `/api` proxy query parameters.
|
||||
- [Subtitles & Media Downloads](https://vyla.cc/stream-api/reference/media-data): REST endpoints for multi-language subtitle tracks and direct media download links.
|
||||
- [Operations & Health Diagnostics](https://vyla.cc/stream-api/reference/operations): Automated `/health` endpoint, direct `/test/:source` harness, and `/debug` diagnostic traces.
|
||||
- [API Keys & Authentication](https://vyla.cc/stream-api/authentication): Managing API keys (`pk_`, `sk_`, `pub_`), SQLite database synchronization, CLI tools, and rate limits.
|
||||
- [Production Self-Hosting Guide](https://vyla.cc/stream-api/deployment/self-hosting): Docker containerization, systemd service, PM2, and Nginx reverse proxy configuration with SSE streaming support.
|
||||
|
||||
## Stream API
|
||||
## Guides & Best Practices
|
||||
|
||||
- [Stream API Overview](https://vyla.cc/stream-api/overview): Self-hosted HTTP and SSE progressive playback service.
|
||||
- [Paths & Playback Events](https://vyla.cc/stream-api/reference/paths): SSE event contract for /movie and /tv routes emitting meta, source, and done events.
|
||||
- [Subtitles & Downloads](https://vyla.cc/stream-api/reference/media-data): REST endpoints for subtitle track extraction and direct media downloads.
|
||||
- [Operations & Health](https://vyla.cc/stream-api/reference/operations): /health metrics, /test probes, and safe production troubleshooting.
|
||||
- [Self-Hosting Guide](https://vyla.cc/stream-api/deployment/self-hosting): Node.js deployment, cluster scaling, environment variables, and reverse proxy setup.
|
||||
- [Client Integration Guide](https://vyla.cc/guides/integrations): Ready-to-use player integration patterns for React, Vue 3, Android ExoPlayer, and iOS AVPlayer.
|
||||
- [Error Handling & Resilience](https://vyla.cc/guides/error-handling): Error classification, exponential retry backoff, client fallback priority queues, and network recovery.
|
||||
|
||||
## Guides & Integrations
|
||||
## Resources & Community
|
||||
|
||||
- [Client Integration](https://vyla.cc/guides/integrations): Frontend implementation patterns using HLS.js, React, Vue, ExoPlayer, and AVPlayer.
|
||||
- [Error Handling](https://vyla.cc/guides/error-handling): Handling SSE timeouts, fallback queues, subtitle absence, and retry strategies.
|
||||
|
||||
## Policies & Attribution
|
||||
|
||||
- [Credit](https://vyla.cc/misc/project/credit): Acknowledgments for upstream open-source inspiration including P-Stream, CinePro, and Movieslay.
|
||||
- [Support Us](https://vyla.cc/misc/project/support-us): Support Vyla infrastructure, development, and creator compensation.
|
||||
- [DMCA Policy](https://vyla.cc/misc/project/dmca): DMCA takedown procedures, upstream content aggregation disclosures, and contact points.
|
||||
- [Frequently Asked Questions](https://vyla.cc/resources/faq): Answers to common questions regarding self-hosting, legal policies, and platform compatibility.
|
||||
- [Troubleshooting & Diagnostics](https://vyla.cc/resources/troubleshooting): Step-by-step diagnostic workflows for Windows WebView2, Linux headers, and Android NDK setups.
|
||||
- [Ecosystem Glossary](https://vyla.cc/resources/glossary): Authoritative definitions of ecosystem terminology (SSE, HLS, TS de-masking, Tauri sidecar, etc.).
|
||||
- [Project Story](https://vyla.cc/misc/project/story): Origin story of Vyla from web project to decentralized ecosystem.
|
||||
- [Changelog](https://vyla.cc/misc/project/changelog): Release history and changelog across all projects.
|
||||
- [Credits & Attribution](https://vyla.cc/misc/project/credit): Acknowledgments for upstream open-source inspiration (P-Stream, CinePro).
|
||||
- [Support Us](https://vyla.cc/misc/project/support-us): Community donation channels and cryptocurrency wallet addresses.
|
||||
- [DMCA & Copyright Policy](https://vyla.cc/misc/project/dmca): Upstream aggregation disclosures and DMCA source removal request procedures.
|
||||
|
||||
@@ -1,30 +1,148 @@
|
||||
{
|
||||
"name": "Vyla Entertainment",
|
||||
"short_name": "Vyla",
|
||||
"description": "A community-built media project focused on freedom, privacy, and control.",
|
||||
"start_url": "/",
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#000000",
|
||||
"theme_color": "#000000",
|
||||
"orientation": "any",
|
||||
"categories": [
|
||||
"entertainment",
|
||||
"multimedia",
|
||||
"utilities"
|
||||
],
|
||||
"icons": [
|
||||
"$schema": "https://json.schemastore.org/web-manifest-combined.json",
|
||||
"id": "https://vyla.cc/",
|
||||
"name": "Vyla Ecosystem Documentation",
|
||||
"short_name": "Vyla Docs",
|
||||
"description": "Official developer guides, API references, architecture specifications, and cross-platform clients for the Vyla Ecosystem.",
|
||||
"start_url": "/",
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
"display_override": [
|
||||
"window-controls-overlay",
|
||||
"standalone",
|
||||
"minimal-ui"
|
||||
],
|
||||
"background_color": "#070709",
|
||||
"theme_color": "#070709",
|
||||
"orientation": "any",
|
||||
"lang": "en-US",
|
||||
"dir": "ltr",
|
||||
"categories": [
|
||||
"development",
|
||||
"documentation",
|
||||
"multimedia",
|
||||
"utilities"
|
||||
],
|
||||
"prefer_related_applications": false,
|
||||
"icons": [
|
||||
{
|
||||
"src": "/src/assets/images/favicon-32x32.png",
|
||||
"sizes": "32x32",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/src/assets/images/favicon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/src/assets/images/apple-touch-icon.png",
|
||||
"sizes": "180x180",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/src/assets/images/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/src/assets/images/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/src/assets/images/maskable-icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/src/assets/images/maskable-icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "Stream API Gateway",
|
||||
"short_name": "Stream API",
|
||||
"description": "Explore the self-hosted streaming gateway and HTTP/SSE endpoints",
|
||||
"url": "/stream-api/overview",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/src/assets/images/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/src/assets/images/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
"src": "/src/assets/images/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Vyla SDK Reference",
|
||||
"short_name": "SDK",
|
||||
"description": "Access the complete TypeScript SDK reference and source scrapers",
|
||||
"url": "/sdk/vyla-sdk",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/src/assets/images/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Vyla Home (Desktop)",
|
||||
"short_name": "Vyla Home",
|
||||
"description": "Download and configure the native desktop application",
|
||||
"url": "/products/home",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/src/assets/images/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "API Reference & Paths",
|
||||
"short_name": "Paths",
|
||||
"description": "Examine endpoints for movies, TV series, subtitles, and proxying",
|
||||
"url": "/stream-api/reference/paths",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/src/assets/images/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "/src/assets/images/desktop-preview.png",
|
||||
"sizes": "1280x729",
|
||||
"type": "image/png",
|
||||
"form_factor": "wide",
|
||||
"label": "Vyla Documentation Platform Desktop Interface"
|
||||
},
|
||||
{
|
||||
"src": "/src/assets/images/thumbnail.png",
|
||||
"sizes": "1200x630",
|
||||
"type": "image/png",
|
||||
"form_factor": "wide",
|
||||
"label": "Vyla Ecosystem Overview & Multi-Device Architecture"
|
||||
},
|
||||
{
|
||||
"src": "/src/assets/images/mobile-preview.png",
|
||||
"sizes": "750x1334",
|
||||
"type": "image/png",
|
||||
"form_factor": "narrow",
|
||||
"label": "Vyla Documentation Platform Mobile Interface"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "node build.mjs",
|
||||
"serve": "cd dist && python3 -m http.server 8080"
|
||||
"build:css": "node scripts/bundle-css.mjs",
|
||||
"watch:css": "node scripts/bundle-css.mjs --watch",
|
||||
"serve": "python -m http.server 8080"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
||||
107
products/go.md
@@ -1,17 +1,106 @@
|
||||
# Vyla Go
|
||||
# Vyla Go (Android Mobile)
|
||||
|
||||
<p class="page-description">The Android control center for running the Vyla media stack locally.</p>
|
||||
<p class="page-description">The native Android mobile client for the Vyla ecosystem, combining hardware-accelerated shaders, automated runtime management, and mobile streaming.</p>
|
||||
|
||||
<img class="vyla-banner" src="/src/assets/images/android/site_preview.png" alt="A collage of films and shows available through a Vyla experience" loading="lazy" fetchpriority="low" />
|
||||
<img class="vyla-banner" src="/src/assets/images/android/site_preview.webp" alt="Vyla Go mobile streaming application interface" loading="lazy" decoding="async" fetchpriority="low" />
|
||||
|
||||
**Vyla Go** is the Android application in the Vyla ecosystem. It prepares and runs the local services that power your media experience, then opens the interface in a secure embedded window. It is designed for people who want Vyla on their Android device without managing a separate server process or service lifecycle by hand.
|
||||
## Overview
|
||||
|
||||
**Vyla Go** brings the power of the Vyla media ecosystem directly to Android smartphones and tablets. Built using **Tauri v2 Mobile**, Vyla Go delivers a fluid, responsive touch experience backed by native hardware acceleration and custom WebGL shaders.
|
||||
|
||||
Designed for mobile reliability on varying cellular and Wi-Fi networks, Vyla Go automatically coordinates local resource extraction, service fallbacks, and remote update fetching without requiring constant manual APK re-installations.
|
||||
|
||||
---
|
||||
|
||||
## Downloads
|
||||
|
||||
Vyla Go is available for Android. Get the latest version below:
|
||||
Official APK packages are published on the [Vyla Go GitLab Releases](https://gitlab.com/vyla-entertainment/go/-/releases):
|
||||
|
||||
| Platform | Download |
|
||||
| --- | --- |
|
||||
| Android | [Download: v0.0.0 (.apk)](https://gitlab.com/vyla-entertainment/go/-/releases) |
|
||||
| Package | Target Architectures | Compatibility | Download Link |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Universal APK** | `arm64-v8a`, `armeabi-v7a`, `x86_64` | Android 7.0+ (API 24+) | [Download Universal APK](https://gitlab.com/vyla-entertainment/go/-/releases) |
|
||||
| **ARM64 APK** | `aarch64` | Modern 64-bit Android Devices | [Download ARM64 APK](https://gitlab.com/vyla-entertainment/go/-/releases) |
|
||||
|
||||
Clicking the download button will retrieve the latest `.apk` package for your device.
|
||||
---
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Tauri v2 Mobile Architecture**: Native Rust-backed Android application layer with secure Chromium WebView rendering.
|
||||
- **Hardware-Accelerated WebGL Shaders**: Real-time dynamic glassmorphism, responsive ambient backdrops, and fluid gesture navigation.
|
||||
- **Automated Resource Management**: Downloads and unpacks core service updates directly from official release channels, keeping scraper definitions up to date.
|
||||
- **Low-Latency Mobile Playback**: Supports HLS and direct stream resolution with adaptive mobile bitrate handling.
|
||||
|
||||
---
|
||||
|
||||
## Building from Source
|
||||
|
||||
### Prerequisites
|
||||
Before compiling Vyla Go, ensure your development environment has:
|
||||
- **Node.js**: v20 or newer
|
||||
- **Rust & Cargo**: Latest stable toolchain
|
||||
- **Android Studio**: Android SDK & NDK (v29+)
|
||||
- **Cross-Compilation Targets**:
|
||||
|
||||
```bash
|
||||
rustup target add aarch64-linux-android x86_64-linux-android
|
||||
```
|
||||
|
||||
### Build Commands
|
||||
|
||||
```bash
|
||||
# 1. Install project dependencies
|
||||
npm install
|
||||
|
||||
# 2. Package the runtime update payload
|
||||
npm run build:payload
|
||||
|
||||
# 3. Compile the release APKs
|
||||
npm run build:apk
|
||||
```
|
||||
|
||||
The compiled release packages are output to:
|
||||
```
|
||||
src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Developer & Debugging Guide (ADB)
|
||||
|
||||
The Vyla Go repository provides complete ADB integration for debugging, log collection, and direct device installation:
|
||||
|
||||
### Streaming Device Logs
|
||||
Monitor the application logs in real time via ADB:
|
||||
```powershell
|
||||
# Stream live application logs tagged VylaGo
|
||||
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" logcat -s VylaGo
|
||||
|
||||
# Clear the log buffer and stream fresh output
|
||||
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" logcat -c
|
||||
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" logcat -s VylaGo
|
||||
```
|
||||
|
||||
### Crash Diagnostics
|
||||
Inspect unhandled exceptions, native SIGSEGV errors, and runtime failures:
|
||||
```powershell
|
||||
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" logcat -d | Select-String "VylaGo|AndroidRuntime|FATAL"
|
||||
```
|
||||
|
||||
### Direct Device Installation
|
||||
Install your newly built APK directly to a connected USB or Wi-Fi debug device:
|
||||
```powershell
|
||||
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" install -r "src-tauri\gen\android\app\build\outputs\apk\universal\release\app-universal-release.apk"
|
||||
```
|
||||
|
||||
### Resetting App State
|
||||
To completely terminate and purge cached app state:
|
||||
```powershell
|
||||
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" shell am force-stop com.vyla.go
|
||||
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" shell pm clear com.vyla.go
|
||||
```
|
||||
|
||||
### Fixing Emulator DNS Timeouts
|
||||
When running Android emulators that fail to reach remote release endpoints:
|
||||
```powershell
|
||||
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" shell settings put global private_dns_mode off
|
||||
```
|
||||
|
||||
149
products/home.md
@@ -1,73 +1,156 @@
|
||||
# Vyla Home
|
||||
# Vyla Home (Desktop)
|
||||
|
||||
<p class="page-description">The desktop control center for running the Vyla media stack locally.</p>
|
||||
<p class="page-description">The native desktop control center and player runtime for Windows, macOS, and Linux built on Tauri v2 and an embedded Node.js sidecar.</p>
|
||||
|
||||
<img class="vyla-banner" src="/src/assets/images/desktop/site_preview.png" alt="Vyla Home desktop application preview" loading="lazy" fetchpriority="low" />
|
||||
<img class="vyla-banner" src="/src/assets/images/desktop/site_preview.webp" alt="Vyla Home desktop application preview displaying streaming interface" loading="lazy" decoding="async" fetchpriority="low" />
|
||||
|
||||
## Vyla Home
|
||||
## Overview
|
||||
|
||||
**Vyla Home** is the desktop application in the Vyla ecosystem. It prepares and runs the local services that power your media experience, then opens the interface in a secure embedded window. It is designed for people who want Vyla without managing a separate server process, reverse proxy, or service lifecycle by hand.
|
||||
**Vyla Home** is the flagship desktop application of the Vyla ecosystem. Engineered for performance and privacy, Vyla Home packages the entire Vyla streaming runtime into a lightweight native shell using **Tauri v2** and an embedded Node.js sidecar process.
|
||||
|
||||
Instead of running heavy web browsers with excessive RAM overhead, Vyla Home leverages system-native WebViews (WebView2 on Windows, WebKit on macOS/Linux), ensuring instant boot times, hardware-accelerated video decoding, and tiny background resource footprints.
|
||||
|
||||
---
|
||||
|
||||
## Downloads
|
||||
|
||||
Vyla Home is available for Windows, macOS, and Linux. Get the latest version below:
|
||||
Official binary releases are published directly to the [Vyla Home GitLab Releases](https://gitlab.com/vyla-entertainment/home/-/releases):
|
||||
|
||||
| Platform | Download |
|
||||
| --- | --- |
|
||||
| Windows | [Download: v0.0.0 (.exe)](https://gitlab.com/vyla-entertainment/home/-/releases) |
|
||||
| macOS | [Download: v0.0.0 (.dmg)](https://gitlab.com/vyla-entertainment/home/-/releases) |
|
||||
| Linux | [Build from source](#linux-setup) |
|
||||
| Platform | Package Format | Architecture | Download Link |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Windows** | Setup Installer (`.exe`) / Portable | x86_64 (64-bit) | [Download for Windows (.exe)](https://gitlab.com/vyla-entertainment/home/-/releases) |
|
||||
| **macOS** | Disk Image (`.dmg`) | Universal (Apple Silicon & Intel) | [Download for macOS (.dmg)](https://gitlab.com/vyla-entertainment/home/-/releases) |
|
||||
| **Linux** | Source Build / Flatpak (in progress) | x86_64, aarch64 | [Build from Source](#linux-build-guide) |
|
||||
|
||||
Clicking the download button will retrieve the latest installer for Windows or macOS.
|
||||
:::callout-info
|
||||
When launching Vyla Home for the first time, the application will automatically perform a self-check, initialize local caches, and establish secure RPC connections.
|
||||
:::
|
||||
|
||||
## Linux Setup
|
||||
---
|
||||
|
||||
For Linux, you'll need to build Vyla Home from source. Follow these steps to install on Fedora:
|
||||
## Architectural Implementation
|
||||
|
||||
**Step 1: Install dependencies**
|
||||
Vyla Home operates via a dual-process architecture:
|
||||
|
||||
| Process Layer | Tech Stack | Responsibilities & Capabilities |
|
||||
| :--- | :--- | :--- |
|
||||
| **Tauri Main Process** | Rust | Manages native OS window lifecycle, system tray icons, native menus, and hosts the secure WebView renderer. |
|
||||
| **Inter-Process Channel** | Stdio IPC | Bi-directional JSON-RPC stream communicating over standard input and standard output (`stdin` / `stdout`). |
|
||||
| **Node.js Sidecar Process** | Node.js (`main.js`) | Orchestrates `ServiceManager` for local/remote fallback, verifies update cryptographic signatures via `UpdateClient`, manages Discord Rich Presence, and handles local cached data. |
|
||||
|
||||
### File Locations & Directory Structure
|
||||
Vyla Home stores its configuration, cryptographic keys, cached catalogs, and logs in standard operating system directories:
|
||||
|
||||
| OS | Default Storage Path |
|
||||
| :--- | :--- |
|
||||
| **Windows** | `%APPDATA%\vyla-home\vyla-home-files\` |
|
||||
| **macOS** | `~/Library/Application Support/vyla-home/vyla-home-files/` |
|
||||
| **Linux** | `~/.local/share/vyla-home/vyla-home-files/` |
|
||||
|
||||
### Diagnostics & Logs
|
||||
If you encounter runtime issues, inspect the sidecar debug log:
|
||||
- **Windows**: `%APPDATA%\vyla-home\vyla-home-files\logs\node-debug.log`
|
||||
- **macOS/Linux**: `<AppDataDir>/logs/node-debug.log`
|
||||
|
||||
---
|
||||
|
||||
## Windows Troubleshooting: WebView2 Runtime
|
||||
|
||||
On Windows systems, Vyla Home utilizes Microsoft's Evergreen WebView2 runtime. If your installation encounters a `WebView2 runtime initialization error`:
|
||||
|
||||
1. Ensure the Microsoft Edge WebView2 Evergreen Runtime is installed from [Microsoft's official site](https://developer.microsoft.com/en-us/microsoft-edge/webview2/).
|
||||
2. For portable or standalone deployments, verify that `WebView2Loader.dll` exists in the `bin/` directory adjacent to the binary:
|
||||
|
||||
```bash
|
||||
sudo dnf install webkit2gtk4.1-devel curl wget openssl-devel libappindicator-gtk3-devel librsvg2-devel
|
||||
sudo dnf install gcc gcc-c++ make automake autoconf
|
||||
vyla-home\bin\WebView2Loader.dll
|
||||
```
|
||||
|
||||
**Step 2: Install Node.js and NPM**
|
||||
---
|
||||
|
||||
## Linux Build Guide
|
||||
|
||||
To build and run Vyla Home natively on Linux distributions (such as Fedora, Ubuntu, Debian, or Arch):
|
||||
|
||||
### 1. Install System Dependencies
|
||||
|
||||
:::code-group
|
||||
<!-- tab: Fedora / RHEL -->
|
||||
```bash
|
||||
sudo dnf install nodejs npm
|
||||
sudo dnf install -y \
|
||||
webkit2gtk4.1-devel curl wget openssl-devel \
|
||||
libappindicator-gtk3-devel librsvg2-devel \
|
||||
gcc gcc-c++ make automake autoconf nodejs npm
|
||||
```
|
||||
|
||||
**Step 3: Install Rust**
|
||||
<!-- tab: Ubuntu / Debian -->
|
||||
```bash
|
||||
sudo apt-get update && sudo apt-get install -y \
|
||||
libwebkit2gtk-4.1-dev build-essential curl wget \
|
||||
libssl-dev libayatana-appindicator3-dev librsvg2-dev \
|
||||
nodejs npm
|
||||
```
|
||||
|
||||
<!-- tab: Arch Linux -->
|
||||
```bash
|
||||
sudo pacman -Syu --needed \
|
||||
webkit2gtk-4.1 base-devel curl wget openssl \
|
||||
libappindicator-gtk3 librsvg nodejs npm
|
||||
```
|
||||
:::
|
||||
|
||||
### 2. Install Rust Toolchain
|
||||
```bash
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source "$HOME/.cargo/env"
|
||||
```
|
||||
|
||||
**Step 4: Clone the Git repository**
|
||||
|
||||
### 3. Clone and Build
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://gitlab.com/vyla-entertainment/home.git vyla-home
|
||||
cd vyla-home
|
||||
```
|
||||
|
||||
**Step 5: Install more dependencies**
|
||||
|
||||
```bash
|
||||
# Install Node dependencies
|
||||
npm install
|
||||
|
||||
# Package runtime payload
|
||||
npm run build:payload
|
||||
|
||||
# Launch in developer mode
|
||||
npm run dev
|
||||
|
||||
# Or compile production binary
|
||||
npm run build
|
||||
```
|
||||
|
||||
**Step 6: Build the Fedora package (.rpm)**
|
||||
---
|
||||
|
||||
## Developer Workflows
|
||||
|
||||
The Vyla Home repository includes specialized scripts for continuous development and release management:
|
||||
|
||||
```bash
|
||||
cargo update --manifest-path src-tauri/Cargo.toml
|
||||
npx tauri build --bundles rpm,appimage
|
||||
# Start the full application in development mode with hot reload
|
||||
npm run dev
|
||||
```
|
||||
|
||||
**Step 7: Final installation**
|
||||
|
||||
```bash
|
||||
sudo dnf install src-tauri/target/release/bundle/rpm/*.rpm
|
||||
# Package the local application payload into update-payload.zip
|
||||
npm run build:payload
|
||||
```
|
||||
|
||||
*Thank you @krishi279 for supplying the Linux setup instructions on Discord ❤️.*
|
||||
```bash
|
||||
# Test the UI shell with a simulated test harness
|
||||
npm run dev -- -- --env VYLA_TEST_UI=1
|
||||
```
|
||||
|
||||
```bash
|
||||
# Compile and package a full production installer release
|
||||
npm run release:full
|
||||
```
|
||||
|
||||
### Resolving File Locks (OS Error 32)
|
||||
If a hung process locks the binary during development on Windows:
|
||||
```powershell
|
||||
Stop-Process -Name "vyla-home", "node", "cargo" -Force -ErrorAction SilentlyContinue
|
||||
```
|
||||
|
||||
@@ -1,37 +1,177 @@
|
||||
# Build a player
|
||||
# Building a Custom Player
|
||||
|
||||
<p class="page-description">Design a resilient Vyla playback experience around your own product and infrastructure.</p>
|
||||
<p class="page-description">Architectural principles, format handling, and engineering guidelines for building resilient video players backed by Vyla infrastructure.</p>
|
||||
|
||||
## Your player, your rules
|
||||
## Overview
|
||||
|
||||
Vyla supplies the integration layer; your application owns the player experience. That includes design, authentication, title discovery, parental controls, analytics, playback policy, and fallback behavior.
|
||||
In the Vyla ecosystem, you are never locked into an official frontend. Vyla provides the discovery, resolution, and proxy layers; your application owns the player experience. Whether you are building a custom web portal, desktop client, or mobile app, this guide covers the core engineering patterns required for a robust player.
|
||||
|
||||
For a local managed experience, Vyla Home starts its bundled player service. For a custom product, choose one of two boundaries:
|
||||
---
|
||||
|
||||
| Integration | Best for |
|
||||
| --- | --- |
|
||||
| **SDK directly** | Node.js products that already have a backend and want complete control |
|
||||
| **Stream API** | browser or mobile clients that need an HTTP/SSE source feed |
|
||||
## Integration Boundaries
|
||||
|
||||
## Build for changing conditions
|
||||
Choose your integration point based on your architecture:
|
||||
|
||||
Provider results vary by title and network. A dependable player should:
|
||||
| Architecture | Integration Point | Data Flow | Best For |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Node.js Backend** | **Vyla SDK Directly** | `sdk.getStream()` in-process | Applications with full backend control, custom authentication, and server-side session tracking. |
|
||||
| **Browser / Mobile** | **Stream API Gateway** | SSE `/movie` or `/tv` over HTTP | Web applications, smart TV frontends, or lightweight mobile clients requiring an HTTP/SSE source feed. |
|
||||
|
||||
1. Begin playback as soon as it has a usable candidate.
|
||||
2. Keep later candidates as fallbacks.
|
||||
3. Keep subtitles independent from first playback.
|
||||
4. Report a helpful unavailable state when no candidates resolve.
|
||||
5. Destroy playback and abort in-flight work when the viewer changes titles.
|
||||
---
|
||||
|
||||
The SDK returns provider results directly; Stream API emits `meta`, `source`, and `done` events. Both models benefit from the same fallback strategy.
|
||||
## Playback Formats: HLS vs Direct Video
|
||||
|
||||
## Playback formats
|
||||
Resolved candidate streams come in two primary container types:
|
||||
|
||||
Candidates may be HLS manifests or direct media files. Let the result metadata and your player capabilities guide how a candidate is loaded; do not assume every URL has the same format or availability. HLS-capable web players normally use HLS.js where native HLS is unavailable, while direct media can be assigned to a video element.
|
||||
1. **HLS Streams (`type: "hls"`)**:
|
||||
- Master M3U8 playlists containing multiple bitrate/resolution variants.
|
||||
- Proxied through Stream API (`/api?url=...`), which automatically rewrites chunk URLs and AES-128 encryption key URIs.
|
||||
- Requires native HLS support (Safari on macOS/iOS) or a JavaScript HLS MSE library such as [HLS.js](https://github.com/video-dev/hls.js/) in modern browsers.
|
||||
2. **Direct Media Files (`type: "mp4"`)**:
|
||||
- Progressive MP4 or MKV streams.
|
||||
- Proxied with HTTP Range header forwarding (`206 Partial Content`), allowing smooth scrubbing and seeking.
|
||||
- Directly assignable to the HTML5 `<video>` element `src` attribute.
|
||||
|
||||
## Continue building
|
||||
---
|
||||
|
||||
- Embed provider resolution with [SDK quickstart](/sdk/vyla-sdk/quickstart).
|
||||
- Learn the optional HTTP event contract in [Paths and playback events](/stream-api/reference/paths).
|
||||
- Use the [client integration guide](/guides/integrations) for a browser-oriented example.
|
||||
- Review [error handling](/guides/error-handling) before shipping a player.
|
||||
## Resilient Player Architecture
|
||||
|
||||
Upstream streaming hosts are dynamic: network latencies fluctuate, upstream proxies fail, and geo-restrictions vary. A production-ready Vyla player must adhere to five design rules:
|
||||
|
||||
### 1. Start on the First Candidate
|
||||
Never wait for all providers to finish. Stream API emits verified sources over Server-Sent Events (SSE). The moment the first `source` event arrives (typically within 3 to 8 seconds):
|
||||
- Initialize your player immediately.
|
||||
- Store any subsequent incoming `source` events in a priority fallback array.
|
||||
|
||||
### 2. Maintain a Fallback Priority Array
|
||||
If the active stream stalls, encounters an unrecoverable network error, or buffer-underruns:
|
||||
- Catch the error event (e.g. `Hls.Events.ERROR` with fatal flag).
|
||||
- Pop the next candidate from your fallback queue.
|
||||
- Re-attach the media source without disrupting the user's viewing session.
|
||||
|
||||
### 3. Decouple Subtitles from Video Playback
|
||||
Subtitles are retrieved independently from the video stream. Never block initial video buffering waiting for subtitle tracks to download. Load the video stream immediately and inject subtitle tracks (`<track kind="subtitles">`) asynchronously once resolved.
|
||||
|
||||
### 4. Implement a Client-Side Timeout
|
||||
Always set a client-side watchdog timer (typically 15 to 20 seconds). If no viable candidate emits a valid video stream before the timer expires, render an informative empty state allowing the user to retry or switch servers.
|
||||
|
||||
### 5. Clean Up on Title Switch
|
||||
When a viewer selects a new movie or episode:
|
||||
- Call `eventSource.close()` on the active SSE connection.
|
||||
- Invoke `hls.destroy()` or `video.removeAttribute('src')`.
|
||||
- Clear pending retry timers and abort in-flight fetch requests.
|
||||
|
||||
---
|
||||
|
||||
## Reference Implementation: Minimal Web Player
|
||||
|
||||
Below is a complete, copyable implementation using native HTML5 and HLS.js:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Vyla Custom Player</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
|
||||
<style>
|
||||
video { width: 100%; max-width: 960px; height: auto; background: #000; border-radius: 8px; }
|
||||
.status { font-family: sans-serif; margin-top: 8px; color: #666; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<video id="vyla-video" controls playsinline></video>
|
||||
<div id="status" class="status">Connecting to Stream API...</div>
|
||||
|
||||
<script>
|
||||
const API_BASE = "https://your-stream-api.example.com";
|
||||
const TMDB_ID = 155;
|
||||
|
||||
const video = document.getElementById("vyla-video");
|
||||
const statusEl = document.getElementById("status");
|
||||
|
||||
const fallbackSources = [];
|
||||
let isPlaying = false;
|
||||
let hlsInstance = null;
|
||||
|
||||
const eventSource = new EventSource(`${API_BASE}/movie?id=${TMDB_ID}`);
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
if (data.type === "meta") {
|
||||
statusEl.textContent = `Found: ${data.meta.title} (${data.meta.year})`;
|
||||
if (data.subtitles) loadSubtitles(data.subtitles);
|
||||
}
|
||||
else if (data.type === "source") {
|
||||
if (!isPlaying) {
|
||||
playCandidate(data);
|
||||
} else {
|
||||
fallbackSources.push(data);
|
||||
}
|
||||
}
|
||||
else if (data.type === "done") {
|
||||
eventSource.close();
|
||||
if (!isPlaying && fallbackSources.length === 0) {
|
||||
statusEl.textContent = "No compatible streaming sources found.";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = () => {
|
||||
eventSource.close();
|
||||
if (!isPlaying) statusEl.textContent = "Error connecting to Stream API.";
|
||||
};
|
||||
|
||||
function playCandidate(candidate) {
|
||||
isPlaying = true;
|
||||
statusEl.textContent = `Streaming from provider: ${candidate.source} (${candidate.quality})`;
|
||||
|
||||
if (candidate.type === "hls") {
|
||||
if (Hls.isSupported()) {
|
||||
if (hlsInstance) hlsInstance.destroy();
|
||||
hlsInstance = new Hls({ maxBufferLength: 30 });
|
||||
hlsInstance.loadSource(candidate.url);
|
||||
hlsInstance.attachMedia(video);
|
||||
hlsInstance.on(Hls.Events.MANIFEST_PARSED, () => video.play());
|
||||
|
||||
hlsInstance.on(Hls.Events.ERROR, (event, errorData) => {
|
||||
if (errorData.fatal) {
|
||||
console.warn("HLS fatal error encountered. Switching to fallback candidate...");
|
||||
hlsInstance.destroy();
|
||||
isPlaying = false;
|
||||
if (fallbackSources.length > 0) playCandidate(fallbackSources.shift());
|
||||
}
|
||||
});
|
||||
} else if (video.canPlayType("application/vnd.apple.mpegurl")) {
|
||||
video.src = candidate.url;
|
||||
video.play();
|
||||
}
|
||||
} else {
|
||||
video.src = candidate.url;
|
||||
video.play();
|
||||
}
|
||||
}
|
||||
|
||||
function loadSubtitles(subtitles) {
|
||||
subtitles.forEach((sub) => {
|
||||
const track = document.createElement("track");
|
||||
track.kind = "subtitles";
|
||||
track.label = sub.label;
|
||||
track.srclang = sub.label.slice(0, 2).toLowerCase();
|
||||
track.src = sub.file;
|
||||
video.appendChild(track);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Next Steps
|
||||
|
||||
- Explore the complete [Client Integration Guide](/guides/integrations) for React, Vue, and ExoPlayer examples.
|
||||
- Review error classification in the [Error Handling Guide](/guides/error-handling).
|
||||
- Review the SSE event protocol in [Paths & Playback Events](/stream-api/reference/paths).
|
||||
|
||||
@@ -1,17 +1,86 @@
|
||||
# Vyla TV
|
||||
# Vyla TV (Android TV)
|
||||
|
||||
<p class="page-description">The Android TV application for running the Vyla media experience on your television.</p>
|
||||
<p class="page-description">The native 10-foot Leanback streaming experience for Android TV and Google TV, built with Jetpack Compose and ExoPlayer.</p>
|
||||
|
||||
<img class="vyla-banner" src="/src/assets/images/android-tv/site-preview.png" alt="A collage of films and shows available through a Vyla experience" loading="lazy" fetchpriority="low" />
|
||||
<img class="vyla-banner" src="/src/assets/images/android-tv/site-preview.webp" alt="Vyla TV application Leanback interface on television display" loading="lazy" decoding="async" fetchpriority="low" />
|
||||
|
||||
**Vyla TV** is an Android TV streaming app built with Jetpack Compose and ExoPlayer that lets you browse and watch movies and TV shows from TMDB.
|
||||
## Overview
|
||||
|
||||
**Vyla TV** is the living-room client in the Vyla ecosystem. Built entirely as a native Android application using modern **Jetpack Compose for TV** and Google's high-performance **ExoPlayer (Media3)** engine, Vyla TV brings the decentralized media experience to large displays.
|
||||
|
||||
Every interface component in Vyla TV is designed specifically for remote control navigation, ensuring seamless D-pad focus management, rapid title browsing, and cinema-grade playback.
|
||||
|
||||
---
|
||||
|
||||
## Downloads
|
||||
|
||||
Vyla TV is available for Android TV. Get the latest version below:
|
||||
Official TV APK builds are published to the [Vyla TV GitLab Releases](https://gitlab.com/vyla-entertainment/TV/-/releases):
|
||||
|
||||
| Platform | Download |
|
||||
| --- | --- |
|
||||
| Android TV | [Download: v0.0.0 (.apk)](https://gitlab.com/vyla-entertainment/TV/-/releases) |
|
||||
| Platform | Format | Recommended Target Devices | Download Link |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Android TV / Google TV** | `.apk` | Sony Bravia, Chromecast with Google TV, NVIDIA Shield TV, Xiaomi Mi Box | [Download Vyla TV APK](https://gitlab.com/vyla-entertainment/TV/-/releases) |
|
||||
| **Amazon Fire TV** | `.apk` | Fire TV Stick 4K / Max, Fire TV Cube | [Download Vyla TV APK](https://gitlab.com/vyla-entertainment/TV/-/releases) |
|
||||
|
||||
Clicking the download button will retrieve the latest `.apk` package for your Android TV device.
|
||||
---
|
||||
|
||||
## TV Experience & Features
|
||||
|
||||
- **D-Pad Directional Navigation**: Engineered from the ground up for directional remotes. No mouse pointers, virtual cursors, or awkward touch adaptations.
|
||||
- **ExoPlayer Media3 Engine**: Hardware-accelerated video decoding with direct support for adaptive HLS (m3u8), MP4, and MKV streams.
|
||||
- **Playback Control Matrix**:
|
||||
- Variable playback speeds (0.5x, 0.75x, 1.0x, 1.25x, 1.5x, 2.0x).
|
||||
- Multi-track subtitle selector with custom sync adjustment and font sizing.
|
||||
- Multi-stream source switcher to toggle between upstream providers without leaving the player.
|
||||
- **Catalog & Library**:
|
||||
- TMDB catalog integration with detailed cast, plot synopses, and genre filters.
|
||||
- "Watch Later" watchlist and persistent "Favorites" library stored locally on device.
|
||||
- Instant resume tracking with automatic timestamp memory.
|
||||
|
||||
---
|
||||
|
||||
## Sideloading on Android TV / Fire TV
|
||||
|
||||
If your device does not have a direct browser installer:
|
||||
|
||||
1. Install **Downloader by AFTVnews** from the Google Play Store or Amazon Appstore.
|
||||
2. In your TV's settings, navigate to **Settings > System > Developer Options** (or **Device & Software > Developer Options**) and enable **Install Unknown Apps** for Downloader.
|
||||
3. Open Downloader and enter the direct APK URL from our releases:
|
||||
|
||||
```bash
|
||||
https://gitlab.com/vyla-entertainment/TV/-/releases
|
||||
```
|
||||
4. Download the latest `.apk` and select **Install**.
|
||||
|
||||
---
|
||||
|
||||
## Building from Source
|
||||
|
||||
### Prerequisites
|
||||
- Node.js 20+
|
||||
- Java Development Kit (JDK 17 or JDK 21)
|
||||
- Android SDK with Android TV API 26+
|
||||
|
||||
### Build Steps
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://gitlab.com/vyla-entertainment/TV.git vyla-tv
|
||||
cd vyla-tv
|
||||
|
||||
# Install JavaScript asset bundling tools
|
||||
npm install
|
||||
|
||||
# Build JS resolver bundle and compile debug APK via Gradle
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The resulting package will be generated at:
|
||||
```
|
||||
android/app/build/outputs/apk/debug/vyla.apk
|
||||
```
|
||||
|
||||
### Developing in Android Studio
|
||||
To inspect Kotlin source code or UI composables directly:
|
||||
1. Launch Android Studio.
|
||||
2. Choose **Open an Existing Project** and select the `android/` subfolder inside the TV repository.
|
||||
3. Allow Gradle to sync and build targets.
|
||||
|
||||
54
resources/faq.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Frequently Asked Questions (FAQ)
|
||||
|
||||
<p class="page-description">Answers to common questions regarding the Vyla ecosystem, self-hosting, platform compatibility, and legal policies.</p>
|
||||
|
||||
## General Questions
|
||||
|
||||
### What is Vyla?
|
||||
Vyla is a community-built, decentralized media platform and developer ecosystem. It provides client applications (Desktop, Android, Android TV), a multi-provider scraping resolver SDK (`@vyla-entertainment/sdk`), and a self-hostable HTTP/SSE streaming gateway (Stream API).
|
||||
|
||||
### Does Vyla host or stream any media?
|
||||
**No.** Vyla does not host, upload, cache, or distribute copyrighted video files or media streams. The software functions purely as a client-side indexing and scraping engine that queries publicly accessible third-party providers on demand.
|
||||
|
||||
### Is Vyla free and open source?
|
||||
Yes. All components of the Vyla ecosystem are open source, licensed under standard permissive open-source licenses, and free to inspect, fork, and self-host.
|
||||
|
||||
---
|
||||
|
||||
## Technical & Developer Questions
|
||||
|
||||
### Why do I need a TMDB API Key?
|
||||
The Movie Database (TMDB) provides authoritative metadata, cover artwork, cast details, and standardized numeric identifiers for movies and TV episodes. Several provider scrapers require converting TMDB IDs to IMDb IDs or episode names. You can obtain a free API key at [themoviedb.org](https://www.themoviedb.org/).
|
||||
|
||||
### Why do some sources work on my home PC but fail on a datacenter VPS?
|
||||
Many public upstream video hosts employ Cloudflare or proprietary anti-bot protections that block known datacenter IP subnets (e.g. AWS, DigitalOcean, Hetzner, OVH) while allowing residential IP connections. When self-hosting Stream API on a VPS, some scrapers may encounter IP-based challenges that do not appear on residential connections.
|
||||
|
||||
### What is the difference between Vyla SDK and Stream API?
|
||||
- **Vyla SDK (`@vyla-entertainment/sdk`)**: A Node.js library you install directly in a backend application using `npm install`. It runs scrapers directly in your process.
|
||||
- **Stream API**: A standalone, self-hostable HTTP/SSE microservice that wraps the SDK, providing a web API for mobile apps, smart TVs, and browser frontends that cannot execute Node.js modules natively.
|
||||
|
||||
### What is the 120-byte segment de-masking routine?
|
||||
Certain streaming providers mask video chunks by prepending a 120-byte fake image header (such as a PNG or JPEG file signature) to disguise traffic. Stream API inspects video chunks and automatically slices off the fake header, restoring valid MPEG-TS video for standard video players.
|
||||
|
||||
---
|
||||
|
||||
## Application & Platform Questions
|
||||
|
||||
### Which desktop operating systems are supported by Vyla Home?
|
||||
Vyla Home officially supports 64-bit Windows (Windows 10/11), macOS (Universal binary supporting Apple Silicon and Intel), and Linux (via source compilation and Flatpak packages).
|
||||
|
||||
### Can I run Vyla TV on Amazon Fire TV devices?
|
||||
Yes. Vyla TV is built for Android TV (API 26+) and is fully compatible with Amazon Fire TV devices (Fire TV Stick 4K, Fire TV Cube, etc.). You can sideload the APK using tools like Downloader.
|
||||
|
||||
### What should I do if Vyla Home fails with a WebView2 error on Windows?
|
||||
Ensure the Microsoft Edge WebView2 Evergreen Runtime is installed. If running a portable version, verify that `WebView2Loader.dll` is present in the `bin/` directory adjacent to the application executable.
|
||||
|
||||
---
|
||||
|
||||
## Policies & Community
|
||||
|
||||
### How do rights holders request removal of a source?
|
||||
Rights holders can submit source removal requests directly to our team. We promptly review and disable scrapers for requested domains. See our [DMCA Policy](/misc/project/dmca) for contact instructions.
|
||||
|
||||
### How can I contribute to Vyla?
|
||||
We welcome code contributions, documentation improvements, and bug reports on [GitLab](https://gitlab.com/vyla-entertainment). Check out the [Contributing Guide](/sdk/vyla-sdk/contributing) to learn how to author and submit new provider scrapers.
|
||||
86
resources/glossary.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Ecosystem Glossary
|
||||
|
||||
<p class="page-description">Authoritative definitions for technical terms, protocols, and architectural concepts across the Vyla ecosystem.</p>
|
||||
|
||||
## A
|
||||
|
||||
### AES-128 Encryption
|
||||
A symmetric key cipher frequently used in HLS streaming. The playlist references an encryption key endpoint (`#EXT-X-KEY:METHOD=AES-128,URI="..."`). Stream API proxies and rewrites these key URIs to ensure playback permissions and headers match the viewer's session.
|
||||
|
||||
---
|
||||
|
||||
## C
|
||||
|
||||
### Cluster Primary & Worker
|
||||
Node.js architecture utilized by Stream API. The **Primary** master process allocates CPU affinity, coordinates IPC messaging, and supervises workers. **Workers** handle inbound HTTP client requests, parse streams, and query upstream hosts concurrently.
|
||||
|
||||
---
|
||||
|
||||
## E
|
||||
|
||||
### ExoPlayer (Media3)
|
||||
Google's high-performance, customizable media player library for Android. Used by Vyla TV to render adaptive 4K/HDR HLS streams with hardware acceleration.
|
||||
|
||||
---
|
||||
|
||||
## F
|
||||
|
||||
### Fallback Priority Queue
|
||||
A client-side architecture where candidate video streams emitted by Stream API are queued in memory. If the currently playing stream crashes or drops offline, the player immediately mounts the next candidate in the queue without interrupting the viewer.
|
||||
|
||||
---
|
||||
|
||||
## H
|
||||
|
||||
### HLS (HTTP Live Streaming)
|
||||
An adaptive bitrate streaming communications protocol developed by Apple. Video is divided into a sequence of small HTTP-based file chunks (.ts or .m4s) indexed by a master `.m3u8` playlist.
|
||||
|
||||
### HLS.js
|
||||
A JavaScript library that implements an HLS client using HTML5 video and Media Source Extensions (MSE). Used by custom web players to play Vyla HLS streams in browsers lacking native HLS support.
|
||||
|
||||
---
|
||||
|
||||
## J
|
||||
|
||||
### Jitter Staggering
|
||||
A randomized delay (0 to 800ms) injected before scraper network requests fire. Prevents dozens of concurrent scrapers from triggering upstream Cloudflare or DDoS rate limits at the same millisecond.
|
||||
|
||||
---
|
||||
|
||||
## M
|
||||
|
||||
### M3U8 Playlist
|
||||
A UTF-8 encoded text file representing an HLS playlist. Stream API intercepts and rewrites master and media M3U8 playlists so that chunk and encryption key URLs route through the proxy with valid CORS headers.
|
||||
|
||||
---
|
||||
|
||||
## P
|
||||
|
||||
### Progressive Streaming
|
||||
A delivery pattern where metadata and media candidates are emitted to client players in real time over Server-Sent Events (SSE) as each scraper finishes, rather than waiting for all scrapers to complete.
|
||||
|
||||
### Provider / Scraper
|
||||
A self-contained JavaScript module in `@vyla-entertainment/sdk` (`src/sources/`) that queries an upstream video host, extracts stream URLs, and formats headers.
|
||||
|
||||
---
|
||||
|
||||
## S
|
||||
|
||||
### Server-Sent Events (SSE)
|
||||
A standardized HTML5 server-to-client streaming technology over a persistent HTTP connection (`Content-Type: text/event-stream`). Used by Stream API `/movie` and `/tv` routes to emit `meta`, `source`, and `done` events.
|
||||
|
||||
### Sidecar Process
|
||||
An architectural pattern used in Vyla Home where a secondary runtime process (Node.js) is bundled adjacent to the primary native application shell (Tauri Rust binary) to manage local services.
|
||||
|
||||
---
|
||||
|
||||
## T
|
||||
|
||||
### Tauri v2
|
||||
A framework for building tiny, ultra-fast native applications for all major desktop and mobile platforms using web frontends and Rust backends. Vyla Home and Vyla Go are built on Tauri v2.
|
||||
|
||||
### TMDB (The Movie Database)
|
||||
A community-built movie and television database. Vyla standardizes all movie and TV series queries on numeric TMDB IDs.
|
||||
|
||||
### TS Segment De-Masking (120-Byte Stripping)
|
||||
A de-obfuscation routine implemented in Stream API. Certain CDNs disguise video chunks by prepending a 120-byte fake image header (PNG, JPEG, TikTok CDN). Stream API removes these 120 bytes, restoring valid MPEG-TS video for media players.
|
||||
95
resources/troubleshooting.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# Troubleshooting & Diagnostics
|
||||
|
||||
<p class="page-description">Step-by-step diagnostic workflows and resolutions for common issues across Vyla Home, Vyla Go, Vyla TV, Stream API, and the SDK.</p>
|
||||
|
||||
## Desktop: Vyla Home
|
||||
|
||||
### 1. Windows: "WebView2 runtime initialization error"
|
||||
- **Cause**: The Microsoft Edge WebView2 runtime is missing or corrupted on the host machine.
|
||||
- **Resolution**:
|
||||
1. Download and run the Evergreen Bootstrapper installer from [Microsoft's WebView2 page](https://developer.microsoft.com/en-us/microsoft-edge/webview2/).
|
||||
2. If using portable builds, verify that `WebView2Loader.dll` exists in `bin\WebView2Loader.dll`.
|
||||
|
||||
### 2. Windows: "OS Error 32: File in use by another process"
|
||||
- **Cause**: A previous Tauri, Node.js, or Cargo process is still running in the background and holding a file lock.
|
||||
- **Resolution**: Terminate any dangling processes in PowerShell:
|
||||
|
||||
```powershell
|
||||
Stop-Process -Name "vyla-home", "node", "cargo" -Force -ErrorAction SilentlyContinue
|
||||
```
|
||||
|
||||
### 3. Linux: Build Fails with Missing WebKit or AppIndicator
|
||||
- **Cause**: Linux requires native GTK3 and WebKit development headers.
|
||||
- **Resolution**: Install required system packages:
|
||||
- **Fedora**: `sudo dnf install webkit2gtk4.1-devel libappindicator-gtk3-devel librsvg2-devel`
|
||||
- **Ubuntu/Debian**: `sudo apt install libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev`
|
||||
|
||||
### 4. Inspecting Node Sidecar Logs
|
||||
If Vyla Home launches but media fails to resolve:
|
||||
- **Windows**: Open `%APPDATA%\vyla-home\vyla-home-files\logs\node-debug.log`
|
||||
- **macOS**: Open `~/Library/Application Support/vyla-home/vyla-home-files/logs/node-debug.log`
|
||||
|
||||
---
|
||||
|
||||
## Mobile: Vyla Go
|
||||
|
||||
### 1. Emulator DNS Timeouts when Connecting to GitLab
|
||||
- **Cause**: Android emulators occasionally fail to resolve remote domains when private DNS is active.
|
||||
- **Resolution**: Disable private DNS mode via ADB:
|
||||
|
||||
```powershell
|
||||
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" shell settings put global private_dns_mode off
|
||||
```
|
||||
|
||||
### 2. Dumping Crash Logs from Device
|
||||
To capture a full logcat dump of crash traces:
|
||||
```powershell
|
||||
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" logcat -d | Select-String "VylaGo|AndroidRuntime|FATAL"
|
||||
```
|
||||
|
||||
### 3. Resetting Cached App Data
|
||||
```powershell
|
||||
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" shell am force-stop com.vyla.go
|
||||
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" shell pm clear com.vyla.go
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Living Room: Vyla TV
|
||||
|
||||
### 1. Build Fails during `assembleDebug`
|
||||
- **Cause**: Incompatible Java Development Kit (JDK) or missing Android TV SDK platform.
|
||||
- **Resolution**:
|
||||
1. Ensure JDK 17 or JDK 21 is active: `java -version`.
|
||||
2. Ensure Android TV API level 26 or higher is installed via Android Studio SDK Manager.
|
||||
|
||||
### 2. Video Stalls or Fails to Play on Fire TV
|
||||
- **Cause**: Hardware codec mismatch or network buffer underrun.
|
||||
- **Resolution**:
|
||||
1. Inside the player, press the Down button on your D-pad to open the stream switcher.
|
||||
2. Select an alternative provider (e.g. switch from an adaptive HLS stream to a direct 1080p stream).
|
||||
|
||||
---
|
||||
|
||||
## Backend: Stream API
|
||||
|
||||
### 1. SSE Events Delayed or Arriving All at Once
|
||||
- **Cause**: The reverse proxy (Nginx, Cloudflare, Caddy) is buffering chunked responses.
|
||||
- **Resolution**:
|
||||
1. In Nginx, ensure `proxy_buffering off;` and `proxy_cache off;` are configured in your `location` block.
|
||||
2. In Cloudflare, ensure response buffering is disabled for your streaming subdomain.
|
||||
|
||||
### 2. `EADDRINUSE: address already in use :::7860`
|
||||
- **Cause**: A previous Stream API instance is already bound to port 7860.
|
||||
- **Resolution**:
|
||||
|
||||
```bash
|
||||
# Find PID on Linux
|
||||
lsof -i :7860
|
||||
# Kill hung process
|
||||
kill -9 <PID>
|
||||
```
|
||||
|
||||
### 3. TMDB API Warnings in Console
|
||||
- **Warning**: `Warning: TMDB_API_KEY not set, imdb-required sources will be unavailable`
|
||||
- **Resolution**: Add `TMDB_API_KEY=your_key` to your `.env` file and restart the service.
|
||||
@@ -1,4 +1,10 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://vyla.cc/sitemap.xml
|
||||
# LLM & AI Discovery Endpoints
|
||||
Allow: /llms.txt
|
||||
Allow: /llms-full.txt
|
||||
Allow: /ai-catalog.json
|
||||
Allow: /.well-known/ard.json
|
||||
|
||||
Sitemap: https://vyla.cc/sitemap.xml
|
||||
|
||||
71
scripts/bundle-css.mjs
Normal file
@@ -0,0 +1,71 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const rootDir = path.resolve(__dirname, '..');
|
||||
const cssDir = path.join(rootDir, 'src', 'css');
|
||||
|
||||
const CSS_ORDER = [
|
||||
'roots.css',
|
||||
'layout.css',
|
||||
'themes.css',
|
||||
'components.css',
|
||||
'features.css',
|
||||
'overlay.css',
|
||||
'prose.css',
|
||||
'404.css'
|
||||
];
|
||||
|
||||
export function bundleCss() {
|
||||
const parts = [];
|
||||
|
||||
for (const file of CSS_ORDER) {
|
||||
const filePath = path.join(cssDir, file);
|
||||
if (fs.existsSync(filePath)) {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
parts.push(`/* --- ${file} --- */\n${content}`);
|
||||
} else {
|
||||
console.warn(`[bundle-css] Warning: ${file} not found.`);
|
||||
}
|
||||
}
|
||||
|
||||
const unminified = parts.join('\n\n');
|
||||
const bundlePath = path.join(cssDir, 'bundle.css');
|
||||
fs.writeFileSync(bundlePath, unminified, 'utf-8');
|
||||
|
||||
let min = unminified
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '')
|
||||
.replace(/\s*([\{\};,>~])\s*/g, '$1')
|
||||
.replace(/\s*(!important)\b/g, '$1')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
|
||||
const minPath = path.join(cssDir, 'bundle.min.css');
|
||||
fs.writeFileSync(minPath, min, 'utf-8');
|
||||
|
||||
const unminKb = (Buffer.byteLength(unminified, 'utf-8') / 1024).toFixed(1);
|
||||
const minKb = (Buffer.byteLength(min, 'utf-8') / 1024).toFixed(1);
|
||||
|
||||
console.log(`[bundle-css] Bundled ${CSS_ORDER.length} files -> bundle.css (${unminKb} KB) | bundle.min.css (${minKb} KB)`);
|
||||
}
|
||||
|
||||
if (process.argv[1] === __filename) {
|
||||
const isWatch = process.argv.includes('--watch') || process.argv.includes('-w');
|
||||
bundleCss();
|
||||
|
||||
if (isWatch) {
|
||||
console.log('[bundle-css] Watching src/css for changes (Press Ctrl+C to stop)...');
|
||||
let timeout = null;
|
||||
fs.watch(cssDir, (eventType, filename) => {
|
||||
if (filename && filename.endsWith('.css') && !filename.includes('bundle')) {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
console.log(`[bundle-css] Change detected in ${filename}, rebuilding...`);
|
||||
bundleCss();
|
||||
}, 80);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,179 +1,139 @@
|
||||
# Contributing to Sources
|
||||
# Contributing a Source Provider
|
||||
|
||||
<p class="page-description">Help maintain, fix broken scrapers, and add new video or download providers to the Vyla SDK.</p>
|
||||
<p class="page-description">Complete technical guide for authoring, testing, and contributing a new video scraping resolver to the Vyla SDK.</p>
|
||||
|
||||
Third-party streaming and scraping endpoints frequently change their DOM layout, API endpoints, tokens, and CDN protections. Community contributions are vital to keeping providers operational.
|
||||
## Overview
|
||||
|
||||
The Vyla SDK maintains its high reliability through a distributed collection of independent provider scrapers located in `src/sources/`. We welcome contributions of new sources that expand coverage for movies, television, and international content.
|
||||
|
||||
---
|
||||
|
||||
### Thank You
|
||||
## Scraper Architecture
|
||||
|
||||
If you’re a developer or just want to help test and fix sources, feel free to contribute! If you get a source working, send me the updated version.
|
||||
Every provider module is a self-contained ES Module adhering to a single exported contract:
|
||||
|
||||
As a thank-you, contributors will receive the Contributor role and credit directly in the code for their contribution, along with a custom message of your choice and your Discord username.
|
||||
|
||||
Any help is appreciated! ❤️
|
||||
|
||||
## Getting Started
|
||||
|
||||
### 1. Clone the repository
|
||||
|
||||
Clone the SDK repository to your local machine:
|
||||
|
||||
```bash
|
||||
git clone https://gitlab.com/vyla-entertainment/sdk.git
|
||||
cd sdk
|
||||
```typescript
|
||||
export async function getStream(args: StreamArgs): Promise<StreamResult | { allUrls: StreamResult[] }>
|
||||
```
|
||||
|
||||
### 2. Install dependencies
|
||||
### The `args` Object
|
||||
When your scraper's `getStream()` is invoked, the SDK passes a normalized `args` context containing:
|
||||
|
||||
Ensure you are running Node.js 18 or newer:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 3. Environment configuration
|
||||
|
||||
Copy the example environment file and add your TMDB API key:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Open `.env` and set your key:
|
||||
|
||||
```ini
|
||||
TMDB_API_KEY=your_actual_tmdb_api_key_here
|
||||
```
|
||||
| Field | Type | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `id` | `string \| number` | The TMDB numeric media ID or string IMDb ID. |
|
||||
| `s` | `number \| null` | Season number for television episodes (or `null` for movies). |
|
||||
| `e` | `number \| null` | Episode number for television episodes (or `null` for movies). |
|
||||
| `clientIP` | `string \| null` | The viewer's client IP address (if passed from an upstream proxy). |
|
||||
| `fetch` | `Function` | Node.js fetch wrapper pre-configured with timeout controls. |
|
||||
| `imdbId` | `string \| null` | Automatically resolved IMDb ID (if TMDB API key is configured). |
|
||||
| `title` | `string \| null` | Verified media title string. |
|
||||
| `year` | `number \| null` | Media release year. |
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
## Step-by-Step Implementation Guide
|
||||
|
||||
- `src/sources/`: Scraper implementations for each provider.
|
||||
- `src/manga/`: Manga catalog search and reader page resolvers.
|
||||
- `src/config.js`: Source registry containing timeouts, retries, headers, and proxy flags.
|
||||
- `src/downloads/downloads.js`: Downloader resolver extracting direct MP4 streams.
|
||||
- `src/subtitles.js`: Subtitle mirror aggregation endpoints.
|
||||
- `test/index.js`: Test runner for testing streams, subtitles, downloads, and manga.
|
||||
|
||||
---
|
||||
|
||||
## Testing Sources
|
||||
|
||||
Testing is driven by `test/index.js`. Open the file and update `CONFIG` to target the source you are fixing or adding:
|
||||
### Step 1: Create the Scraper Module
|
||||
Create a new file in `src/sources/<source_name>.js`:
|
||||
|
||||
```javascript
|
||||
const CONFIG = {
|
||||
DEBUG: true,
|
||||
const BASE_URL = 'https://provider-domain.example.com';
|
||||
|
||||
MOVIE_ID: "936075",
|
||||
TV_ID: "37854",
|
||||
TV_SEASON: "1",
|
||||
TV_EPISODE: "1",
|
||||
STREAM_SOURCE: "vidrock",
|
||||
|
||||
TEST_SUBTITLE: false,
|
||||
TEST_DOWNLOAD: false,
|
||||
TEST_MOVIE: true,
|
||||
TEST_TV: true,
|
||||
|
||||
EXTRA_SDK_PARAMS: {}
|
||||
const DEFAULT_HEADERS = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||||
'Referer': `${BASE_URL}/`,
|
||||
'Origin': BASE_URL,
|
||||
};
|
||||
```
|
||||
|
||||
Run the test suite:
|
||||
export async function getStream({ id, s, e, imdbId }) {
|
||||
const isTv = s != null && e != null;
|
||||
const endpoint = isTv
|
||||
? `${BASE_URL}/api/tv/${id}/${s}/${e}`
|
||||
: `${BASE_URL}/api/movie/${id}`;
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
const response = await fetch(endpoint, {
|
||||
headers: DEFAULT_HEADERS
|
||||
});
|
||||
|
||||
When `DEBUG: true` is enabled, all outbound fetch calls, headers, request payloads, and raw responses will be logged to your terminal and saved to `test/debug-output.json`.
|
||||
if (!response.ok) {
|
||||
throw new Error(`Upstream returned HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
---
|
||||
const data = await response.json();
|
||||
if (!data.streamUrl) {
|
||||
throw new Error('No stream found in upstream response');
|
||||
}
|
||||
|
||||
## Fixing or Adding a Source
|
||||
|
||||
### 1. Source Resolver
|
||||
|
||||
All source files are located in `src/sources/`. Each module must export an asynchronous `getStream` function:
|
||||
|
||||
```javascript
|
||||
export async function getStream({ id, s, e, clientIP, audio, sdk, config }) {
|
||||
const isTv = Boolean(s && e);
|
||||
const mediaId = id;
|
||||
|
||||
const streamUrl = "https://example-cdn.com/stream/playlist.m3u8";
|
||||
|
||||
return {
|
||||
url: streamUrl,
|
||||
quality: "auto",
|
||||
isM3U8: true,
|
||||
headers: {
|
||||
Referer: "https://example-provider.to/",
|
||||
Origin: "https://example-provider.to"
|
||||
},
|
||||
allUrls: [
|
||||
{
|
||||
url: streamUrl,
|
||||
quality: "1080p",
|
||||
isM3U8: true
|
||||
}
|
||||
]
|
||||
};
|
||||
return {
|
||||
url: data.streamUrl,
|
||||
headers: {
|
||||
...DEFAULT_HEADERS,
|
||||
'Referer': data.referer || DEFAULT_HEADERS.Referer
|
||||
},
|
||||
server: 'MySource-Primary',
|
||||
quality: data.quality || '1080p',
|
||||
type: data.streamUrl.includes('.m3u8') ? 'hls' : 'mp4'
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Registering in `src/config.js`
|
||||
---
|
||||
|
||||
Add or update the source entry inside the `SOURCES` array in `src/config.js`:
|
||||
### Step 2: Register in `src/config.js`
|
||||
|
||||
Add your provider configuration to the `SOURCES` array in `src/config.js`:
|
||||
|
||||
```javascript
|
||||
{
|
||||
key: 'mysource',
|
||||
label: 'MySource',
|
||||
label: 'My Source',
|
||||
sourceFile: 'mysource',
|
||||
proxyParam: 'ms',
|
||||
timeout: 25000,
|
||||
jitter: 500,
|
||||
retries: 2,
|
||||
multiUrl: true,
|
||||
skipProxy: true,
|
||||
cdnHeaders: [{
|
||||
pattern: /cdn\.mysource\.to/i,
|
||||
headers: {
|
||||
Referer: 'https://mysource.to/',
|
||||
Origin: 'https://mysource.to',
|
||||
},
|
||||
}],
|
||||
timeout: 12000,
|
||||
jitter: 400,
|
||||
retries: 1,
|
||||
multiUrl: false,
|
||||
skipProxy: false,
|
||||
skipVerify: false
|
||||
}
|
||||
```
|
||||
|
||||
- `skipProxy`: Set to `true` if the stream plays directly in client players without server-side proxying.
|
||||
- `multiUrl`: Set to `true` if the scraper provides multiple resolutions or fallback mirrors.
|
||||
- `cdnHeaders`: Include custom headers required by the upstream CDN to avoid HTTP 403 Forbidden responses.
|
||||
#### Configuration Field Guidelines:
|
||||
- `key`: Lowercase alphanumeric identifier without spaces (e.g. `vidlink`, `cinecat`).
|
||||
- `label`: Clean display title shown in UI menus.
|
||||
- `proxyParam`: Unique 2-to-4 character query flag used by the Stream API HLS proxy (must not collide with existing flags).
|
||||
- `timeout`: Maximum execution budget before aborting (typically 8,000ms–15,000ms).
|
||||
- `jitter`: Random delay ceiling (typically 300ms–500ms) to avoid server burst locks.
|
||||
|
||||
---
|
||||
|
||||
## Download Providers
|
||||
### Step 3: Local Testing
|
||||
|
||||
Downloads are handled automatically by `src/downloads/downloads.js`. The downloader queries active sources that have `skipProxy: true` enabled and inspects returned streams:
|
||||
Run your scraper against known TMDB titles using the test runner:
|
||||
|
||||
- URLs ending in `.mp4` or containing `.mp4` queries are automatically extracted.
|
||||
- `.m3u8` streams are filtered out because they are HLS segment playlists rather than direct file downloads.
|
||||
- If you are implementing a provider that supplies raw MP4 video files, return them in `allUrls` or `url` with `skipProxy: true` in `src/config.js`.
|
||||
```bash
|
||||
# Test a known movie (The Dark Knight - TMDB 155)
|
||||
node test/index.js mysource 155
|
||||
|
||||
To verify download resolution in `test/index.js`, toggle:
|
||||
|
||||
```javascript
|
||||
TEST_DOWNLOAD: true
|
||||
# Test a known TV episode (Breaking Bad S01E01 - TMDB 1396)
|
||||
node test/index.js mysource 1396 1 1
|
||||
```
|
||||
|
||||
Verify that:
|
||||
1. A valid URL is printed.
|
||||
2. The `type` is correctly identified as `'hls'` or `'mp4'`.
|
||||
3. Required upstream headers (`Referer`, `Origin`, `User-Agent`) are included.
|
||||
4. Testing an invalid ID throws a clean error without crashing the process.
|
||||
|
||||
---
|
||||
|
||||
## Submitting Pull Requests
|
||||
## Contribution Rules & Checklist
|
||||
|
||||
1. Verify that your updated source passes tests for both movies (`TEST_MOVIE: true`) and TV shows (`TEST_TV: true`).
|
||||
2. Ensure you have not committed your `.env` file or personal TMDB API keys.
|
||||
3. Push your branch and open a Merge Request on GitLab.
|
||||
Before submitting a Pull Request to the GitLab repository:
|
||||
|
||||
- [ ] Code is formatted with clean ES Module syntax.
|
||||
- [ ] No hardcoded personal API keys or credentials.
|
||||
- [ ] Provider does not execute external untrusted `eval()` or un-sanitized script execution.
|
||||
- [ ] Upstream stream candidate URLs must be verified for playback.
|
||||
- [ ] Errors are thrown with informative messages (`throw new Error(...)`).
|
||||
|
||||
@@ -1,500 +1,83 @@
|
||||
# SDK Reference
|
||||
# Vyla SDK (`@vyla-entertainment/sdk`)
|
||||
|
||||
<p class="page-description">Scrape video streams, fetch subtitles, search manga catalogs, resolve chapter pages, retrieve download links, and monitor provider health directly from your Node.js application.</p>
|
||||
<p class="page-description">The official Node.js scraping and resolver library for movies, television series, subtitles, downloads, and manga.</p>
|
||||
|
||||
The **Vyla SDK** (`@vyla-entertainment/sdk`) is a server-side JavaScript/TypeScript package that provides direct access to Vyla's stream resolvers, health probes, download resolvers, and subtitle providers.
|
||||
<img class="vyla-banner" src="/src/assets/images/devices.webp" alt="Vyla SDK developer tools and multi-platform support" loading="lazy" decoding="async" fetchpriority="low" />
|
||||
|
||||
By embedding the SDK directly into your backend application, provider resolution executes in your own environment. It is an alternative to operating the Stream API as a separate process: your application owns its request boundary, caching, authorization, timeouts, and response shape.
|
||||
## Overview
|
||||
|
||||
:::callout-note
|
||||
The SDK can be used independently of the self-hosted Stream API. If you prefer to embed Vyla capabilities in an existing Node.js application rather than run a separate HTTP service, begin with the [SDK quickstart](/sdk/vyla-sdk/quickstart).
|
||||
:::
|
||||
`@vyla-entertainment/sdk` is the core media resolver engine powering the entire Vyla ecosystem. Available as a pure ES Module for Node.js environments, the SDK aggregates over 60 streaming providers, extracts multi-language subtitle tracks, discovers direct download packages, and interfaces with manga archives.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Direct-to-Client Playback**: Scraped streams link directly to source CDNs. No proxy server overhead required unless specified by provider headers.
|
||||
- **Manga & Comic Aggregation**: Search title catalogs, inspect volume/chapter indexes, and resolve full reader image sets.
|
||||
- **Built-In Health Probing**: Monitor latency and uptime per provider with built-in health check utilities.
|
||||
- **Automated Anime Sub/Dub Routing**: Automatically configures audio track requirements (`sub` vs `dub`) based on provider naming conventions.
|
||||
- **Multi-Source Subtitle Aggregation**: Merges subtitle tracks from multiple redundant subtitle mirrors into a unified VTT/SRT format.
|
||||
- **Metadata Fallbacks**: Automatically maps TMDB IDs to external IDs (such as IMDB and AniList) for smooth stream lookups.
|
||||
- **Self-Contained**: No need to deploy a separate API server - all streaming and reading functionality runs within your existing Node.js application.
|
||||
Whether you are building a custom backend API, an automated scraper pipeline, a command-line tool, or an embeddable player backend, the SDK provides a clean, typed JavaScript and TypeScript interface.
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
Install the package into your Node.js backend using your package manager of choice:
|
||||
Install the package via npm, pnpm, or yarn:
|
||||
|
||||
:::code-group
|
||||
```bash [npm]
|
||||
```bash
|
||||
npm install @vyla-entertainment/sdk
|
||||
```
|
||||
```bash [pnpm]
|
||||
pnpm install @vyla-entertainment/sdk
|
||||
```
|
||||
```bash [yarn]
|
||||
yarn add @vyla-entertainment/sdk
|
||||
```
|
||||
:::
|
||||
|
||||
> **Note**: Node.js **v18+** is required due to modern standard `fetch` and `AbortSignal.timeout` usage.
|
||||
### System Requirements
|
||||
- **Node.js**: v20.0.0 or higher (ESM native)
|
||||
- **TMDB API Key (Optional but Recommended)**: Required for sources that query metadata by IMDb ID or episode titles. Obtain a free key from [The Movie Database](https://www.themoviedb.org/).
|
||||
|
||||
---
|
||||
|
||||
## Initialization
|
||||
## SDK Capabilities
|
||||
|
||||
Initialize the `VylaSDK` client by supplying your **TMDB API Key**. The key resolves media metadata, validates movie/TV show IDs, and translates TMDB IDs into AniList IDs for anime sources.
|
||||
| Functional Engine | Primary Methods | Output Data & Features |
|
||||
| :--- | :--- | :--- |
|
||||
| **Stream Engine** | `getStream()`, `getSources()`, `probeSource()` | Direct MP4 and adaptive HLS (`.m3u8`) links, quality tags, and verified playback flags. |
|
||||
| **Subtitle Parser** | `getSubtitles()` | Multi-language WebVTT (`.vtt`) and SubRip (`.srt`) track descriptors with language labels. |
|
||||
| **Download Engine** | `getDownloads()` | Direct high-speed download links, file sizes in bytes, and container specifications (MKV/MP4). |
|
||||
| **Manga Engine** | `searchManga()`, `getManga()`, `getMangaChapter()` | Titles, covers, chapter indexes, and image page URLs bypassing TLS fingerprint protection. |
|
||||
|
||||
You may optionally provide a pengu.uk manifest URL to enable the Pengu source.
|
||||
### 1. Video Stream Resolution
|
||||
Queries active providers for direct MP4 and adaptive HLS streaming links based on TMDB movie or TV identifiers.
|
||||
|
||||
### 2. Multi-Language Subtitles
|
||||
Extracts clean `.vtt` and `.srt` subtitle tracks from multiple open subtitle providers, categorized by language label.
|
||||
|
||||
### 3. Media Downloads
|
||||
Resolves high-speed direct media download packages (including MKV containers with file size metadata).
|
||||
|
||||
### 4. Manga Discovery & Reading
|
||||
Features a dedicated manga scraping module leveraging `node-tls-client` with Chrome 131 TLS fingerprinting to search manga, view chapters, and retrieve high-resolution page assets.
|
||||
|
||||
---
|
||||
|
||||
## Quick Example
|
||||
|
||||
```javascript
|
||||
import VylaSDK from "@vyla-entertainment/sdk";
|
||||
import VylaSDK from '@vyla-entertainment/sdk';
|
||||
|
||||
const sdk = new VylaSDK({
|
||||
tmdbApiKey: process.env.TMDB_API_KEY,
|
||||
|
||||
penguManifest: "" // Optional, get from pengu.uk
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core Methods
|
||||
|
||||
### `getStream(key, id, s?, e?, clientIP?)`
|
||||
|
||||
Scrapes video stream URLs and playability headers for a specified provider key.
|
||||
|
||||
```javascript
|
||||
// Fetch a movie stream (TMDB ID: 155 - The Dark Knight)
|
||||
const movieStream = await sdk.getStream("sourcename", "155");
|
||||
|
||||
// Fetch a TV show episode (TMDB ID: 1399, Season 1, Episode 1)
|
||||
const tvStream = await sdk.getStream("sourcename", "1399", 1, 1);
|
||||
|
||||
// Pass client IP for providers that use geo-verification
|
||||
const geoStream = await sdk.getStream("sourcename", "155", null, null, "203.0.113.195");
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `key` | `string` | **Yes** | - | Unique key identifier for the provider (e.g. `"sourcename"`). |
|
||||
| `id` | `string` | **Yes** | - | TMDB media ID (e.g., `"155"`). |
|
||||
| `s` | `string \| number` | **No** | `null` | Season number (required for TV shows). |
|
||||
| `e` | `string \| number` | **No** | `null` | Episode number (required for TV shows). |
|
||||
| `clientIP` | `string` | **No** | `null` | End-user IP address forwarded for geo-restricted scrapers. |
|
||||
|
||||
#### Example Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"url": "https://example-cdn.com/hls/manifest.m3u8",
|
||||
"quality": "auto",
|
||||
"isM3U8": true,
|
||||
"headers": {
|
||||
"Referer": "https://example.com/",
|
||||
"Origin": "https://example.com"
|
||||
},
|
||||
"allUrls": [
|
||||
{
|
||||
"url": "https://example-cdn.com/hls/manifest.m3u8",
|
||||
"quality": "1080p",
|
||||
"isM3U8": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getSubtitles(id, s?, e?)`
|
||||
|
||||
Fetches subtitle files aggregated across multiple subtitle backend mirrors (`v1`, `v2`, `febbox`). Returns clean VTT and SRT tracks.
|
||||
|
||||
```javascript
|
||||
// Fetch movie subtitles
|
||||
const movieSubs = await sdk.getSubtitles("155");
|
||||
|
||||
// Fetch TV show episode subtitles
|
||||
const tvSubs = await sdk.getSubtitles("1399", 1, 1);
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | `string` | **Yes** | - | TMDB media ID. |
|
||||
| `s` | `string \| number` | **No** | `null` | Season number (required for TV shows). |
|
||||
| `e` | `string \| number` | **No** | `null` | Episode number (required for TV shows). |
|
||||
|
||||
#### Example Response Format
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"label": "English",
|
||||
"file": "https://sub.vdrk.site/v1/vtt/movie/155/English.vtt",
|
||||
"type": "vtt",
|
||||
"source": "v1"
|
||||
},
|
||||
{
|
||||
"label": "Spanish",
|
||||
"file": "https://fed-subs.pstream.mov/subtitles/spanish.srt",
|
||||
"type": "srt",
|
||||
"source": "febbox"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getDownloads(id, s?, e?)`
|
||||
|
||||
Fetches direct downloadable media files (MP4/MKV) paired with resolution and file size descriptors.
|
||||
|
||||
```javascript
|
||||
// Movie downloads
|
||||
const downloads = await sdk.getDownloads("155");
|
||||
|
||||
// TV Episode downloads
|
||||
const tvDownloads = await sdk.getDownloads("1399", 1, 1);
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | `string` | **Yes** | - | TMDB media ID. |
|
||||
| `s` | `string \| number` | **No** | `null` | Season number (required for TV shows). |
|
||||
| `e` | `string \| number` | **No** | `null` | Episode number (required for TV shows). |
|
||||
|
||||
#### Example Response Format
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"url": "https://download-node.com/file/the-dark-knight-1080p.mp4",
|
||||
"quality": "1080p",
|
||||
"size": "2.14 GB",
|
||||
"format": "MP4"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `searchManga(query, page?, perPage?)`
|
||||
|
||||
Searches manga catalogs by title, author, or keyword.
|
||||
|
||||
```javascript
|
||||
const searchResults = await sdk.searchManga("Steel Ball Run", 1, 10);
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `query` | `string` | **Yes** | - | Search query text. |
|
||||
| `page` | `number` | **No** | `1` | Page number. |
|
||||
| `perPage` | `number` | **No** | `12` | Results per page limit. |
|
||||
|
||||
#### Example Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"page": 1,
|
||||
"total": 2,
|
||||
"results": [
|
||||
{
|
||||
"id": "aKXN8",
|
||||
"title": "JoJo's Bizarre Adventure: Part 7–Steel Ball Run",
|
||||
"englishTitle": null,
|
||||
"authors": ["ARAKI Hirohiko"],
|
||||
"type": "Manga",
|
||||
"medium": "Comic",
|
||||
"status": "Completed",
|
||||
"year": 2004,
|
||||
"rating": 9.03681428571429,
|
||||
"popularity": "1.6M",
|
||||
"poster": "https://cdn.atsu.moe/static/posters/nR3hGYk9GmOqgD52.jpg",
|
||||
"posterSmall": "https://cdn.atsu.moe/static/posters/nR3hGYk9GmOqgD52-small.avif",
|
||||
"posterMedium": "https://cdn.atsu.moe/static/posters/nR3hGYk9GmOqgD52-medium.avif"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getManga(id)`
|
||||
|
||||
Retrieves manga entry details, author info, synopsis, and full chapter list.
|
||||
|
||||
```javascript
|
||||
const manga = await sdk.getManga("aKXN8");
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | `string` | **Yes** | - | Unique manga ID. |
|
||||
|
||||
#### Example Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "aKXN8",
|
||||
"title": "JoJo's Bizarre Adventure: Part 7–Steel Ball Run",
|
||||
"englishTitle": null,
|
||||
"synopsis": "Riders from around the world gather in the Wild West for the race of the century...",
|
||||
"status": "Completed",
|
||||
"type": "Manga",
|
||||
"medium": "Comic",
|
||||
"rating": 9.03681428571429,
|
||||
"views": "330.5K",
|
||||
"authors": ["Hirohiko Araki"],
|
||||
"genres": ["Action", "Adventure", "Drama"],
|
||||
"tags": ["19th Century", "Seinen"],
|
||||
"poster": "https://cdn.atsu.moe/static/posters/nR3hGYk9GmOqgD52.jpg",
|
||||
"totalChapters": 95,
|
||||
"chapters": [
|
||||
{
|
||||
"id": "8KyKadCx",
|
||||
"number": 1,
|
||||
"title": "# 1",
|
||||
"pageCount": 48,
|
||||
"createdAt": "2026-04-18T00:00:00.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getMangaChapter(mangaId, chapterId)`
|
||||
|
||||
Resolves all page image assets for a given manga chapter.
|
||||
|
||||
```javascript
|
||||
const chapter = await sdk.getMangaChapter("aKXN8", "8KyKadCx");
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `mangaId` | `string` | **Yes** | - | Unique manga ID. |
|
||||
| `chapterId` | `string` | **Yes** | - | Unique chapter ID. |
|
||||
|
||||
#### Example Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "8KyKadCx",
|
||||
"number": 1,
|
||||
"title": "# 1",
|
||||
"scanlatorId": "cmo3e2t970003ktp80ip0n5hb",
|
||||
"pages": [
|
||||
{
|
||||
"number": 0,
|
||||
"url": "https://cdn.atsu.moe/static/pages/aKXN8/8KyKadCx/0.webp",
|
||||
"width": 1200,
|
||||
"height": 1722,
|
||||
"aspectRatio": 0.6968641114982579
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Provider & Health Management
|
||||
|
||||
### `getSources(excludeDisabled?)`
|
||||
|
||||
Retrieves provider configurations registered in the SDK.
|
||||
|
||||
```javascript
|
||||
// Get all sources including temporarily disabled ones
|
||||
const allSources = sdk.getSources(false);
|
||||
|
||||
// Get only active/enabled sources
|
||||
const activeSources = sdk.getSources(true);
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `excludeDisabled` | `boolean` | **No** | `false` | When `true`, filters out providers marked as disabled (`disabled: true`). |
|
||||
|
||||
---
|
||||
|
||||
### `probeSource(key)`
|
||||
|
||||
Executes a health ping test against a single provider to determine current availability and latency using a probe payload (`TMDB ID: 155`).
|
||||
|
||||
```javascript
|
||||
const health = await sdk.probeSource("vidrock");
|
||||
console.log(health);
|
||||
// Output: { ok: true, ms: 840 }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `probeAllSources()`
|
||||
|
||||
Executes health tests in parallel across all non-disabled provider modules.
|
||||
|
||||
```javascript
|
||||
const results = await sdk.probeAllSources();
|
||||
console.log(results);
|
||||
```
|
||||
|
||||
#### Example Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"sourcename1": { "ok": true, "ms": 420 },
|
||||
"sourcename2": { "ok": true, "ms": 1150 },
|
||||
"sourcename3": { "ok": false, "ms": null }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Provider Metadata Properties
|
||||
|
||||
When calling `getSources()`, each provider entry returns metadata describing its features and streaming behavior:
|
||||
|
||||
```javascript
|
||||
{
|
||||
key: 'example',
|
||||
label: 'Example',
|
||||
sourceFile: 'example',
|
||||
proxyParam: 'ex',
|
||||
timeout: 20000,
|
||||
jitter: 800,
|
||||
retries: 3,
|
||||
multiUrl: true,
|
||||
skipProxy: false,
|
||||
cdnHeaders: [{
|
||||
pattern: /./,
|
||||
headers: {
|
||||
Referer: 'https://example.com/',
|
||||
Origin: 'https://example.com'
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### Property Reference
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `key` | `string` | Unique identifier used in `getStream()` and `probeSource()`. |
|
||||
| `label` | `string` | Human-readable name for UI display. |
|
||||
| `skipProxy` | `boolean` | When `true`, stream URLs can be fed directly to web video players (e.g., HLS.js, Video.js) without needing an intermediate backend proxy. |
|
||||
| `multiUrl` | `boolean` | Indicates whether the source returns multiple stream resolution choices. |
|
||||
| `timeout` | `number` | Internal request timeout threshold in milliseconds. |
|
||||
| `retries` | `number` | Max automatic retry attempts executed by health/stream scrapers. |
|
||||
| `cdnHeaders` | `Array` | Regex pattern matching rules detailing required request headers (`Referer`, `Origin`) needed to playback stream segments without `403 Forbidden` errors. |
|
||||
| `disabled` | `boolean` | Set to `true` if a source is down or undergoing maintenance. |
|
||||
|
||||
---
|
||||
|
||||
## Practical Express.js Backend Example
|
||||
|
||||
Here is an example showing how to serve stream links to your client web application while handling custom CDN playback headers and subtitle merging:
|
||||
|
||||
```javascript
|
||||
import express from "express";
|
||||
import VylaSDK from "@vyla-entertainment/sdk";
|
||||
|
||||
const app = express();
|
||||
const sdk = new VylaSDK({ tmdbApiKey: process.env.TMDB_API_KEY });
|
||||
|
||||
app.get("/api/stream", async (req, res) => {
|
||||
const { provider, tmdbId, season, episode } = req.query;
|
||||
const clientIP = req.headers["x-forwarded-for"] || req.socket.remoteAddress;
|
||||
|
||||
try {
|
||||
const streamData = await sdk.getStream(
|
||||
provider,
|
||||
tmdbId,
|
||||
season || null,
|
||||
episode || null,
|
||||
clientIP
|
||||
);
|
||||
|
||||
if (!streamData || !streamData.url) {
|
||||
return res.status(404).json({ error: "No stream found for this provider" });
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
provider,
|
||||
stream: streamData,
|
||||
});
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: error.message });
|
||||
}
|
||||
tmdbApiKey: process.env.TMDB_API_KEY
|
||||
});
|
||||
|
||||
app.get("/api/subtitles", async (req, res) => {
|
||||
const { tmdbId, season, episode } = req.query;
|
||||
const sources = sdk.getSources(true);
|
||||
console.log(`Active providers: ${sources.length}`);
|
||||
|
||||
try {
|
||||
const subtitles = await sdk.getSubtitles(
|
||||
tmdbId,
|
||||
season || null,
|
||||
episode || null
|
||||
);
|
||||
try {
|
||||
const stream = await sdk.getStream('vidlink', 155);
|
||||
console.log('Stream resolved:', stream.url);
|
||||
console.log('Format:', stream.type);
|
||||
} catch (err) {
|
||||
console.error('Resolution error:', err.message);
|
||||
}
|
||||
|
||||
return res.json({ subtitles });
|
||||
} catch (error) {
|
||||
return res.status(500).json({ subtitles: [] });
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/manga/search", async (req, res) => {
|
||||
const { q, page, perPage } = req.query;
|
||||
try {
|
||||
const results = await sdk.searchManga(q, page || 1, perPage || 12);
|
||||
return res.json(results);
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/manga/:mangaId/chapter/:chapterId", async (req, res) => {
|
||||
const { mangaId, chapterId } = req.params;
|
||||
try {
|
||||
const pages = await sdk.getMangaChapter(mangaId, chapterId);
|
||||
return res.json(pages);
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(3000, () => console.log("Vyla SDK Server running on port 3000"));
|
||||
const subtitles = await sdk.getSubtitles(155);
|
||||
console.log(`Found ${subtitles.length} subtitle tracks`);
|
||||
```
|
||||
|
||||
## SDK vs Self-Hosted API
|
||||
---
|
||||
|
||||
The SDK and the self-hosted API server provide the same streaming functionality but in different deployment models:
|
||||
## SDK Navigation
|
||||
|
||||
| Aspect | SDK | Self-Hosted API |
|
||||
| --- | --- | --- |
|
||||
| **Deployment** | Embedded in your Node.js app | Separate server process |
|
||||
| **Network** | Direct source CDN access | Built-in proxy for CORS |
|
||||
| **Rate Limiting** | None (you control it) | Per-API-key limits |
|
||||
| **Setup** | npm install + init | Clone repo + configure |
|
||||
| **Use Case** | Existing Node.js apps | Standalone streaming service |
|
||||
|
||||
Choose the SDK if you want to integrate streaming directly into an existing Node.js application. Choose the self-hosted API if you want a standalone streaming service that can be used by multiple clients.
|
||||
- **[SDK Quickstart](/sdk/vyla-sdk/quickstart)**: Practical, step-by-step tutorial for resolving movies, TV episodes, and handling errors.
|
||||
- **[Complete API Reference](/sdk/vyla-sdk/reference)**: Exhaustive documentation of all classes, methods, parameters, and TypeScript types.
|
||||
- **[Manga Module Guide](/sdk/vyla-sdk/manga)**: Guide for manga search, chapter page extraction, and aspect-ratio rendering.
|
||||
- **[Contributing a Source](/sdk/vyla-sdk/contributing)**: How to author, test, and contribute a new provider scraper.
|
||||
|
||||
@@ -1,51 +1,57 @@
|
||||
# Manga Integration
|
||||
# Manga Resolution Module
|
||||
|
||||
<p class="page-description">Search manga catalogs, retrieve chapter listings, and resolve reader-ready page images directly with Vyla SDK.</p>
|
||||
<p class="page-description">How to integrate manga title discovery, chapter listings, and high-resolution page resolution using the Vyla SDK.</p>
|
||||
|
||||
Vyla SDK provides built-in manga support to discover titles, fetch full chapter indices, and resolve high-resolution page image URLs with asset dimensions and aspect ratios.
|
||||
## Overview
|
||||
|
||||
In addition to video streaming, `@vyla-entertainment/sdk` includes a native Manga resolution module. Powered by `atsu.moe` and leveraging `node-tls-client`, the module bypasses anti-bot TLS fingerprinting by mimicking real Google Chrome 131 cryptographic handshakes.
|
||||
|
||||
This allows developers to build responsive, ad-free manga reading interfaces directly into their applications.
|
||||
|
||||
---
|
||||
|
||||
## Technical Architecture
|
||||
|
||||
Upstream manga archives frequently block standard Node.js `fetch` or `axios` requests using JA3/JA4 TLS fingerprinting. To ensure reliable connectivity:
|
||||
|
||||
1. **TLS Emulation**: Uses `node-tls-client` configured with `ClientIdentifier.chrome_131`.
|
||||
2. **Session Persistence**: Reuses keep-alive TLS sessions across search and chapter page requests.
|
||||
3. **Structured Geometry**: Returns explicit image dimensions (`width`, `height`) and pre-calculated aspect ratios so mobile and desktop readers can prevent layout shifts during image loading.
|
||||
|
||||
---
|
||||
|
||||
## Manga Methods
|
||||
|
||||
### `searchManga(query, page?, perPage?)`
|
||||
### 1. Searching for Titles
|
||||
|
||||
Searches the manga catalog for titles matching a query string.
|
||||
Search the manga catalog by query string with pagination support:
|
||||
|
||||
```javascript
|
||||
const searchResults = await sdk.searchManga("Steel Ball Run", 1, 10);
|
||||
console.log(searchResults.results[0]);
|
||||
import VylaSDK from '@vyla-entertainment/sdk';
|
||||
|
||||
const sdk = new VylaSDK();
|
||||
|
||||
const searchResult = await sdk.searchManga('Chainsaw Man', 1, 10);
|
||||
|
||||
console.log(`Total results: ${searchResult.total}`);
|
||||
searchResult.items.forEach((manga) => {
|
||||
console.log(`[ID: ${manga.id}] ${manga.title} - ${manga.latestChapter}`);
|
||||
});
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `query` | `string` | **Yes** | - | Title, author, or acronym search query. |
|
||||
| `page` | `number` | **No** | `1` | Pagination page number. |
|
||||
| `perPage` | `number` | **No** | `12` | Results per page. |
|
||||
|
||||
#### Response Format
|
||||
|
||||
#### Search Response Structure
|
||||
```json
|
||||
{
|
||||
"page": 1,
|
||||
"total": 2,
|
||||
"results": [
|
||||
"perPage": 10,
|
||||
"total": 42,
|
||||
"items": [
|
||||
{
|
||||
"id": "aKXN8",
|
||||
"title": "JoJo's Bizarre Adventure: Part 7–Steel Ball Run",
|
||||
"englishTitle": null,
|
||||
"authors": ["ARAKI Hirohiko"],
|
||||
"type": "Manga",
|
||||
"medium": "Comic",
|
||||
"status": "Completed",
|
||||
"year": 2004,
|
||||
"rating": 9.03681428571429,
|
||||
"popularity": "1.6M",
|
||||
"poster": "https://cdn.atsu.moe/static/posters/nR3hGYk9GmOqgD52.jpg",
|
||||
"posterSmall": "https://cdn.atsu.moe/static/posters/nR3hGYk9GmOqgD52-small.avif",
|
||||
"posterMedium": "https://cdn.atsu.moe/static/posters/nR3hGYk9GmOqgD52-medium.avif"
|
||||
"id": "chainsaw-man",
|
||||
"title": "Chainsaw Man",
|
||||
"cover": "https://cdn.atsu.moe/covers/chainsaw-man.webp",
|
||||
"status": "Ongoing",
|
||||
"latestChapter": "Chapter 180"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -53,85 +59,58 @@ console.log(searchResults.results[0]);
|
||||
|
||||
---
|
||||
|
||||
### `getManga(id)`
|
||||
### 2. Fetching Manga Details & Chapters
|
||||
|
||||
Retrieves comprehensive details for a manga entry, including synopsis, genres, authors, and full chapter list.
|
||||
Retrieve full metadata, descriptions, genres, and the complete chapter index:
|
||||
|
||||
```javascript
|
||||
const manga = await sdk.getManga("aKXN8");
|
||||
console.log(manga.title, manga.totalChapters);
|
||||
const mangaDetails = await sdk.getManga('chainsaw-man');
|
||||
|
||||
console.log('Title:', mangaDetails.title);
|
||||
console.log('Synopsis:', mangaDetails.description);
|
||||
console.log('Genres:', mangaDetails.genres.join(', '));
|
||||
console.log('Available Chapters:', mangaDetails.chapters.length);
|
||||
|
||||
const latest = mangaDetails.chapters[0];
|
||||
console.log(`Latest Chapter: ${latest.chapterNumber} (ID: ${latest.id})`);
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
---
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | `string` | **Yes** | - | Unique manga identifier. |
|
||||
### 3. Resolving Chapter Pages
|
||||
|
||||
#### Response Format
|
||||
To render the reader view, fetch the ordered page image assets for a specific chapter:
|
||||
|
||||
```javascript
|
||||
const mangaId = 'chainsaw-man';
|
||||
const chapterId = 'chapter-1';
|
||||
|
||||
const pages = await sdk.getMangaChapter(mangaId, chapterId);
|
||||
|
||||
console.log(`Chapter contains ${pages.length} pages.`);
|
||||
|
||||
pages.forEach((page, index) => {
|
||||
console.log(`Page ${index + 1}: ${page.url} [${page.width}x${page.height}]`);
|
||||
});
|
||||
```
|
||||
|
||||
#### Page Object Structure
|
||||
```json
|
||||
{
|
||||
"id": "aKXN8",
|
||||
"title": "JoJo's Bizarre Adventure: Part 7–Steel Ball Run",
|
||||
"englishTitle": null,
|
||||
"synopsis": "Riders from around the world gather in the Wild West for the race of the century...",
|
||||
"status": "Completed",
|
||||
"type": "Manga",
|
||||
"medium": "Comic",
|
||||
"rating": 9.03681428571429,
|
||||
"views": "330.5K",
|
||||
"authors": ["Hirohiko Araki"],
|
||||
"genres": ["Action", "Adventure", "Drama", "Historical", "Supernatural"],
|
||||
"tags": ["19th Century", "Horses", "Seinen"],
|
||||
"poster": "https://cdn.atsu.moe/static/posters/nR3hGYk9GmOqgD52.jpg",
|
||||
"totalChapters": 95,
|
||||
"chapters": [
|
||||
{
|
||||
"id": "8KyKadCx",
|
||||
"number": 1,
|
||||
"title": "# 1",
|
||||
"pageCount": 48,
|
||||
"createdAt": "2026-04-18T00:00:00.000Z"
|
||||
}
|
||||
]
|
||||
"index": 1,
|
||||
"url": "https://cdn.atsu.moe/data/.../01.webp",
|
||||
"width": 1080,
|
||||
"height": 1600,
|
||||
"aspectRatio": 0.675
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getMangaChapter(mangaId, chapterId)`
|
||||
## Reader Layout Best Practices
|
||||
|
||||
Resolves all page images for a selected chapter, including pixel dimensions and aspect ratios for smooth responsive rendering.
|
||||
When rendering manga pages in a frontend reader:
|
||||
|
||||
```javascript
|
||||
const chapter = await sdk.getMangaChapter("aKXN8", "8KyKadCx");
|
||||
console.log(chapter.pages[0].url);
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `mangaId` | `string` | **Yes** | - | Unique manga identifier. |
|
||||
| `chapterId` | `string` | **Yes** | - | Unique chapter identifier. |
|
||||
|
||||
#### Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "8KyKadCx",
|
||||
"number": 1,
|
||||
"title": "# 1",
|
||||
"scanlatorId": "cmo3e2t970003ktp80ip0n5hb",
|
||||
"pages": [
|
||||
{
|
||||
"number": 0,
|
||||
"url": "https://cdn.atsu.moe/static/pages/aKXN8/8KyKadCx/0.webp",
|
||||
"width": 1200,
|
||||
"height": 1722,
|
||||
"aspectRatio": 0.6968641114982579
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
1. **Prevent Cumulative Layout Shift (CLS)**: Use the provided `aspectRatio` or `width`/`height` in CSS (`aspect-ratio: attr(width) / attr(height);`) to reserve vertical scroll space before each image finishes downloading.
|
||||
2. **Preload Neighboring Pages**: Implement a 2-page lookahead preloader so viewers turning pages experience instantaneous transitions.
|
||||
3. **Session Cleanup**: In long-running background tasks, invoke `cleanup()` from the module when closing reader sessions to terminate idle TLS sockets.
|
||||
|
||||
@@ -1,97 +1,162 @@
|
||||
# SDK quickstart
|
||||
# Vyla SDK Quickstart
|
||||
|
||||
<p class="page-description">Install Vyla SDK, resolve a source, and build a safe server-side integration.</p>
|
||||
<p class="page-description">Get up and running with the Vyla SDK in under five minutes. Learn how to resolve movies, television episodes, subtitles, and downloads.</p>
|
||||
|
||||
## Before you start
|
||||
## Prerequisites
|
||||
|
||||
Vyla SDK is for **server-side Node.js**. It contains provider integrations and makes outbound requests from the process where you install it. Do not import it into a browser bundle or expose provider results, credentials, or internal source keys directly to untrusted clients.
|
||||
Ensure you have Node.js 20+ installed on your system. Create a new Node.js project:
|
||||
|
||||
You need Node.js 18 or later and, for sources that resolve title metadata, a TMDB API key.
|
||||
|
||||
## Install and initialize
|
||||
|
||||
:::code-group
|
||||
```bash [npm]
|
||||
npm install @vyla-entertainment/sdk
|
||||
```bash
|
||||
mkdir vyla-demo && cd vyla-demo
|
||||
npm init -y
|
||||
npm pkg set type="module"
|
||||
npm install @vyla-entertainment/sdk dotenv
|
||||
```
|
||||
```bash [pnpm]
|
||||
pnpm install @vyla-entertainment/sdk
|
||||
|
||||
Create a `.env` file containing your TMDB API key:
|
||||
|
||||
```ini
|
||||
TMDB_API_KEY=your_tmdb_api_key_here
|
||||
```
|
||||
```bash [yarn]
|
||||
yarn add @vyla-entertainment/sdk
|
||||
```
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 1. Initializing the SDK
|
||||
|
||||
Create an `index.js` file and initialize the `VylaSDK` class:
|
||||
|
||||
```javascript
|
||||
import VylaSDK from "@vyla-entertainment/sdk";
|
||||
import 'dotenv/config';
|
||||
import VylaSDK from '@vyla-entertainment/sdk';
|
||||
|
||||
const vyla = new VylaSDK({
|
||||
tmdbApiKey: process.env.TMDB_API_KEY,
|
||||
penguManifest: process.env.PENGU_MANIFEST
|
||||
const sdk = new VylaSDK({
|
||||
tmdbApiKey: process.env.TMDB_API_KEY
|
||||
});
|
||||
|
||||
console.log('Vyla SDK initialized successfully!');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Resolving a Movie Stream
|
||||
|
||||
To resolve a movie stream, supply the source key and the numeric TMDB movie ID:
|
||||
|
||||
```javascript
|
||||
const sourceKey = 'vidlink';
|
||||
const tmdbId = 155;
|
||||
|
||||
try {
|
||||
const result = await sdk.getStream(sourceKey, tmdbId);
|
||||
console.log('Stream Candidate URL:', result.url);
|
||||
console.log('Container Type:', result.type);
|
||||
console.log('Video Quality:', result.quality);
|
||||
console.log('Upstream Server:', result.server);
|
||||
} catch (error) {
|
||||
console.error(`Failed to resolve ${sourceKey}:`, error.message);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Resolving a Television Episode
|
||||
|
||||
For TV series, provide the TMDB show ID, season number, and episode number:
|
||||
|
||||
```javascript
|
||||
const showId = 1396;
|
||||
const season = 1;
|
||||
const episode = 1;
|
||||
|
||||
try {
|
||||
const tvStream = await sdk.getStream('vixsrc', showId, season, episode);
|
||||
console.log('TV Episode Stream:', tvStream.url);
|
||||
} catch (error) {
|
||||
console.error('TV stream resolution failed:', error.message);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Multi-Source Concurrent Resolution with Fallbacks
|
||||
|
||||
In production, you should query multiple active providers concurrently and select the fastest viable stream:
|
||||
|
||||
```javascript
|
||||
async function resolveFastestStream(tmdbId, s = null, e = null) {
|
||||
const activeSources = sdk.getSources(true);
|
||||
|
||||
const queryPromises = activeSources.map(async (cfg) => {
|
||||
try {
|
||||
const stream = await Promise.race([
|
||||
sdk.getStream(cfg.key, tmdbId, s, e),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Timeout')), cfg.timeout || 10000)
|
||||
)
|
||||
]);
|
||||
return { source: cfg.key, label: cfg.label, stream };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const results = await Promise.all(queryPromises);
|
||||
const successful = results.filter(Boolean);
|
||||
|
||||
if (successful.length === 0) {
|
||||
throw new Error('No viable streaming sources resolved.');
|
||||
}
|
||||
|
||||
console.log(`Resolved ${successful.length} valid streams!`);
|
||||
return successful;
|
||||
}
|
||||
|
||||
const sources = await resolveFastestStream(155);
|
||||
console.log('Primary Source:', sources[0].label, sources[0].stream.url);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Fetching Subtitles and Downloads
|
||||
|
||||
Subtitles and direct download links can be fetched in parallel:
|
||||
|
||||
```javascript
|
||||
const [subtitles, downloads] = await Promise.all([
|
||||
sdk.getSubtitles(155),
|
||||
sdk.getDownloads(155)
|
||||
]);
|
||||
|
||||
console.log('--- Subtitle Tracks ---');
|
||||
subtitles.slice(0, 5).forEach((sub) => {
|
||||
console.log(`[${sub.type.toUpperCase()}] ${sub.label}: ${sub.file}`);
|
||||
});
|
||||
|
||||
console.log('--- Download Packages ---');
|
||||
downloads.forEach((dl) => {
|
||||
console.log(`[${dl.quality}] Size: ${dl.size ? (dl.size / (1024**3)).toFixed(2) + ' GB' : 'Unknown'} -> ${dl.url}`);
|
||||
});
|
||||
```
|
||||
|
||||
:::callout-warning
|
||||
Keep `TMDB_API_KEY` and any deployment configuration in server-side environment variables. Never commit them or send them to a client.
|
||||
:::
|
||||
---
|
||||
|
||||
## Resolve a movie
|
||||
## 6. Live Source Health Probing
|
||||
|
||||
First inspect active providers. Provider availability changes over time, so select a key from the running configuration instead of hard-coding a placeholder.
|
||||
To check which providers are operational in your network environment:
|
||||
|
||||
```javascript
|
||||
const providers = vyla.getSources(true);
|
||||
console.table(providers.map(({ key, label }) => ({ key, label })));
|
||||
const healthReport = await sdk.probeAllSources();
|
||||
|
||||
const provider = providers[0];
|
||||
const result = await vyla.getStream(provider.key, 155);
|
||||
const candidates = result.allUrls ?? [result];
|
||||
|
||||
console.log(candidates.map(({ url, type, quality }) => ({ url, type, quality })));
|
||||
for (const [key, status] of Object.entries(healthReport)) {
|
||||
const icon = status.ok ? 'OK' : 'FAIL';
|
||||
console.log(`[${icon}] ${key}: ${status.ok ? status.ms + 'ms' : 'offline'}`);
|
||||
}
|
||||
```
|
||||
|
||||
`155` is a TMDB movie ID. For an episode, include season and episode:
|
||||
---
|
||||
|
||||
```javascript
|
||||
const result = await vyla.getStream(provider.key, 1396, 1, 1);
|
||||
```
|
||||
## Next Steps
|
||||
|
||||
## Read manga
|
||||
|
||||
Search manga titles, retrieve chapter listings, and resolve reader-ready page images directly:
|
||||
|
||||
```javascript
|
||||
const search = await vyla.searchManga("Steel Ball Run", 1, 5);
|
||||
const firstResult = search.results[0];
|
||||
|
||||
const manga = await vyla.getManga(firstResult.id);
|
||||
const firstChapter = manga.chapters[manga.chapters.length - 1];
|
||||
|
||||
const chapterData = await vyla.getMangaChapter(manga.id, firstChapter.id);
|
||||
console.log(chapterData.pages.map(p => p.url));
|
||||
```
|
||||
|
||||
## Make it resilient
|
||||
|
||||
Provider results are inherently variable: a source can fail, time out, return more than one candidate, or be temporarily disabled. In production:
|
||||
|
||||
1. Probe sources or maintain a recent availability signal before choosing one.
|
||||
2. Give every provider call a request deadline in your application.
|
||||
3. Treat `allUrls` as fallback candidates, not a guarantee that every URL will play.
|
||||
4. Return only the fields your client needs and validate requests at your own API boundary.
|
||||
5. Cache and rate-limit at your boundary to avoid repeatedly triggering upstream work.
|
||||
|
||||
```javascript
|
||||
const health = await vyla.probeAllSources();
|
||||
const usableKey = Object.entries(health).find(([, result]) => result.ok)?.[0];
|
||||
|
||||
if (!usableKey) throw new Error("No providers are currently available");
|
||||
|
||||
const stream = await vyla.getStream(usableKey, 155);
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
- Read the complete [SDK reference](/sdk) for methods and return shapes.
|
||||
- Learn provider flags, timeout behavior, and source keys in [Providers](/concepts/providers).
|
||||
- Choose [Stream API](/stream-api/overview) only when an SSE HTTP interface better fits your client architecture.
|
||||
- Consult the [Complete API Reference](/sdk/vyla-sdk/reference) for exhaustive class and method parameters.
|
||||
- Learn how to integrate manga reading with the [Manga SDK Guide](/sdk/vyla-sdk/manga).
|
||||
- Deploy an HTTP/SSE bridge with the [Stream API Guide](/stream-api/overview).
|
||||
|
||||
276
sdk/vyla-sdk/reference.md
Normal file
@@ -0,0 +1,276 @@
|
||||
# Vyla SDK API Reference
|
||||
|
||||
<p class="page-description">Complete technical reference for the `@vyla-entertainment/sdk` package: classes, methods, parameters, interfaces, and TypeScript definitions.</p>
|
||||
|
||||
## Class: `VylaSDK`
|
||||
|
||||
The primary entry point of the package. Instantiated to manage scraper execution, health probing, subtitle retrieval, downloads, and manga discovery.
|
||||
|
||||
```typescript
|
||||
import VylaSDK from '@vyla-entertainment/sdk';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Constructor
|
||||
|
||||
```typescript
|
||||
constructor(options?: VylaSDKOptions)
|
||||
```
|
||||
|
||||
Creates a new instance of `VylaSDK`.
|
||||
|
||||
#### Parameters
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `options.tmdbApiKey` | `string` | Optional | `null` | Your TMDB v3 API Key. Highly recommended; required by sources that query metadata by IMDb ID or episode name. |
|
||||
| `options.penguManifest` | `any` | Optional | `null` | Custom manifest definition for the Pengu provider source. |
|
||||
|
||||
#### Example
|
||||
```javascript
|
||||
const sdk = new VylaSDK({
|
||||
tmdbApiKey: '9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d'
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getSources(excludeDisabled?)`
|
||||
|
||||
```typescript
|
||||
getSources(excludeDisabled?: boolean): SourceConfig[]
|
||||
```
|
||||
|
||||
Returns the registered list of provider scraper configurations.
|
||||
|
||||
#### Parameters
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `excludeDisabled` | `boolean` | Optional | `false` | When `true`, filters out providers marked with `disabled: true`. |
|
||||
|
||||
#### Returns
|
||||
Array of [`SourceConfig`](#interface-sourceconfig) objects.
|
||||
|
||||
#### Example
|
||||
```javascript
|
||||
const activeSources = sdk.getSources(true);
|
||||
console.log(`Available active providers: ${activeSources.map(s => s.key).join(', ')}`);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getStream(key, id, s?, e?, clientIP?)`
|
||||
|
||||
```typescript
|
||||
async getStream(
|
||||
key: string,
|
||||
id: string | number,
|
||||
s?: number | null,
|
||||
e?: number | null,
|
||||
clientIP?: string | null
|
||||
): Promise<StreamResult | { allUrls: StreamResult[] }>
|
||||
```
|
||||
|
||||
Executes the scraper module for a specified provider and resolves streaming candidate URLs.
|
||||
|
||||
#### Parameters
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `key` | `string` | Required | — | The source key identifier (e.g. `'vidlink'`, `'vixsrc'`). |
|
||||
| `id` | `string \| number` | Required | — | Numeric TMDB ID or string IMDb ID (for movies and TV shows). |
|
||||
| `s` | `number \| null` | Optional | `null` | Season number (required for TV episodes). |
|
||||
| `e` | `number \| null` | Optional | `null` | Episode number (required for TV episodes). |
|
||||
| `clientIP` | `string \| null` | Optional | `null` | Client IP address passed for upstream geo-token generation. |
|
||||
|
||||
#### Returns
|
||||
A Promise resolving to a [`StreamResult`](#interface-streamresult) object, or `{ allUrls: StreamResult[] }` if the source supports multi-URL extraction.
|
||||
|
||||
#### Throws
|
||||
- `Error('Source with key "..." not found')` if the key does not exist.
|
||||
- `Error('Source module for key "..." not found')` if the source module is disabled or failed to import.
|
||||
- Upstream network errors or scraper extraction errors if the title cannot be found.
|
||||
|
||||
#### Example
|
||||
```javascript
|
||||
const movieStream = await sdk.getStream('vidlink', 155);
|
||||
|
||||
const episodeStream = await sdk.getStream('vixsrc', 1396, 1, 1);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getSubtitles(id, s?, e?)`
|
||||
|
||||
```typescript
|
||||
async getSubtitles(
|
||||
id: string | number,
|
||||
s?: number | null,
|
||||
e?: number | null
|
||||
): Promise<SubtitleResult[]>
|
||||
```
|
||||
|
||||
Discovers and parses all available subtitle tracks for a movie or TV episode.
|
||||
|
||||
#### Parameters
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `id` | `string \| number` | Required | — | TMDB media identifier. |
|
||||
| `s` | `number \| null` | Optional | `null` | Season number (TV shows). |
|
||||
| `e` | `number \| null` | Optional | `null` | Episode number (TV shows). |
|
||||
|
||||
#### Returns
|
||||
A Promise resolving to an array of [`SubtitleResult`](#interface-subtitleresult) objects.
|
||||
|
||||
---
|
||||
|
||||
### `getDownloads(id, s?, e?)`
|
||||
|
||||
```typescript
|
||||
async getDownloads(
|
||||
id: string | number,
|
||||
s?: number | null,
|
||||
e?: number | null
|
||||
): Promise<DownloadResult[]>
|
||||
```
|
||||
|
||||
Resolves direct media download packages and torrent/debrid links.
|
||||
|
||||
#### Returns
|
||||
A Promise resolving to an array of [`DownloadResult`](#interface-downloadresult) objects.
|
||||
|
||||
---
|
||||
|
||||
### `probeSource(key)`
|
||||
|
||||
```typescript
|
||||
async probeSource(key: string): Promise<ProbeResult>
|
||||
```
|
||||
|
||||
Sends a lightweight probe request against the designated source to measure responsiveness and availability.
|
||||
|
||||
#### Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `key` | `string` | Required | The provider key to probe. |
|
||||
|
||||
#### Returns
|
||||
A Promise resolving to a [`ProbeResult`](#interface-proberesult) object: `{ ok: boolean, ms: number | null }`.
|
||||
|
||||
---
|
||||
|
||||
### `probeAllSources()`
|
||||
|
||||
```typescript
|
||||
async probeAllSources(): Promise<ProbeAllResult>
|
||||
```
|
||||
|
||||
Concurrently probes all active (non-disabled) sources and compiles a live health map.
|
||||
|
||||
#### Returns
|
||||
A Promise resolving to a [`ProbeAllResult`](#type-probeallresult) dictionary mapping source keys to their probe results.
|
||||
|
||||
---
|
||||
|
||||
### Manga Methods
|
||||
|
||||
#### `searchManga(query, page?, perPage?)`
|
||||
```typescript
|
||||
async searchManga(query: string, page?: number, perPage?: number): Promise<any>
|
||||
```
|
||||
Queries the manga catalog via `atsu.moe` using Chrome 131 TLS fingerprinting. Returns paginated title listings.
|
||||
|
||||
#### `getManga(id)`
|
||||
```typescript
|
||||
async getManga(id: string): Promise<any>
|
||||
```
|
||||
Retrieves full manga details including description, genres, author, status, and chapter list.
|
||||
|
||||
#### `getMangaChapter(mangaId, chapterId)`
|
||||
```typescript
|
||||
async getMangaChapter(mangaId: string, chapterId: string): Promise<any>
|
||||
```
|
||||
Resolves ordered image URLs, dimensions, and aspect ratios for all pages in a given chapter.
|
||||
|
||||
---
|
||||
|
||||
## TypeScript Interfaces
|
||||
|
||||
### `Interface: SourceConfig`
|
||||
|
||||
```typescript
|
||||
export interface SourceConfig {
|
||||
key: string;
|
||||
label: string;
|
||||
sourceFile: string;
|
||||
proxyParam: string;
|
||||
timeout: number;
|
||||
jitter: number;
|
||||
retries: number;
|
||||
multiUrl: boolean;
|
||||
skipProxy?: boolean;
|
||||
skipVerify?: boolean;
|
||||
verifyHeaders?: boolean;
|
||||
disabled?: boolean;
|
||||
sourcesTimeout?: number;
|
||||
cdnHeaders?: {
|
||||
pattern: RegExp;
|
||||
headers: {
|
||||
Referer: string;
|
||||
Origin: string;
|
||||
};
|
||||
}[];
|
||||
}
|
||||
```
|
||||
|
||||
### `Interface: StreamResult`
|
||||
|
||||
```typescript
|
||||
export interface StreamResult {
|
||||
url: string;
|
||||
headers: {
|
||||
"User-Agent": string;
|
||||
Referer: string;
|
||||
Origin: string;
|
||||
[key: string]: string;
|
||||
};
|
||||
server: string;
|
||||
quality: string;
|
||||
type: "mp4" | "hls";
|
||||
skipVerify?: boolean;
|
||||
skipProxy?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### `Interface: SubtitleResult`
|
||||
|
||||
```typescript
|
||||
export interface SubtitleResult {
|
||||
label: string;
|
||||
file: string;
|
||||
type: "vtt" | "srt";
|
||||
source: string;
|
||||
}
|
||||
```
|
||||
|
||||
### `Interface: DownloadResult`
|
||||
|
||||
```typescript
|
||||
export interface DownloadResult {
|
||||
url: string;
|
||||
quality: string;
|
||||
size: number | null;
|
||||
type: "mkv";
|
||||
active: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### `Interface: ProbeResult` & `ProbeAllResult`
|
||||
|
||||
```typescript
|
||||
export interface ProbeResult {
|
||||
ok: boolean;
|
||||
ms: number | null;
|
||||
}
|
||||
|
||||
export type ProbeAllResult = Record<string, ProbeResult>;
|
||||
```
|
||||
124
sitemap.xml
@@ -2,103 +2,163 @@
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://vyla.cc/</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/start-here/introduction</loc>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/start-here/architecture</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
<priority>0.85</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/concepts/providers</loc>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.85</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/products/home</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/products/go</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/products/tv</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/products/player</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/concepts/providers</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/sdk</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/sdk/vyla-sdk/quickstart</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.85</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/sdk/vyla-sdk/reference</loc>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.85</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/sdk/vyla-sdk/manga</loc>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/sdk/vyla-sdk/contributing</loc>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.75</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/stream-api/overview</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/stream-api/reference/paths</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<loc>https://vyla.cc/stream-api/authentication</loc>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/stream-api/reference/paths</loc>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.85</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/stream-api/reference/media-data</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/stream-api/reference/operations</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/stream-api/deployment/self-hosting</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/stream-api/deployment/self-hosting</loc>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.85</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/guides/integrations</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/guides/error-handling</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/resources/faq</loc>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.75</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/resources/troubleshooting</loc>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.75</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/resources/glossary</loc>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/misc/project/story</loc>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/misc/project/changelog</loc>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/misc/project/credit</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
@@ -106,12 +166,12 @@
|
||||
<loc>https://vyla.cc/misc/project/support-us</loc>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/misc/project/dmca</loc>
|
||||
<lastmod>2025-02-15</lastmod>
|
||||
<lastmod>2026-11-14</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
</urlset>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 901 KiB |
BIN
src/assets/images/android-tv/site-preview.webp
Normal file
|
After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 147 KiB |
BIN
src/assets/images/android/site_preview.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 2.0 MiB After Width: | Height: | Size: 528 KiB |
BIN
src/assets/images/desktop-preview.webp
Normal file
|
After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 1016 KiB After Width: | Height: | Size: 762 KiB |
BIN
src/assets/images/desktop/site_preview.webp
Normal file
|
After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 441 KiB After Width: | Height: | Size: 356 KiB |
BIN
src/assets/images/devices.webp
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
src/assets/images/maskable-icon-192x192.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
src/assets/images/maskable-icon-512x512.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src/assets/images/mobile-preview.png
Normal file
|
After Width: | Height: | Size: 327 KiB |
BIN
src/assets/images/mobile-preview.webp
Normal file
|
After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 3.9 MiB After Width: | Height: | Size: 1.4 MiB |
BIN
src/assets/images/rounded-banner.webp
Normal file
|
After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 698 KiB After Width: | Height: | Size: 291 KiB |
BIN
src/assets/images/themes/halloween/cobweb.webp
Normal file
|
After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 3.3 MiB After Width: | Height: | Size: 882 KiB |
BIN
src/assets/images/thumbnail.webp
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
src/assets/images/wallpaper-media/tree-mobile.webp
Normal file
|
After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 7.2 MiB After Width: | Height: | Size: 3.9 MiB |
BIN
src/assets/images/wallpaper-media/tree.webp
Normal file
|
After Width: | Height: | Size: 558 KiB |
3804
src/css/bundle.css
Normal file
1
src/css/bundle.min.css
vendored
Normal file
@@ -179,7 +179,7 @@
|
||||
#main-wrap {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
max-width: 720px;
|
||||
max-width: 1000px;
|
||||
margin-top: 120px;
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
@@ -206,7 +206,7 @@ footer#site-footer {
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
min-height: 22px;
|
||||
margin: 0 0 36px;
|
||||
margin: 0 0 200px;
|
||||
}
|
||||
|
||||
.footer-link {
|
||||
@@ -231,11 +231,149 @@ footer#site-footer {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.footer-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr 1fr 1fr;
|
||||
gap: 36px;
|
||||
padding-bottom: 36px;
|
||||
border-bottom: 1px solid var(--footer-border);
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.footer-brand-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.footer-brand-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
text-decoration: none;
|
||||
color: var(--text);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.footer-brand-name {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.footer-brand-desc {
|
||||
font-size: 13.5px;
|
||||
line-height: 1.6;
|
||||
color: var(--muted);
|
||||
margin: 0 0 18px;
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
.footer-col-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--muted);
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.footer-col-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.footer-col-list a {
|
||||
font-size: 13.5px;
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
transition: color 0.15s ease;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.footer-col-list a:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
border-top: 1px solid var(--footer-border);
|
||||
padding-top: 28px;
|
||||
padding-left: 7px;
|
||||
border-top: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.footer-bottom-info p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.footer-bottom-info .footer-subtext {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.footer-badges {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.footer-green-badge {
|
||||
display: inline-block;
|
||||
opacity: 0.85;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.footer-green-badge:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.footer-green-badge img {
|
||||
display: block;
|
||||
min-height: 100px;
|
||||
width: auto;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.footer-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 28px;
|
||||
}
|
||||
|
||||
.footer-brand-col {
|
||||
grid-column: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 550px) {
|
||||
.footer-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.footer-brand-col {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.footer-card {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-socials {
|
||||
@@ -246,8 +384,11 @@ footer#site-footer {
|
||||
.footer-socials a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--muted);
|
||||
transition: color 0.15s ease;
|
||||
min-width: 36px;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.footer-socials a:hover {
|
||||
@@ -267,8 +408,10 @@ footer#site-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
background: none;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap");
|
||||
|
||||
/* Google fonts loaded in head */
|
||||
:root,
|
||||
:root[data-theme="dark"] {
|
||||
--bg: #000000;
|
||||
|
||||
449
src/js/main.js
@@ -40,102 +40,256 @@ var THEMES_GENERAL = [
|
||||
];
|
||||
|
||||
var THEME_MEDIA = {
|
||||
dark: "/src/assets/images/wallpaper-media/tree.png",
|
||||
light: "https://www.pexels.com/download/video//",
|
||||
midnight: "https://www.pexels.com/download/video//",
|
||||
nord: "https://www.pexels.com/download/video//",
|
||||
dracula: "https://www.pexels.com/download/video//",
|
||||
forest: "https://www.pexels.com/download/video//",
|
||||
sunset: "https://www.pexels.com/download/video//",
|
||||
cyberpunk: "https://www.pexels.com/download/video//"
|
||||
dark: "/src/assets/images/wallpaper-media/tree.webp",
|
||||
light: "/src/assets/images/wallpaper-media/tree.webp",
|
||||
midnight: "/src/assets/images/wallpaper-media/tree.webp",
|
||||
nord: "/src/assets/images/wallpaper-media/tree.webp",
|
||||
dracula: "/src/assets/images/wallpaper-media/tree.webp",
|
||||
forest: "/src/assets/images/wallpaper-media/tree.webp",
|
||||
sunset: "/src/assets/images/wallpaper-media/tree.webp",
|
||||
cyberpunk: "/src/assets/images/wallpaper-media/tree.webp"
|
||||
};
|
||||
|
||||
var SITE_TREE = [
|
||||
{
|
||||
id: "vyla",
|
||||
title: "Vyla",
|
||||
path: "/start-here/introduction",
|
||||
groups: [
|
||||
"id": "vyla",
|
||||
"title": "Ecosystem",
|
||||
"path": "/start-here/introduction",
|
||||
"groups": [
|
||||
{
|
||||
title: "Start here",
|
||||
items: [
|
||||
{ title: "Introduction", file: "/start-here/introduction.md", path: "/start-here/introduction", hasMedia: true },
|
||||
{ title: "Architecture", file: "/start-here/architecture.md", path: "/start-here/architecture" }
|
||||
"title": "Start here",
|
||||
"items": [
|
||||
{
|
||||
"title": "Introduction",
|
||||
"file": "/start-here/introduction.md",
|
||||
"path": "/start-here/introduction",
|
||||
"hasMedia": true
|
||||
},
|
||||
{
|
||||
"title": "Architecture",
|
||||
"file": "/start-here/architecture.md",
|
||||
"path": "/start-here/architecture"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Products",
|
||||
items: [
|
||||
{ title: "Home", file: "/products/home.md", path: "/products/home", hasMedia: true },
|
||||
{ title: "Go", file: "/products/go.md", path: "/products/go", hasMedia: true },
|
||||
{ title: "TV", file: "/products/tv.md", path: "/products/tv", hasMedia: true },
|
||||
{ title: "Player", file: "/products/player.md", path: "/products/player" }
|
||||
"title": "Products",
|
||||
"items": [
|
||||
{
|
||||
"title": "Vyla Home",
|
||||
"file": "/products/home.md",
|
||||
"path": "/products/home",
|
||||
"hasMedia": true
|
||||
},
|
||||
{
|
||||
"title": "Vyla Go",
|
||||
"file": "/products/go.md",
|
||||
"path": "/products/go",
|
||||
"hasMedia": true
|
||||
},
|
||||
{
|
||||
"title": "Vyla TV",
|
||||
"file": "/products/tv.md",
|
||||
"path": "/products/tv",
|
||||
"hasMedia": true
|
||||
},
|
||||
{
|
||||
"title": "Build a Player",
|
||||
"file": "/products/player.md",
|
||||
"path": "/products/player"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Concepts",
|
||||
items: [
|
||||
{ title: "Providers", file: "/concepts/providers.md", path: "/concepts/providers" }
|
||||
"title": "Concepts",
|
||||
"items": [
|
||||
{
|
||||
"title": "Providers",
|
||||
"file": "/concepts/providers.md",
|
||||
"path": "/concepts/providers"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "sdk",
|
||||
title: "SDK",
|
||||
path: "/sdk",
|
||||
groups: [
|
||||
"id": "sdk",
|
||||
"title": "SDK",
|
||||
"path": "/sdk",
|
||||
"groups": [
|
||||
{
|
||||
title: "Vyla SDK",
|
||||
items: [
|
||||
{ title: "SDK", file: "/sdk/vyla-sdk/index.md", path: "/sdk", hasMedia: true },
|
||||
{ title: "SDK Quickstart", file: "/sdk/vyla-sdk/quickstart.md", path: "/sdk/vyla-sdk/quickstart" },
|
||||
{ title: "Manga", file: "/sdk/vyla-sdk/manga.md", path: "/sdk/vyla-sdk/manga", hasMedia: true },
|
||||
{ title: "Contributing", file: "/sdk/vyla-sdk/contributing.md", path: "/sdk/vyla-sdk/contributing", hasMedia: true }
|
||||
"title": "Getting Started",
|
||||
"items": [
|
||||
{
|
||||
"title": "SDK Overview",
|
||||
"file": "/sdk/vyla-sdk/index.md",
|
||||
"path": "/sdk",
|
||||
"hasMedia": true
|
||||
},
|
||||
{
|
||||
"title": "SDK Quickstart",
|
||||
"file": "/sdk/vyla-sdk/quickstart.md",
|
||||
"path": "/sdk/vyla-sdk/quickstart"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Reference",
|
||||
"items": [
|
||||
{
|
||||
"title": "API Reference",
|
||||
"file": "/sdk/vyla-sdk/reference.md",
|
||||
"path": "/sdk/vyla-sdk/reference"
|
||||
},
|
||||
{
|
||||
"title": "Manga Module",
|
||||
"file": "/sdk/vyla-sdk/manga.md",
|
||||
"path": "/sdk/vyla-sdk/manga",
|
||||
"hasMedia": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Development",
|
||||
"items": [
|
||||
{
|
||||
"title": "Contributing Sources",
|
||||
"file": "/sdk/vyla-sdk/contributing.md",
|
||||
"path": "/sdk/vyla-sdk/contributing",
|
||||
"hasMedia": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "stream-api",
|
||||
title: "Stream API",
|
||||
path: "/stream-api/overview",
|
||||
groups: [
|
||||
"id": "stream-api",
|
||||
"title": "Stream API",
|
||||
"path": "/stream-api/overview",
|
||||
"groups": [
|
||||
{
|
||||
title: "Overview",
|
||||
items: [
|
||||
{ title: "Overview", file: "/stream-api/overview/index.md", path: "/stream-api/overview" }
|
||||
"title": "Overview",
|
||||
"items": [
|
||||
{
|
||||
"title": "Stream API Overview",
|
||||
"file": "/stream-api/overview/index.md",
|
||||
"path": "/stream-api/overview"
|
||||
},
|
||||
{
|
||||
"title": "Authentication & Keys",
|
||||
"file": "/stream-api/authentication/index.md",
|
||||
"path": "/stream-api/authentication"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Reference",
|
||||
items: [
|
||||
{ title: "Paths", file: "/stream-api/reference/paths.md", path: "/stream-api/reference/paths" },
|
||||
{ title: "Media Data", file: "/stream-api/reference/media-data.md", path: "/stream-api/reference/media-data", hasMedia: true },
|
||||
{ title: "Operations", file: "/stream-api/reference/operations.md", path: "/stream-api/reference/operations" }
|
||||
"title": "Reference",
|
||||
"items": [
|
||||
{
|
||||
"title": "Paths & Playback",
|
||||
"file": "/stream-api/reference/paths.md",
|
||||
"path": "/stream-api/reference/paths"
|
||||
},
|
||||
{
|
||||
"title": "Media Data",
|
||||
"file": "/stream-api/reference/media-data.md",
|
||||
"path": "/stream-api/reference/media-data",
|
||||
"hasMedia": true
|
||||
},
|
||||
{
|
||||
"title": "Operations & Health",
|
||||
"file": "/stream-api/reference/operations.md",
|
||||
"path": "/stream-api/reference/operations"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Deployment",
|
||||
items: [
|
||||
{ title: "Self Hosting", file: "/stream-api/deployment/self-hosting.md", path: "/stream-api/deployment/self-hosting" }
|
||||
"title": "Deployment",
|
||||
"items": [
|
||||
{
|
||||
"title": "Self Hosting",
|
||||
"file": "/stream-api/deployment/self-hosting.md",
|
||||
"path": "/stream-api/deployment/self-hosting"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "misc",
|
||||
title: "Misc",
|
||||
path: "/misc/project/credit",
|
||||
groups: [
|
||||
"id": "guides",
|
||||
"title": "Guides",
|
||||
"path": "/guides/integrations",
|
||||
"groups": [
|
||||
{
|
||||
title: "Project",
|
||||
items: [
|
||||
{ title: "Story", file: "/misc/project/story.md", path: "/misc/project/story" },
|
||||
{ title: "Changelog", file: "/misc/project/changelog.md", path: "/misc/project/changelog" },
|
||||
{ title: "Credit", file: "/misc/project/credit.md", path: "/misc/project/credit" },
|
||||
{ title: "Support Us", file: "/misc/project/support-us.md", path: "/misc/project/support-us" },
|
||||
{ title: "DMCA", file: "/misc/project/DMCA.md", path: "/misc/project/DMCA" }
|
||||
"title": "Player Guides",
|
||||
"items": [
|
||||
{
|
||||
"title": "Client Integration",
|
||||
"file": "/guides/integrations.md",
|
||||
"path": "/guides/integrations"
|
||||
},
|
||||
{
|
||||
"title": "Error Handling",
|
||||
"file": "/guides/error-handling.md",
|
||||
"path": "/guides/error-handling"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "resources",
|
||||
"title": "Resources",
|
||||
"path": "/resources/faq",
|
||||
"groups": [
|
||||
{
|
||||
"title": "Reference & Help",
|
||||
"items": [
|
||||
{
|
||||
"title": "FAQ",
|
||||
"file": "/resources/faq.md",
|
||||
"path": "/resources/faq"
|
||||
},
|
||||
{
|
||||
"title": "Troubleshooting",
|
||||
"file": "/resources/troubleshooting.md",
|
||||
"path": "/resources/troubleshooting"
|
||||
},
|
||||
{
|
||||
"title": "Glossary",
|
||||
"file": "/resources/glossary.md",
|
||||
"path": "/resources/glossary"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Project",
|
||||
"items": [
|
||||
{
|
||||
"title": "Project Story",
|
||||
"file": "/misc/project/story.md",
|
||||
"path": "/misc/project/story"
|
||||
},
|
||||
{
|
||||
"title": "Changelog",
|
||||
"file": "/misc/project/changelog.md",
|
||||
"path": "/misc/project/changelog"
|
||||
},
|
||||
{
|
||||
"title": "Credits",
|
||||
"file": "/misc/project/credit.md",
|
||||
"path": "/misc/project/credit"
|
||||
},
|
||||
{
|
||||
"title": "Support Us",
|
||||
"file": "/misc/project/support-us.md",
|
||||
"path": "/misc/project/support-us"
|
||||
},
|
||||
{
|
||||
"title": "DMCA Policy",
|
||||
"file": "/misc/project/DMCA.md",
|
||||
"path": "/misc/project/dmca"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -173,6 +327,9 @@ function applyTheme() {
|
||||
|
||||
|
||||
var targetSrc = THEME_MEDIA[theme] || THEME_MEDIA.dark;
|
||||
if (typeof window !== "undefined" && window.innerWidth <= 768 && targetSrc && targetSrc.indexOf("tree.webp") !== -1) {
|
||||
targetSrc = "/src/assets/images/wallpaper-media/tree-mobile.webp";
|
||||
}
|
||||
var videosOff = getPref("video", "on") === "off";
|
||||
var isImageSource = /\.(jpe?g|png|gif|webp|avif|svg)(\?.*)?$/i.test(targetSrc || "");
|
||||
|
||||
@@ -226,6 +383,9 @@ function updateThemeAmbient(src, isImage, videosOff) {
|
||||
media.className = "theme-ambient-media";
|
||||
if (isImage) {
|
||||
media.alt = "";
|
||||
media.setAttribute("width", "1920");
|
||||
media.setAttribute("height", "1080");
|
||||
media.setAttribute("aria-hidden", "true");
|
||||
} else {
|
||||
media.muted = true;
|
||||
media.loop = true;
|
||||
@@ -1297,6 +1457,119 @@ function mountGreeting(root) {
|
||||
}
|
||||
}
|
||||
|
||||
function updatePageSEO(route, mdContent) {
|
||||
var isHome = !route || !route.item || route.item.path === "/";
|
||||
var canonicalLink = document.querySelector('link[rel="canonical"]');
|
||||
var metaDesc = document.querySelector('meta[name="description"]');
|
||||
var ogTitle = document.querySelector('meta[property="og:title"]');
|
||||
var ogDesc = document.querySelector('meta[property="og:description"]');
|
||||
var ogUrl = document.querySelector('meta[property="og:url"]');
|
||||
var twTitle = document.querySelector('meta[name="twitter:title"]');
|
||||
var twDesc = document.querySelector('meta[name="twitter:description"]');
|
||||
|
||||
if (isHome) {
|
||||
var homeTitle = "Vyla Entertainment - Free & Decentralized Media Platform";
|
||||
var homeDesc = "Vyla Entertainment is a community-built, decentralized media platform and developer ecosystem. Featuring native client apps for Desktop, Mobile, and Android TV, a scraping SDK, and a self-hosted Stream API.";
|
||||
var homeUrl = "https://vyla.cc/";
|
||||
|
||||
document.title = homeTitle;
|
||||
if (canonicalLink) canonicalLink.setAttribute("href", homeUrl);
|
||||
if (metaDesc) metaDesc.setAttribute("content", homeDesc);
|
||||
if (ogTitle) ogTitle.setAttribute("content", homeTitle);
|
||||
if (ogDesc) ogDesc.setAttribute("content", homeDesc);
|
||||
if (ogUrl) ogUrl.setAttribute("content", homeUrl);
|
||||
if (twTitle) twTitle.setAttribute("content", homeTitle);
|
||||
if (twDesc) twDesc.setAttribute("content", homeDesc);
|
||||
|
||||
var dynJson = document.getElementById("dynamic-jsonld");
|
||||
if (dynJson) dynJson.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
var pageTitle = route.item.title + " - Vyla Docs";
|
||||
document.title = pageTitle;
|
||||
|
||||
var pageUrl = "https://vyla.cc" + route.item.path;
|
||||
if (canonicalLink) canonicalLink.setAttribute("href", pageUrl);
|
||||
if (ogUrl) ogUrl.setAttribute("content", pageUrl);
|
||||
if (ogTitle) ogTitle.setAttribute("content", pageTitle);
|
||||
if (twTitle) twTitle.setAttribute("content", pageTitle);
|
||||
|
||||
var desc = "";
|
||||
if (mdContent) {
|
||||
var descMatch = mdContent.match(/<p class="page-description">([\s\S]*?)<\/p>/i);
|
||||
if (descMatch) {
|
||||
desc = descMatch[1].replace(/<[^>]+>/g, "").trim();
|
||||
} else {
|
||||
var pMatch = mdContent.match(/^(?:#+[^\n]*\n+)+([^\n#<][^\n]+)/m);
|
||||
if (pMatch) {
|
||||
desc = pMatch[1].replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!desc) {
|
||||
desc = route.item.title + " guide and developer documentation for the Vyla Ecosystem.";
|
||||
}
|
||||
|
||||
if (metaDesc) metaDesc.setAttribute("content", desc);
|
||||
if (ogDesc) ogDesc.setAttribute("content", desc);
|
||||
if (twDesc) twDesc.setAttribute("content", desc);
|
||||
|
||||
var dynScript = document.getElementById("dynamic-jsonld");
|
||||
if (!dynScript) {
|
||||
dynScript = document.createElement("script");
|
||||
dynScript.id = "dynamic-jsonld";
|
||||
dynScript.type = "application/ld+json";
|
||||
document.head.appendChild(dynScript);
|
||||
}
|
||||
|
||||
var graph = [
|
||||
{
|
||||
"@type": "TechArticle",
|
||||
"@id": pageUrl + "#article",
|
||||
"url": pageUrl,
|
||||
"headline": route.item.title,
|
||||
"description": desc,
|
||||
"inLanguage": "en-US",
|
||||
"isPartOf": {
|
||||
"@id": "https://vyla.cc/#website"
|
||||
},
|
||||
"publisher": {
|
||||
"@id": "https://vyla.cc/#organization"
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "BreadcrumbList",
|
||||
"@id": pageUrl + "#breadcrumb",
|
||||
"itemListElement": [
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 1,
|
||||
"name": "Home",
|
||||
"item": "https://vyla.cc/"
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 2,
|
||||
"name": route.tab.title,
|
||||
"item": "https://vyla.cc" + route.tab.path
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 3,
|
||||
"name": route.item.title,
|
||||
"item": pageUrl
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
dynScript.textContent = JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@graph": graph
|
||||
});
|
||||
}
|
||||
|
||||
function loadPage() {
|
||||
var path = window.location.pathname.replace(/\/+$/, "") || "/";
|
||||
var isHome = path === "/";
|
||||
@@ -1305,26 +1578,31 @@ function loadPage() {
|
||||
var contentEl = document.getElementById("main-content");
|
||||
|
||||
if (isHome) {
|
||||
document.title = "Vyla Entertainment - Vyla";
|
||||
updatePageSEO(null, null);
|
||||
renderNavTabs(SITE_TREE[0]);
|
||||
renderSidebar(SITE_TREE[0], { title: "Home", path: "/" });
|
||||
|
||||
if (contentEl) {
|
||||
contentEl.classList.remove("content-animate", "caught-up");
|
||||
fetch("/home.txt")
|
||||
.then(function (res) {
|
||||
if (!res.ok) throw new Error();
|
||||
return res.text();
|
||||
})
|
||||
.then(function (html) {
|
||||
contentEl.innerHTML = html;
|
||||
mountGreeting(contentEl);
|
||||
applyTheme();
|
||||
window.scrollTo(0, 0);
|
||||
})
|
||||
.catch(function () {
|
||||
contentEl.innerHTML = "<h1>Home</h1><p>Create /home.txt to customize this page.</p>";
|
||||
});
|
||||
if (contentEl.querySelector(".home-container")) {
|
||||
mountGreeting(contentEl);
|
||||
applyTheme();
|
||||
} else {
|
||||
contentEl.classList.remove("content-animate", "caught-up");
|
||||
fetch("/home.txt")
|
||||
.then(function (res) {
|
||||
if (!res.ok) throw new Error();
|
||||
return res.text();
|
||||
})
|
||||
.then(function (html) {
|
||||
contentEl.innerHTML = html;
|
||||
mountGreeting(contentEl);
|
||||
applyTheme();
|
||||
window.scrollTo(0, 0);
|
||||
})
|
||||
.catch(function () {
|
||||
contentEl.innerHTML = "<h1>Home</h1><p>Create /home.txt to customize this page.</p>";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var tocList = document.getElementById("toc-list");
|
||||
@@ -1346,19 +1624,8 @@ function loadPage() {
|
||||
var route = resolveRoute(window.location.pathname);
|
||||
renderNavTabs(route.tab);
|
||||
renderSidebar(route.tab, route.item);
|
||||
|
||||
document.title = route.item.title + " - Vyla";
|
||||
|
||||
var contentEl = document.getElementById("main-content");
|
||||
|
||||
document.body.classList.remove("is-404");
|
||||
|
||||
var route = resolveRoute(window.location.pathname);
|
||||
renderNavTabs(route.tab);
|
||||
renderSidebar(route.tab, route.item);
|
||||
|
||||
document.title = route.item.title + " - Vyla";
|
||||
|
||||
var contentEl = document.getElementById("main-content");
|
||||
|
||||
if (route.is404) {
|
||||
@@ -1414,6 +1681,7 @@ function loadPage() {
|
||||
})
|
||||
.then(function (md) {
|
||||
if (contentEl) {
|
||||
updatePageSEO(route, md);
|
||||
var hasApiToken = md.indexOf("YOUR_API_URL") !== -1;
|
||||
contentEl.innerHTML = parseMarkdown(personalize(md));
|
||||
|
||||
@@ -1629,7 +1897,10 @@ function onSearchKey(e) {
|
||||
|
||||
function buildSearch() {
|
||||
var trigger = document.getElementById("search-trigger");
|
||||
if (trigger) trigger.innerHTML = icon("search") + "<span>Search...</span><kbd>Ctrl K</kbd>";
|
||||
if (trigger) {
|
||||
trigger.innerHTML = icon("search") + "<span>Search...</span><kbd>Ctrl K</kbd>";
|
||||
trigger.setAttribute("aria-label", "Search... (Ctrl+K)");
|
||||
}
|
||||
var modal = document.createElement("div");
|
||||
modal.id = "search-modal";
|
||||
modal.innerHTML =
|
||||
|
||||
@@ -1,44 +1,143 @@
|
||||
# Our Architecture
|
||||
# Architecture & System Design
|
||||
|
||||
<p class="page-description">How Vyla Home, SDK, and Stream API relate, and where each responsibility belongs.</p>
|
||||
<p class="page-description">Deep architectural breakdown of the Vyla Ecosystem: component boundaries, communication protocols, video delivery pipelines, and failover topologies.</p>
|
||||
|
||||
## The Vyla stack
|
||||
## High-Level Architecture
|
||||
|
||||
Vyla separates local application management, provider resolution, and client-facing delivery. That separation lets each layer be used on its own while keeping the integration points clear.
|
||||
The Vyla architecture cleanly separates **presentation**, **gateway mediation**, and **content resolution**. This decoupling ensures that any individual layer can be replaced, scaled, or run independently.
|
||||
|
||||
### Vyla Home
|
||||
| Architecture Tier | Components | Protocols & Interfaces | Core Responsibilities |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Presentation Tier** | Vyla Home, Vyla Go, Vyla TV, Third-Party Web Players | SSE (HTTP/1.1), REST (JSON), HLS / DASH | Render user interface, manage local state, execute video playback via HLS.js or ExoPlayer Media3. |
|
||||
| **Gateway Tier** | Stream API (`server.js`) | Node.js Cluster IPC, HTTP/REST, SSE, Reverse Proxy | Expose public endpoints, manage 1,500-entry in-memory cache, rewrite M3U8 playlists & AES keys, strip 120-byte obfuscation masks. |
|
||||
| **Resolver Tier** | Vyla SDK (`@vyla-entertainment/sdk`) | Direct ESM function calls, HTTP/HTTPS scrapers | Scrape 60+ upstream providers concurrently, enforce random jitter delays, run two-phase stream validation. |
|
||||
| **Upstream Hosts** | Public Third-Party CDNs & Video Hosts | Encrypted TLS/HTTPS, M3U8, MP4 | Deliver raw media segments and video assets. |
|
||||
|
||||
Home is responsible for a desktop session: local configuration, encrypted local credentials, update coordination, managed processes, and the embedded interface. It is the right layer for launching and supervising a local experience.
|
||||
---
|
||||
|
||||
### Vyla SDK
|
||||
## Component Boundaries & Responsibilities
|
||||
|
||||
The SDK contains the provider-facing work: source configuration, stream resolution, subtitle retrieval, manga discovery and page resolution, download lookup, and health probes. It is the right layer when your Node.js application needs to decide how requests are authorized, cached, selected, and returned.
|
||||
### 1. Vyla Home (Desktop)
|
||||
- **Runtime Environment**: Built on [Tauri v2](https://v2.tauri.app/) using Rust and an embedded Node.js sidecar service.
|
||||
- **IPC Protocol**: Communicates with the sidecar process via standard input/output (`stdin`/`stdout`) passing JSON-RPC messages.
|
||||
- **Responsibilities**:
|
||||
- Manages application lifecycle, window management, and native system tray.
|
||||
- Automatically checks, verifies cryptographic signatures (`update-signing.pem`), and extracts delta updates from GitLab releases.
|
||||
- Houses the local `ServiceManager` and `Patcher` for offline and local service fallback.
|
||||
- Dispatches Discord Rich Presence activity updates (`discord-rpc.js`).
|
||||
- Persists configuration and cache to platform-specific AppData locations.
|
||||
|
||||
### Stream API
|
||||
### 2. Vyla Go (Mobile)
|
||||
- **Runtime Environment**: Tauri v2 for Android (`tauri-android`) targeting ARM64 (`aarch64`) and x86_64 architectures.
|
||||
- **Responsibilities**:
|
||||
- Touch-optimized interface utilizing WebGL hardware shaders and glassmorphism styling.
|
||||
- Automated resource management: fetches, unpacks, and hot-patches runtime assets directly from remote releases without requiring full APK reinstallations for minor script updates.
|
||||
- Background playback coordination and media notification integration.
|
||||
|
||||
Stream API wraps the SDK in a self-hosted HTTP service. Its movie and TV endpoints use Server-Sent Events so metadata can arrive first and usable sources can be delivered progressively. It also provides subtitle, download, health, test, and optional debug routes.
|
||||
### 3. Vyla TV (Leanback)
|
||||
- **Runtime Environment**: Native Android application written in Kotlin utilizing Jetpack Compose and Google's [ExoPlayer (Media3)](https://developer.android.com/media/media3/exoplayer).
|
||||
- **Responsibilities**:
|
||||
- Strictly optimized for D-pad remote controls, 10-foot viewing distances, and Android TV / Google TV operating systems.
|
||||
- Native HLS stream parsing, hardware-accelerated 4K/HDR decoding, playback speed manipulation, audio track selection, and subtitle overlay.
|
||||
- Local client storage for user favorites, watch progress resume, and TMDB catalog browsing.
|
||||
|
||||
## Common deployment patterns
|
||||
### 4. Stream API (Gateway)
|
||||
- **Runtime Environment**: Node.js microservice utilizing the native `cluster` module for multi-core process forking.
|
||||
- **Responsibilities**:
|
||||
- Acts as an HTTP/SSE bridge for clients that cannot run the Node.js SDK directly (e.g. mobile apps, TV apps, browser frontends).
|
||||
- Emits real-time playback events (`meta`, `source`, `done`) over Server-Sent Events.
|
||||
- Proxies HLS media streams (`/api?url=...`), automatically rewriting playlist chunk URIs and AES encryption key endpoints to bypass browser CORS and network restrictions.
|
||||
- Strips obfuscation masks (e.g. 120-byte fake PNG/JPEG headers used by ByteDance/TikTok-based content delivery networks).
|
||||
- Caches verified results for 5 minutes (TTL = 300,000 ms) in a shared inter-process memory map.
|
||||
|
||||
| Pattern | Best for | Key decision |
|
||||
| --- | --- | --- |
|
||||
| Home only | a managed local desktop experience | Home owns the lifecycle of bundled local services |
|
||||
| App + SDK | a Node.js backend with a custom product API | your app owns authentication, policy, and response design |
|
||||
| Player + Stream API | browser or mobile clients that need a stable HTTP contract | deploy Stream API and place it behind TLS when exposed remotely |
|
||||
| Home + Stream API | a local player backed by a local service | keep the service bound and access-controlled for the local environment |
|
||||
### 5. Vyla SDK (`@vyla-entertainment/sdk`)
|
||||
- **Runtime Environment**: Pure ES Module Node.js library.
|
||||
- **Responsibilities**:
|
||||
- Houses the core provider scraping modules (60+ scrapers in `src/sources/`).
|
||||
- Implements jitter scheduling, retry backoff, and custom header injection (`User-Agent`, `Referer`, `Origin`).
|
||||
- Performs two-phase verification on resolved candidate URLs.
|
||||
- Houses the Manga resolver engine leveraging `node-tls-client` to bypass TLS fingerprint blocking on `atsu.moe`.
|
||||
|
||||
## Data flow for playback
|
||||
---
|
||||
|
||||
1. A client provides a TMDB media ID (plus season and episode for TV).
|
||||
2. Stream API, or your own SDK-based backend, asks active providers for candidates.
|
||||
3. Candidates are checked and successful sources are returned progressively.
|
||||
4. The player begins with the first compatible candidate and keeps later candidates as fallbacks.
|
||||
5. Subtitle and download lookups are separate operations; neither should block first playback.
|
||||
## Playback Data Flow: Step-by-Step
|
||||
|
||||
Provider availability varies by source and network environment. Build the player experience around fallbacks and empty-result states, as described in [Error handling](/guides/error-handling).
|
||||
Understanding how a media stream is discovered, verified, and played:
|
||||
|
||||
## Data flow for manga
|
||||
1. **Client Establishes SSE Connection**
|
||||
The viewer selects a title (e.g., TMDB ID `155`), and the client initiates an HTTP Server-Sent Events handshake:
|
||||
|
||||
```http
|
||||
GET /movie?id=155 HTTP/1.1
|
||||
Host: api.vyla.cc
|
||||
Accept: text/event-stream
|
||||
```
|
||||
|
||||
1. A client provides a search term, or direct manga identifier.
|
||||
2. The SDK queries the manga provider for title details, metadata, and chapter listings.
|
||||
3. When reading a chapter, the SDK resolves ordered page assets with dimensions and aspect ratios for responsive layout rendering.
|
||||
2. **Stream API Emits Initial Metadata**
|
||||
Stream API validates the identifier, queries TMDB for metadata and artwork, queries subtitle providers, and immediately delivers a `meta` event:
|
||||
|
||||
```json
|
||||
data: {"type": "meta", "meta": {"title": "The Dark Knight", "year": 2008}, "subtitles": [{"lang": "English", "url": "..."}]}
|
||||
```
|
||||
|
||||
3. **SDK Initiates Concurrent Provider Queries**
|
||||
The SDK filters active provider scrapers from configuration, applies randomized per-source jitter (0–500 ms) to avoid burst-rate triggers, and initiates upstream HTTP scraping requests concurrently.
|
||||
|
||||
4. **Two-Phase Stream Verification**
|
||||
- **Phase A (`verifyStream`)**: Executes an HTTP `HEAD` or range `GET` request against candidate stream URLs. Responses with status $\ge 400$ are discarded immediately.
|
||||
- **Phase B (`verifyPlayable`)**: Downloads and parses the master HLS manifest (`#EXT-X-STREAM-INF`), validates chunk resolution, and tests segment availability.
|
||||
|
||||
5. **Progressive Source Delivery**
|
||||
As each provider scraper completes two-phase verification, Stream API immediately pushes a `source` event over the active SSE stream:
|
||||
|
||||
```json
|
||||
data: {"type": "source", "source": "vidlink", "url": "https://api.vyla.cc/api?url=...", "quality": "1080p", "type": "hls"}
|
||||
```
|
||||
|
||||
6. **Instant Client Playback**
|
||||
The client media player (ExoPlayer, HLS.js, or AVPlayer) immediately begins playback upon receiving the first valid source event (typically within 3–8 seconds). Subsequent incoming sources are enqueued into a client-side fallback list.
|
||||
|
||||
7. **Stream Completion**
|
||||
Once all configured source scrapers have either returned verified streams or reached their timeout thresholds (typically 15–25 seconds), Stream API emits the completion event and closes the connection:
|
||||
|
||||
```json
|
||||
data: {"type": "done", "total": 4}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## The HLS Proxy & De-Obfuscation Mechanics
|
||||
|
||||
Many upstream streaming hosts employ strict CORS restrictions, IP-bound tokens, or binary header obfuscation. Vyla resolves this transparently:
|
||||
|
||||
### Playlist & Key Rewriting
|
||||
When a stream URL is proxied through `/api?url=<URL>&<param>=1`, the gateway:
|
||||
1. Requests the upstream playlist with provider-mandated `Referer` and `Origin` headers.
|
||||
2. Intercepts `#EXT-X-KEY:METHOD=AES-128,URI="..."` and rewrites the key URI to route through the proxy.
|
||||
3. Rewrites all chunk lines (`.ts` / `.m4s`) so that browser audio/video fetch calls pass through the proxy with valid `Access-Control-Allow-Origin: *` headers.
|
||||
|
||||
### Byte-Mask Stripping
|
||||
Certain modern providers mask `.ts` video chunks as fake image files (`image/png`, `image/jpeg`, `letsgocdn`, or TikTok CDN formats) by prepending a 120-byte image header before the MPEG-TS sync byte (`0x47`):
|
||||
```
|
||||
[ 120-byte Fake PNG Header (0x89 0x50 0x4E 0x47...) ] + [ MPEG-TS Payload (0x47...) ]
|
||||
```
|
||||
When Stream API detects these signatures via regular expressions (`TIKTOK_REGEX`, `STRIP_REGEX`), it slices off the first 120 bytes:
|
||||
```javascript
|
||||
const stripped = (full[0] === 0x89 || full[0] === 0xFF || full[0] === 0x00)
|
||||
? full.subarray(120)
|
||||
: full;
|
||||
```
|
||||
The resulting clean MPEG-TS stream is delivered to players with `Content-Type: video/MP2T`, enabling seamless playback across all platforms.
|
||||
|
||||
---
|
||||
|
||||
## Deployment Topologies
|
||||
|
||||
Choose the deployment architecture that fits your infrastructure:
|
||||
|
||||
| Topology | Components Used | Best For | Security & Networking |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **All-in-One Desktop** | Vyla Home | Casual users wanting local playback on a single PC | Fully local, runs on localhost, zero open ports |
|
||||
| **Living Room TV** | Vyla TV + Hosted Stream API | Smart TVs, Chromecast, NVIDIA Shield | TV connects over LAN/WAN to a secure Stream API gateway |
|
||||
| **Private Family Server** | Stream API on VPS/NAS | Multi-device households streaming across phones, PCs, and TVs | Run behind Nginx/Caddy with TLS, enable API key protection |
|
||||
| **Custom Platform** | SDK in Node.js backend | Developers building bespoke streaming web applications | Complete control over DB, auth, caching, and playback logic |
|
||||
|
||||
@@ -1,54 +1,82 @@
|
||||
# Vyla Entertainment
|
||||
|
||||
<p class="page-description">A community-built media project focused on freedom, privacy, and giving people control over their own experience.</p>
|
||||
<p class="page-description">A community-built, decentralized media ecosystem and developer platform focused on privacy, user sovereignty, and self-hosted streaming infrastructure.</p>
|
||||
|
||||
<img class="vyla-banner" src="/src/assets/images/devices.png" alt="A collage of films and shows available through a Vyla experience" loading="lazy" fetchpriority="low" />
|
||||
<img class="vyla-banner" src="/src/assets/images/devices.webp" alt="The Vyla Ecosystem across Desktop, Android, and Android TV devices" loading="lazy" decoding="async" fetchpriority="low" />
|
||||
|
||||
## About Vyla
|
||||
## What is Vyla?
|
||||
|
||||
Vyla is a community-built media project created around a simple idea: **people should have control over how they experience their media.**
|
||||
**Vyla** is an open-source, modular media ecosystem built on a simple foundation: **users and developers should have absolute sovereignty over their media experience.**
|
||||
|
||||
We started Vyla because we wanted something clean, private, and easy to use without relying on a single company or centralized service. What began as a small project has grown into a collection of applications and tools for desktop, Android, Android TV, and more.
|
||||
Unlike centralized streaming platforms that lock users into closed walled gardens, proprietary codecs, and telemetry tracking, Vyla separates the entire media stack into distinct, reusable layers:
|
||||
|
||||
Vyla is not a streaming service, and we do not host or provide the media itself. Instead, we build the software that brings the experience together while leaving control in the hands of the person using it.
|
||||
1. **Client Frontends**: Native applications for Desktop (Windows, macOS, Linux), Mobile (Android), and Television (Android TV / Google TV).
|
||||
2. **Scraper & Resolver SDK**: A high-performance Node.js scraping engine (`@vyla-entertainment/sdk`) aggregating over 60 distributed upstream providers with automatic jitter buffering and HLS playback verification.
|
||||
3. **Stream API Gateway**: A self-hostable HTTP and Server-Sent Events (SSE) microservice proxy delivering low-latency progressive playback manifests, subtitle tracks, and media downloads.
|
||||
|
||||
## Where we come from
|
||||
:::callout-note
|
||||
**Core Policy**: Vyla does not host, upload, store, or distribute video files or copyrighted media. The ecosystem provides client applications and self-hosted scraping tools that resolve publicly accessible links on demand.
|
||||
:::
|
||||
|
||||
Vyla started as a small web project focused on creating a better way to discover and watch movies and shows. Over time, it grew into a broader project with applications for different platforms alongside the tools that power them.
|
||||
---
|
||||
|
||||
We care about keeping Vyla open, accessible, and community-driven. Our project has also been shared by communities such as **FMHY** and **r/Piracy**, where people interested in alternative and self-hosted media solutions have discovered Vyla.
|
||||
## Ecosystem Architecture at a Glance
|
||||
|
||||
We are proud to be part of a wider community of people who build, share, and explore alternatives to traditional media platforms.
|
||||
The Vyla Ecosystem is designed around modularity. Each component functions autonomously or seamlessly interconnects with other components:
|
||||
|
||||
## What we make
|
||||
| Layer | Components | Core Capabilities |
|
||||
| :--- | :--- | :--- |
|
||||
| **User Interfaces** | **Vyla Home**, **Vyla Go**, **Vyla TV** | Desktop Tauri v2 with embedded sidecar, Android mobile client with WebGL shaders, and native Android TV (Jetpack Compose + ExoPlayer). |
|
||||
| **Distribution Gateway** | **Stream API** | Node.js cluster gateway, SSE progressive source emitter, M3U8 playlist & key rewriting proxy, and 120-byte fake-image obfuscation stripper. |
|
||||
| **Resolver Engine** | **Vyla SDK** (`@vyla-entertainment/sdk`) | 60+ modular source scrapers, two-phase stream verification (`verifyStream` / `verifyPlayable`), manga TLS-fingerprinted extraction, and subtitle normalization. |
|
||||
|
||||
Vyla is made up of several projects designed for different ways of using the platform.
|
||||
---
|
||||
|
||||
| Platform | Description | Link |
|
||||
| --- | --- | --- |
|
||||
| Vyla Home | Desktop app | [Learn more](/products/home) |
|
||||
| Vyla Go | Android app | [Learn more](/products/go) |
|
||||
| Vyla TV | Android TV app | [Learn more](/products/tv) |
|
||||
| Vyla SDK | Developer SDK | [Learn more](/sdk) |
|
||||
| Stream API | Self-hosted API | [Learn more](/stream-api/overview) |
|
||||
## The Vyla Component Matrix
|
||||
|
||||
## Made for people, not platforms
|
||||
| Component | Platform / Tech Stack | Primary Responsibility | Documentation |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Vyla Home** | Tauri v2, Node.js Sidecar, Rust | Full desktop control center, automatic updates, Discord RPC | [Vyla Home Guide](/products/home) |
|
||||
| **Vyla Go** | Tauri v2 Mobile, WebGL Shaders | Android mobile runtime, automated payload download, background playback | [Vyla Go Guide](/products/go) |
|
||||
| **Vyla TV** | Kotlin, Jetpack Compose, ExoPlayer | 10-foot Leanback television UI, D-pad navigation, 4K/HDR streaming | [Vyla TV Guide](/products/tv) |
|
||||
| **Stream API** | Node.js, Cluster Workers, SSE | Self-hosted HTTP streaming gateway, M3U8 proxy, segment stripper | [Stream API Overview](/stream-api/overview) |
|
||||
| **Vyla SDK** | Node.js (ESM), TLS Client, Cheerio | Multi-provider video resolver, subtitle extractor, manga engine | [SDK Overview](/sdk) |
|
||||
| **Custom Player** | HLS.js, Video.js, HTML5 | Custom web and native playback integration layer | [Build a Player](/products/player) |
|
||||
|
||||
We believe software should give people more control, not less.
|
||||
---
|
||||
|
||||
That means keeping Vyla local and self-hostable, avoiding unnecessary centralized dependencies, and allowing developers and users to decide how they want to use the project.
|
||||
## Core Principles
|
||||
|
||||
Vyla can be as simple as a TV or mobile app, a desktop application, or the foundation for a completely custom media experience. **You decide how much of Vyla you use and where it runs.**
|
||||
- **Zero Centralized Telemetry**: Vyla frontends never require mandatory tracking, user account creation, or proprietary telemetry services.
|
||||
- **Failover by Design**: Video resolution queries multiple providers concurrently with randomized jitter. If one upstream host goes down or throttles, the client seamlessly shifts to the next viable candidate.
|
||||
- **Progressive Delivery**: Stream API delivers media metadata instantly, followed by source candidates streamed over Server-Sent Events (SSE) as each scraper resolves. Players start playback within 3 to 8 seconds without waiting for the slowest source.
|
||||
- **Local Sovereignty**: Every part of Vyla can be self-hosted locally on your laptop, home server, NAS, or virtual private server (VPS).
|
||||
|
||||
## Start exploring
|
||||
---
|
||||
|
||||
- **On desktop?** See [Vyla Home](/products/home).
|
||||
- **On Android?** See [Vyla Go](/products/go).
|
||||
- **Using Android TV?** See [Vyla TV](/products/tv).
|
||||
- **Building something with Vyla?** Start with the [SDK quickstart](/sdk/vyla-sdk/quickstart).
|
||||
- **Building your own client?** See the [Stream API](/stream-api/overview).
|
||||
- **Want to understand how everything fits together?** Visit [Our Architecture](/start-here/architecture).
|
||||
## Where to Begin
|
||||
|
||||
:::callout-info
|
||||
Vyla is self-hosted software and does not host or distribute media. Please respect applicable laws, upstream terms, and rights-holder requests. See the [DMCA policy](/misc/project/dmca) for removal information.
|
||||
:::
|
||||
Select your path based on your role:
|
||||
|
||||
:::code-group
|
||||
<!-- tab: End Users -->
|
||||
- **On Desktop (Windows, macOS, Linux)?** Download and run [Vyla Home](/products/home).
|
||||
- **On Android Phone or Tablet?** Install the native [Vyla Go APK](/products/go).
|
||||
- **On Android TV, Google TV, or Fire TV?** Install the [Vyla TV APK](/products/tv).
|
||||
- **Need help with setup?** Read the [Troubleshooting Guide](/resources/troubleshooting).
|
||||
|
||||
<!-- tab: Backend Developers -->
|
||||
- **Embedding scrapers in a Node.js project?** Check out the [SDK Quickstart](/sdk/vyla-sdk/quickstart) and [Complete API Reference](/sdk/vyla-sdk/reference).
|
||||
- **Want to author a new provider?** Follow the [Contributing a Source Guide](/sdk/vyla-sdk/contributing).
|
||||
- **Integrating Manga discovery?** Read the [Manga SDK Documentation](/sdk/vyla-sdk/manga).
|
||||
|
||||
<!-- tab: Frontend & App Developers -->
|
||||
- **Connecting a web or mobile player?** Read [Paths & Playback Events](/stream-api/reference/paths) and the [Client Integration Guide](/guides/integrations).
|
||||
- **Building fallback strategies?** Study the [Error Handling Guide](/guides/error-handling).
|
||||
- **Self-hosting the streaming gateway?** Read the [Self-Hosting Guide](/stream-api/deployment/self-hosting).
|
||||
|
||||
---
|
||||
|
||||
## Legal Notice & DMCA Compliance
|
||||
|
||||
Vyla is self-hosted software intended for personal educational and development use. All stream resolution and scraper logic interacts with publicly available web sources. Rights holders can review our takedown policies in the [DMCA Policy](/misc/project/dmca).
|
||||
|
||||
125
stream-api/authentication/index.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# API Keys & Authentication
|
||||
|
||||
<p class="page-description">Managing API keys, rate limits, SQLite database synchronization, and authorization headers in Stream API.</p>
|
||||
|
||||
## Overview
|
||||
|
||||
Stream API includes a built-in cryptographic key management and rate-limiting system backed by SQLite. When enabled, client applications must supply an authenticated key to establish Server-Sent Events connections or request proxied media streams.
|
||||
|
||||
---
|
||||
|
||||
## Key Structure & Prefixes
|
||||
|
||||
API keys are 32-character hexadecimal strings generated with a specific prefix indicating the client tier:
|
||||
|
||||
| Key Prefix | Key Type | Intended Use Case | Default Rate Limit |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **`pk_`** | **Partner Key** | Verified high-volume partner frontends and applications | 1,000 req / minute |
|
||||
| **`sk_`** | **Standard Key** | Private self-hosters and internal client instances | 500 req / minute |
|
||||
| **`pub_`** | **Public Key** | Anonymous public trial access or open community frontends | 10 req / minute |
|
||||
|
||||
---
|
||||
|
||||
## Generating API Keys via CLI
|
||||
|
||||
The Stream API repository includes a CLI tool (`add-key.mjs`) to generate and insert keys into the active SQLite database:
|
||||
|
||||
```bash
|
||||
# Syntax: node add-key.mjs <type> <rate_limit_per_min> <client_name>
|
||||
|
||||
# 1. Create a Standard Key for a personal desktop client:
|
||||
node add-key.mjs standard 500 my-desktop-client
|
||||
|
||||
# 2. Create a Partner Key for a high-traffic community player:
|
||||
node add-key.mjs partner 1000 partner-frontend
|
||||
|
||||
# 3. Create a restricted Public Key:
|
||||
node add-key.mjs public 10 public-guest
|
||||
```
|
||||
|
||||
### CLI Output Example
|
||||
```
|
||||
Generated Key: sk_8f14b2d39a7e4c5b10e9f8a7b6c5d4e3
|
||||
Type: standard
|
||||
Limit: 500 req/min
|
||||
Client: my-desktop-client
|
||||
Status: Active
|
||||
```
|
||||
The key is printed once upon creation. Copy and deliver it to your client application securely.
|
||||
|
||||
---
|
||||
|
||||
## Managing Keys via JSON Database Export
|
||||
|
||||
For automated deployments or bulk key management, you can export the database to a JSON file:
|
||||
|
||||
### 1. Export Database
|
||||
```bash
|
||||
node export-db.mjs
|
||||
```
|
||||
This dumps the active SQLite database tables into `db.json`:
|
||||
```json
|
||||
{
|
||||
"keys": [
|
||||
{
|
||||
"key": "sk_8f14b2d39a7e4c5b10e9f8a7b6c5d4e3",
|
||||
"type": "standard",
|
||||
"limit": 500,
|
||||
"client": "my-desktop-client",
|
||||
"active": 1,
|
||||
"created_at": "2026-11-14T12:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Modify and Re-Import
|
||||
Edit `db.json` to add, revoke, or modify limits, then commit the changes back to SQLite:
|
||||
```bash
|
||||
node update-db.mjs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Client Authorization Headers
|
||||
|
||||
Clients can authenticate requests by passing the key in one of the following locations:
|
||||
|
||||
### 1. Authorization Header (Recommended)
|
||||
```http
|
||||
GET /movie?id=155 HTTP/1.1
|
||||
Host: your-api.example.com
|
||||
Authorization: Bearer sk_8f14b2d39a7e4c5b10e9f8a7b6c5d4e3
|
||||
```
|
||||
|
||||
### 2. Custom API Key Header
|
||||
```http
|
||||
X-API-Key: sk_8f14b2d39a7e4c5b10e9f8a7b6c5d4e3
|
||||
```
|
||||
|
||||
### 3. Query Parameter (for EventSource & HTML5 Video)
|
||||
Because native browser `EventSource` and `<video>` tags do not support custom request headers, you can pass the key as a query parameter:
|
||||
```http
|
||||
GET /movie?id=155&key=sk_8f14b2d39a7e4c5b10e9f8a7b6c5d4e3 HTTP/1.1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rate Limit Headers
|
||||
|
||||
When requests are evaluated, Stream API returns standard rate-limiting headers:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
X-RateLimit-Limit: 500
|
||||
X-RateLimit-Remaining: 498
|
||||
X-RateLimit-Reset: 1731614460
|
||||
```
|
||||
|
||||
If a client exceeds their tier quota, Stream API responds with HTTP 429:
|
||||
```json
|
||||
{
|
||||
"error": "Rate limit exceeded. Please retry in 32 seconds.",
|
||||
"retry_after": 32
|
||||
}
|
||||
```
|
||||
@@ -1,121 +1,194 @@
|
||||
# Self-host Stream API
|
||||
# Self-Hosting Stream API
|
||||
|
||||
<p class="page-description">Deploy the optional Stream API when your clients need an HTTP/SSE layer.</p>
|
||||
<p class="page-description">Complete production self-hosting guide for Stream API: Docker, Node.js systemd, PM2, and Nginx reverse proxy configuration with SSE streaming support.</p>
|
||||
|
||||
## Overview
|
||||
|
||||
Stream API is a **self-hosted Node.js** application that you run on infrastructure you control. It does not provide a public hosted instance. The service runs as a Node.js process, uses in-memory caching, and can fork workers through Node's built-in `cluster` module. No database is required by the service itself.
|
||||
Stream API is designed to be completely self-hostable on any Linux virtual private server (VPS), home server, or Docker container environment. Running your own instance gives you private access, eliminates shared IP rate limits, and allows you to tune worker concurrency to your hardware.
|
||||
|
||||
:::callout-warning
|
||||
Stream API is self-hosted only. There is no public Vyla streaming endpoint. You only need it when your architecture requires an HTTP/SSE layer; Node.js applications can use [Vyla SDK](/sdk) directly.
|
||||
:::
|
||||
---
|
||||
|
||||
## Requirements
|
||||
## Environment Variables
|
||||
|
||||
- Node.js 18 or later
|
||||
- A TMDB API key if you want title validation and metadata lookups
|
||||
Configure your environment variables via a `.env` file in the project root:
|
||||
|
||||
## Environment variables
|
||||
| Variable | Required | Default | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `PORT` | Optional | `7860` | HTTP port on which the server binds. |
|
||||
| `TMDB_API_KEY` | **Recommended** | `""` | The Movie Database v3 API key. Required for scrapers that resolve metadata via IMDb ID or episode titles. |
|
||||
| `WORKER_COUNT` | Optional | `os.cpus().length` | Number of worker processes to fork in cluster mode. |
|
||||
| `ENABLE_DEBUG_ROUTE` | Optional | `false` | Enables deep diagnostic traces at `/debug/:source`. Keep `false` in production. |
|
||||
| `GA_MEASUREMENT_ID` | Optional | `""` | Google Analytics 4 Measurement ID for private usage telemetry. |
|
||||
| `GA_API_SECRET` | Optional | `""` | Google Analytics 4 Measurement Protocol API Secret. |
|
||||
| `FALLBACK_BASE` | Optional | `""` | Secondary Stream API instance URL used for failover redirects. |
|
||||
| `IS_HF` | Optional | `false` | Enables HuggingFace Spaces compatibility constraints. |
|
||||
|
||||
Create a `.env` file in the project root:
|
||||
---
|
||||
|
||||
## Deployment Option 1: Docker (Recommended)
|
||||
|
||||
### 1. Dockerfile
|
||||
Create a `Dockerfile` in the project directory:
|
||||
|
||||
```dockerfile
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install native dependencies required for TLS and encryption
|
||||
RUN apk add --no-cache python3 make g++
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
COPY . .
|
||||
|
||||
ENV PORT=7860
|
||||
EXPOSE 7860
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
### 2. Docker Compose
|
||||
Create a `docker-compose.yml` file:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
stream-api:
|
||||
build: .
|
||||
container_name: vyla-stream-api
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:7860:7860"
|
||||
environment:
|
||||
- PORT=7860
|
||||
- TMDB_API_KEY=your_tmdb_api_key_here
|
||||
- WORKER_COUNT=4
|
||||
- ENABLE_DEBUG_ROUTE=false
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
```
|
||||
|
||||
Start the container in detached mode:
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment Option 2: Direct Node.js & Systemd
|
||||
|
||||
### 1. Installation
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://gitlab.com/vyla-entertainment/stream-api.git /opt/stream-api
|
||||
cd /opt/stream-api
|
||||
|
||||
# Install dependencies
|
||||
npm ci --omit=dev
|
||||
```
|
||||
|
||||
### 2. Create Systemd Service
|
||||
Create `/etc/systemd/system/stream-api.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Vyla Stream API Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=www-data
|
||||
WorkingDirectory=/opt/stream-api
|
||||
ExecStart=/usr/bin/node /opt/stream-api/server.js
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
Environment=NODE_ENV=production
|
||||
Environment=PORT=7860
|
||||
Environment=TMDB_API_KEY=your_tmdb_api_key_here
|
||||
Environment=WORKER_COUNT=4
|
||||
|
||||
# Security Hardening
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Enable and start the service:
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now stream-api
|
||||
sudo systemctl status stream-api
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reverse Proxy: Nginx Configuration
|
||||
|
||||
Because Stream API utilizes **Server-Sent Events (SSE)** and large **HLS video streams**, your reverse proxy must disable proxy buffering. Standard buffering will delay SSE events until the buffer fills, breaking progressive playback.
|
||||
|
||||
Create an Nginx configuration at `/etc/nginx/sites-available/stream-api.conf`:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
server_name stream-api.yourdomain.com;
|
||||
|
||||
# Maximum upload size for DB syncs
|
||||
client_max_body_size 20M;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:7860;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# Mandatory for Server-Sent Events (SSE)
|
||||
proxy_set_header Connection '';
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
chunked_transfer_encoding off;
|
||||
|
||||
# Keep-Alive & Timeouts for long media streams
|
||||
proxy_read_timeout 90s;
|
||||
proxy_send_timeout 90s;
|
||||
|
||||
# Forward Client Identifiers for Geo-Token Verification
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
listen 80;
|
||||
}
|
||||
```
|
||||
|
||||
Enable the configuration and obtain a free SSL certificate via Certbot:
|
||||
```bash
|
||||
sudo ln -s /etc/nginx/sites-available/stream-api.conf /etc/nginx/sites-enabled/
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
|
||||
# Install Let's Encrypt SSL
|
||||
sudo certbot --nginx -d stream-api.yourdomain.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verifying Your Deployment
|
||||
|
||||
Once deployed, verify the installation by testing the `/health` endpoint:
|
||||
|
||||
```bash
|
||||
TMDB_API_KEY=
|
||||
PORT=7860
|
||||
WORKER_COUNT=1
|
||||
ENABLE_DEBUG_ROUTE=false
|
||||
PROXY_STREAMS=false
|
||||
PROXY_URL=
|
||||
|
||||
GA_MEASUREMENT_ID=
|
||||
GA_API_SECRET=
|
||||
curl -i https://stream-api.yourdomain.com/health
|
||||
```
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">Variable reference</div>
|
||||
<div class="card-body">
|
||||
|
||||
| Variable | Required | Description |
|
||||
| --- | --- | --- |
|
||||
| `TMDB_API_KEY` | Yes | Enables TMDB-based ID validation, metadata lookups, and anime detection. Some sources will not work without it. |
|
||||
| `GA_MEASUREMENT_ID` / `GA_API_SECRET` | No | Enables Google Analytics event reporting. Leave blank to disable. |
|
||||
| `PORT` | No | Port the server listens on. Defaults to `7860`. |
|
||||
| `WORKER_COUNT` | No | Number of cluster workers to fork. Defaults to `1` locally. |
|
||||
| `ENABLE_DEBUG_ROUTE` | No | Set to `true` to enable `/api/debug/:id`. Keep this `false` in production - it exposes upstream request/response headers and raw source output. |
|
||||
| `PROXY_STREAMS` | No | Set to `true` to route stream playback through your own server's `/api` proxy endpoint instead of direct URLs. |
|
||||
| `PROXY_URL` | No | An external proxy base URL to use instead of the built-in `/api` proxy. Only used if `PROXY_STREAMS` is enabled. |
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Installing and running
|
||||
|
||||
```bash
|
||||
git clone https://gitlab.com/vyla-entertainment/stream-api
|
||||
cd stream-api
|
||||
npm install
|
||||
node server.js
|
||||
```
|
||||
|
||||
The server listens on:
|
||||
|
||||
```
|
||||
http://localhost:7860
|
||||
```
|
||||
|
||||
Visit `/health` to confirm it is running and check which sources are currently reachable from your host.
|
||||
|
||||
## Running with multiple workers
|
||||
|
||||
Setting `WORKER_COUNT` above `1` forks additional worker processes via `cluster`.
|
||||
|
||||
The primary process holds a shared in-memory cache (capped at 1500 entries, pruned every 30 seconds) and relays cache reads/writes between workers over IPC, allowing cached stream results to be shared rather than duplicated.
|
||||
|
||||
```bash
|
||||
WORKER_COUNT=4
|
||||
node server.js
|
||||
```
|
||||
|
||||
Each worker independently loads source modules and handles outbound requests. Only shared cache operations and concurrency limits are coordinated through the primary process.
|
||||
|
||||
## Network considerations
|
||||
|
||||
Several sources are documented in `config.js` as blocked on datacenter IPs. This commonly affects cloud hosting providers and VPS environments.
|
||||
|
||||
If a source's `getStream` consistently returns nothing from your server but works from a residential connection, the cause is usually upstream blocking based on IP reputation. For the best experience, run Vyla API on a residential connection or use a residential proxy.
|
||||
|
||||
When running on a local desktop environment, you should have minimal issues with source availability since you're using a residential IP address.
|
||||
|
||||
## Reverse proxy / TLS
|
||||
|
||||
The server runs over plain HTTP. For public deployments, place it behind a reverse proxy such as Caddy, Nginx, or Cloudflare Tunnel to terminate TLS and forward requests to the configured `PORT`.
|
||||
|
||||
CORS is already configured at the application layer with:
|
||||
|
||||
```
|
||||
Access-Control-Allow-Origin: *
|
||||
```
|
||||
|
||||
No additional CORS configuration is required on the proxy.
|
||||
|
||||
## Verifying your deployment
|
||||
|
||||
Run these checks in order:
|
||||
|
||||
1. `GET /health`
|
||||
|
||||
Confirms the process is running and reports per-source reachability from your host.
|
||||
|
||||
2. `GET /api/test/155?source=vidrock`
|
||||
|
||||
Runs a real source resolution using a known TMDB movie ID and returns whether the source succeeded.
|
||||
|
||||
3. `GET /api/debug/155?source=vidrock`
|
||||
|
||||
Only available when `ENABLE_DEBUG_ROUTE=true`.
|
||||
|
||||
Returns request traces, headers, and raw candidate URLs for debugging source failures.
|
||||
|
||||
:::callout-warning
|
||||
Leave `ENABLE_DEBUG_ROUTE` disabled on public deployments. The debug route exposes upstream request information and raw stream URLs, which can leak internal details.
|
||||
:::
|
||||
You should receive an HTTP 200 (or 207) with JSON detailing the cluster status, cache count, and source probe results.
|
||||
|
||||
@@ -1,36 +1,67 @@
|
||||
# Stream API
|
||||
# Stream API Overview
|
||||
|
||||
<p class="page-description">An optional self-hosted HTTP and SSE layer built on top of Vyla SDK.</p>
|
||||
<p class="page-description">A self-hosted Node.js HTTP and Server-Sent Events (SSE) streaming gateway for progressive source discovery, HLS proxying, and subtitle extraction.</p>
|
||||
|
||||
## Use this only when HTTP is the right boundary
|
||||
<img class="vyla-banner" src="/src/assets/images/devices.webp" alt="Vyla Stream API infrastructure across client devices" loading="lazy" decoding="async" fetchpriority="low" />
|
||||
|
||||
Stream API packages Vyla SDK behind a small Node.js HTTP service. It is useful when a browser, mobile app, or another process needs progressive playback candidates over HTTP. It is not required to use Vyla, and it is not the primary integration for a Node.js application-use [Vyla SDK](/sdk) directly in that case.
|
||||
## Overview
|
||||
|
||||
The service accepts TMDB IDs, asks configured SDK providers for candidates, verifies results, and emits usable sources as they arrive. A player can start with the first source while retaining later sources as fallbacks.
|
||||
**Stream API** is the dedicated backend gateway for the Vyla ecosystem. While the Vyla SDK executes directly in Node.js environments, web browsers, mobile applications, and smart TVs require a standard HTTP/SSE bridge to communicate with upstream providers, circumvent browser CORS restrictions, and decrypt video streams.
|
||||
|
||||
## What it provides
|
||||
Stream API wraps `@vyla-entertainment/sdk` in a high-concurrency, self-hostable service designed for maximum throughput and zero playback stutter.
|
||||
|
||||
| Capability | Endpoint family | Why it exists |
|
||||
| --- | --- | --- |
|
||||
| Progressive playback | `/movie`, `/tv` | Stream metadata and verified source candidates with SSE |
|
||||
| Supplemental media | `/subtitles`, `/downloads` | Fetch tracks or downloadable candidates separately |
|
||||
| Operations | `/health`, `/test`, `/debug` | Check source status and investigate a provider |
|
||||
---
|
||||
|
||||
## Start a local instance
|
||||
## Architectural Highlights
|
||||
|
||||
```bash
|
||||
git clone https://gitlab.com/vyla-entertainment/stream-api
|
||||
cd stream-api
|
||||
npm install
|
||||
TMDB_API_KEY=your_key
|
||||
node server.js
|
||||
```
|
||||
| Cluster Role | Process Type | Responsibilities & Functions |
|
||||
| :--- | :--- | :--- |
|
||||
| **Primary Process** | Node.js Cluster Master | Spawns and supervises worker processes across CPU cores, manages the shared 1,500-entry in-memory LRU cache (5-minute TTL), coordinates IPC messages (`cache:get` / `cache:set`), and executes cache cleanup every 30 seconds. |
|
||||
| **Inter-Process Channel** | Node.js IPC | High-throughput bi-directional message pipe (`process.send` and `cluster.on('message')`) synchronizing cache entries across workers without Redis or external database overhead. |
|
||||
| **Worker Processes** | Forked HTTP Workers (`0..N`) | Runs native HTTP request routers (`http.createServer`), pushes progressive Server-Sent Events (SSE), executes universal reverse proxying with live M3U8/key rewriting, and strips 120-byte image obfuscation masks. |
|
||||
|
||||
The default local address is `http://localhost:7860`. For remote access, place the service behind TLS and your own access controls. Full configuration is in [Self-hosting](/stream-api/deployment/self-hosting).
|
||||
### 1. Multi-Core Cluster Architecture
|
||||
Stream API leverages Node's native `cluster` module to fork independent worker processes across available CPU cores. If one worker experiences a heavy upstream network timeout, other workers continue handling client requests without interruption.
|
||||
|
||||
## Lifecycle of a playback request
|
||||
### 2. Progressive Streaming over Server-Sent Events (SSE)
|
||||
Rather than blocking HTTP requests for 20 seconds while 60 providers resolve, Stream API utilizes Server-Sent Events:
|
||||
- **Instant Metadata**: Title info and subtitle tracks arrive in the first event (`type: "meta"`).
|
||||
- **Progressive Sources**: As each provider scraper finishes verification, a `type: "source"` event is pushed immediately.
|
||||
- **Completion Signal**: When all sources complete, a `type: "done"` event closes the stream cleanly.
|
||||
|
||||
1. A client opens `/movie?id=:tmdbId` or `/tv?id=:tmdbId&season=:season&episode=:episode`.
|
||||
2. The API returns an SSE response and emits a `meta` event with title metadata and subtitle tracks.
|
||||
3. Active SDK providers resolve in parallel; verified results arrive as individual `source` events.
|
||||
4. The server sends `done` after all source attempts finish or time out.
|
||||
### 3. Shared In-Memory LRU Cache
|
||||
All cluster workers communicate with a centralized in-memory cache managed by the primary process:
|
||||
- **Cache TTL**: 5 minutes (300,000 ms).
|
||||
- **Capacity**: 1,500 items max with LRU eviction.
|
||||
- **Cache Hit Latency**: < 2 milliseconds for frequently requested titles.
|
||||
|
||||
### 4. Reverse Proxy & Chunk De-Obfuscator
|
||||
- Transparently proxies HLS playlists (`/api?url=...`), rewriting AES encryption keys and `.ts` chunk URIs to inject permissive CORS headers.
|
||||
- Strips 120-byte fake image headers prepended to video chunks by ByteDance/TikTok-based content delivery networks.
|
||||
- Supports HTTP Range headers (`206 Partial Content`) for seeking large MP4 and MKV media containers.
|
||||
|
||||
---
|
||||
|
||||
## Route Overview
|
||||
|
||||
| Route | Protocol | Primary Purpose | Reference |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **`/movie?id=:id`** | GET (SSE) | Progressive movie source resolution | [Paths Reference](/stream-api/reference/paths) |
|
||||
| **`/tv?id=:id&season=:s&episode=:e`** | GET (SSE) | Progressive TV episode source resolution | [Paths Reference](/stream-api/reference/paths) |
|
||||
| **`/subtitles/movie/:id`** | GET (REST) | Multi-language subtitle tracks for movies | [Media Data Reference](/stream-api/reference/media-data) |
|
||||
| **`/subtitles/tv/:id/:s/:e`** | GET (REST) | Subtitle tracks for TV episodes | [Media Data Reference](/stream-api/reference/media-data) |
|
||||
| **`/downloads/movie/:id`** | GET (REST) | Direct media download packages for movies | [Media Data Reference](/stream-api/reference/media-data) |
|
||||
| **`/downloads/tv/:id/:s/:e`** | GET (REST) | Direct media download packages for TV | [Media Data Reference](/stream-api/reference/media-data) |
|
||||
| **`/api?url=:url`** | GET (Proxy) | Universal HLS & video chunk proxy | [Paths Reference](/stream-api/reference/paths) |
|
||||
| **`/health`** | GET (REST) | Cluster status, cache size, source health | [Operations Reference](/stream-api/reference/operations) |
|
||||
| **`/test/:source`** | GET (REST) | Direct test harness for individual sources | [Operations Reference](/stream-api/reference/operations) |
|
||||
|
||||
---
|
||||
|
||||
## Stream API Documentation Index
|
||||
|
||||
- **[Paths & Playback Events](/stream-api/reference/paths)**: Complete SSE payload contracts, parameters, and proxy options.
|
||||
- **[Subtitles & Media Downloads](/stream-api/reference/media-data)**: REST endpoints for subtitle extraction and direct media files.
|
||||
- **[Operations, Health & Testing](/stream-api/reference/operations)**: Cluster health monitoring, probe IDs, and debug routes.
|
||||
- **[API Keys & Authentication](/stream-api/authentication)**: Key management, SQLite database, and partner keys.
|
||||
- **[Production Self-Hosting Guide](/stream-api/deployment/self-hosting)**: Docker containerization, PM2, systemd, and Nginx reverse proxy configurations.
|
||||
|
||||
@@ -1,54 +1,141 @@
|
||||
# Subtitles and downloads
|
||||
# Subtitles & Media Downloads
|
||||
|
||||
<p class="page-description">Unified media-data paths for movies and TV episodes.</p>
|
||||
<p class="page-description">REST API reference for retrieving subtitle tracks and direct media download packages for movies and television series.</p>
|
||||
|
||||
## Subtitle paths
|
||||
## Subtitle Endpoints
|
||||
|
||||
| Media | Path |
|
||||
| --- | --- |
|
||||
| Movie | `GET /subtitles/movie/:id` |
|
||||
| TV episode | `GET /subtitles/tv/:id/:season/:episode` |
|
||||
Stream API provides dedicated REST endpoints to fetch subtitle tracks independently from video streams. Subtitles are discovered and parsed into web-standard `.vtt` or `.srt` formats.
|
||||
|
||||
The same routes are available under `/api/subtitles`. A successful response is a JSON array of tracks; a missing result returns `404` with an error object.
|
||||
---
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"label": "English",
|
||||
"file": "https://example.invalid/subtitles/en.vtt",
|
||||
"type": "vtt",
|
||||
"source": "v1"
|
||||
}
|
||||
]
|
||||
### 1. Movie Subtitles
|
||||
|
||||
```http
|
||||
GET /subtitles/movie/:id HTTP/1.1
|
||||
Host: your-api.example.com
|
||||
```
|
||||
|
||||
`file` is the track URL, `type` is normally `vtt` or `srt`, and `source` identifies the subtitle backend. Prefer the tracks included in the playback `meta` event when you are already opening a playback stream; use these paths when subtitles are fetched separately.
|
||||
#### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `id` | `integer` | Required | Numeric TMDB Movie ID (e.g. `155`) |
|
||||
|
||||
## Download paths
|
||||
|
||||
| Media | Path |
|
||||
| --- | --- |
|
||||
| Movie | `GET /downloads/movie/:id` |
|
||||
| TV episode | `GET /downloads/tv/:id/:season/:episode` |
|
||||
|
||||
The `/api/downloads` prefix is also supported. A successful response is an array; an empty array simply means no candidates were found.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"url": "https://example.invalid/file.mp4",
|
||||
"quality": "1080p",
|
||||
"size": "2.14 GB",
|
||||
"format": "MP4"
|
||||
}
|
||||
]
|
||||
#### Example Request & Response
|
||||
```bash
|
||||
curl https://your-api.example.com/subtitles/movie/155
|
||||
```
|
||||
|
||||
Download URLs are provider results, not durable Vyla-hosted files. Display quality and size as informational fields, and handle expired or unavailable links in your client.
|
||||
```json
|
||||
{
|
||||
"subtitles": [
|
||||
{
|
||||
"label": "English",
|
||||
"file": "https://your-api.example.com/api?url=https%3A%2F%2Fsubtitles.org%2Fen.vtt",
|
||||
"type": "vtt",
|
||||
"source": "opensubtitles"
|
||||
},
|
||||
{
|
||||
"label": "Spanish",
|
||||
"file": "https://your-api.example.com/api?url=https%3A%2F%2Fsubtitles.org%2Fes.vtt",
|
||||
"type": "vtt",
|
||||
"source": "opensubtitles"
|
||||
},
|
||||
{
|
||||
"label": "French",
|
||||
"file": "https://your-api.example.com/api?url=https%3A%2F%2Fsubtitles.org%2Ffr.srt",
|
||||
"type": "srt",
|
||||
"source": "subdl"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Request and response rules
|
||||
---
|
||||
|
||||
- IDs are TMDB IDs; TV paths additionally require season and episode numbers.
|
||||
- Both families return JSON, unlike the SSE playback paths.
|
||||
- Handle `404` for missing subtitles and `500` for an upstream or resolver failure.
|
||||
- Do not block first playback on either call. Fetch them in parallel or after the player has started.
|
||||
### 2. Television Episode Subtitles
|
||||
|
||||
```http
|
||||
GET /subtitles/tv/:id/:season/:episode HTTP/1.1
|
||||
Host: your-api.example.com
|
||||
```
|
||||
|
||||
#### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `id` | `integer` | Required | Numeric TMDB TV Series ID (e.g. `1396`) |
|
||||
| `season` | `integer` | Required | Season number (e.g. `1`) |
|
||||
| `episode` | `integer` | Required | Episode number (e.g. `1`) |
|
||||
|
||||
#### Example Request
|
||||
```bash
|
||||
curl https://your-api.example.com/subtitles/tv/1396/1/1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Download Endpoints
|
||||
|
||||
Stream API resolves high-speed direct media download packages and torrent/debrid links. Download lookups are separated from playback streams to allow offline caching or background download managers.
|
||||
|
||||
---
|
||||
|
||||
### 1. Movie Downloads
|
||||
|
||||
```http
|
||||
GET /downloads/movie/:id HTTP/1.1
|
||||
Host: your-api.example.com
|
||||
```
|
||||
|
||||
#### Example Response
|
||||
```json
|
||||
{
|
||||
"downloads": [
|
||||
{
|
||||
"quality": "1080p",
|
||||
"size": 2450882194,
|
||||
"type": "mkv",
|
||||
"active": true,
|
||||
"url": "https://fast-cdn.example.com/download/the-dark-knight-1080p.mkv"
|
||||
},
|
||||
{
|
||||
"quality": "720p",
|
||||
"size": 1150428190,
|
||||
"type": "mkv",
|
||||
"active": true,
|
||||
"url": "https://fast-cdn.example.com/download/the-dark-knight-720p.mkv"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Television Episode Downloads
|
||||
|
||||
```http
|
||||
GET /downloads/tv/:id/:season/:episode HTTP/1.1
|
||||
Host: your-api.example.com
|
||||
```
|
||||
|
||||
#### Example Response
|
||||
```json
|
||||
{
|
||||
"downloads": [
|
||||
{
|
||||
"quality": "1080p",
|
||||
"size": 1420882194,
|
||||
"type": "mkv",
|
||||
"active": true,
|
||||
"url": "https://fast-cdn.example.com/download/breaking-bad-s01e01-1080p.mkv"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices for Clients
|
||||
|
||||
1. **Do Not Block Initial Playback**: Initiate subtitle and download queries asynchronously. The video player should start playback immediately on the first candidate stream, and subtitles should be attached dynamically as they arrive.
|
||||
2. **CORS Handling**: Subtitle files proxied through `/api?url=...` are served with `Access-Control-Allow-Origin: *`, ensuring HTML5 `<track>` elements load cleanly in cross-origin browser contexts.
|
||||
3. **Format Support**: While most modern players prefer `.vtt`, some tracks are returned in `.srt`. If your player strictly requires WebVTT, convert SRT timestamps (`00:01:20,500` -> `00:01:20.500`) client-side before loading.
|
||||
|
||||
@@ -1,37 +1,107 @@
|
||||
# Health, testing, and safe operations
|
||||
# Operations, Health & Diagnostics
|
||||
|
||||
<p class="page-description">Observe SDK providers and diagnose a Stream API deployment without making debug data public.</p>
|
||||
<p class="page-description">Monitoring, cluster diagnostics, live health probing, and testing endpoints in Stream API.</p>
|
||||
|
||||
## Health
|
||||
## The Health Check Endpoint: `GET /health`
|
||||
|
||||
`GET /health` (or `/api/health`) probes active providers and returns a JSON summary. It includes a status, timestamp, TMDB-key presence, cache size, and a map of provider keys to `{ ok, ms }` results.
|
||||
Stream API provides an automated health probe endpoint that executes live responsiveness checks across all registered scraper modules.
|
||||
|
||||
Health is a signal, not a promise of playback. Provider behavior can differ by title, client network, and the moment of the request. Use it for observability and source selection-not as a guarantee that a viewer will receive a stream.
|
||||
|
||||
## Test one provider
|
||||
|
||||
`GET /test/:id?source=:providerKey` resolves a single provider for a movie. Add `season` and `episode` (or `s` and `e`) for TV.
|
||||
|
||||
```bash
|
||||
curl "YOUR_API_URL/test/155?source=provider-key"
|
||||
curl "YOUR_API_URL/test/1396?season=1&episode=1&source=provider-key"
|
||||
```http
|
||||
GET /health HTTP/1.1
|
||||
Host: your-api.example.com
|
||||
```
|
||||
|
||||
Use source keys reported by the running deployment. A test result reports whether resolution and verification succeeded; clients should inspect the result body rather than relying only on a transport success.
|
||||
### Response Status Codes
|
||||
- **`200 OK`**: All active provider sources passed their health probes.
|
||||
- **`207 Multi-Status`**: One or more providers failed their probes (degraded state), but the API gateway remains functional.
|
||||
|
||||
## Debug route
|
||||
### Example Response Payload
|
||||
```json
|
||||
{
|
||||
"note": "This is a health check endpoint. This is extremely unreliable, be sure to test sources directly.",
|
||||
"status": "ok",
|
||||
"timestamp": "2026-11-14T20:30:00.000Z",
|
||||
"tmdb": true,
|
||||
"cache": 142,
|
||||
"probe_id": 155,
|
||||
"sources": {
|
||||
"vidlink": { "ok": true, "ms": 420 },
|
||||
"vixsrc": { "ok": true, "ms": 610 },
|
||||
"cinecat": { "ok": true, "ms": 890 },
|
||||
"movy": { "ok": false, "ms": null }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`GET /debug/:id?source=:providerKey` offers detailed candidate and request traces, but it is only available when `ENABLE_DEBUG_ROUTE=true`.
|
||||
#### Response Fields
|
||||
| Field | Type | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `status` | `string` | Overall system state (`"ok"` or `"degraded"`). |
|
||||
| `timestamp` | `string` | ISO 8601 timestamp of probe completion. |
|
||||
| `tmdb` | `boolean` | `true` if `TMDB_API_KEY` is configured and valid in the environment. |
|
||||
| `cache` | `integer` | Current number of active cached titles in the primary LRU cache. |
|
||||
| `probe_id` | `integer` | The standard TMDB ID used to probe source responsiveness. |
|
||||
| `sources` | `object` | Dictionary of per-source availability (`ok`) and roundtrip latency in milliseconds (`ms`). |
|
||||
|
||||
---
|
||||
|
||||
## Direct Source Testing: `GET /test/:source`
|
||||
|
||||
To test an individual scraper directly without querying all 60 providers:
|
||||
|
||||
```http
|
||||
GET /test/vidlink?id=155 HTTP/1.1
|
||||
```
|
||||
|
||||
For television episodes, pass season and episode query parameters:
|
||||
```http
|
||||
GET /test/vixsrc?id=1396&s=1&e=1 HTTP/1.1
|
||||
```
|
||||
|
||||
### Response
|
||||
Returns the raw resolved candidate object directly from the provider scraper:
|
||||
|
||||
```json
|
||||
{
|
||||
"source": "vidlink",
|
||||
"url": "https://upstream.example.com/playlist.m3u8",
|
||||
"quality": "1080p",
|
||||
"type": "hls",
|
||||
"server": "vidlink-cf"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deep Debug Route: `GET /debug/:source`
|
||||
|
||||
When `ENABLE_DEBUG_ROUTE=true` is set in your environment variables, Stream API exposes a comprehensive diagnostic route:
|
||||
|
||||
```http
|
||||
GET /debug/vidlink?id=155 HTTP/1.1
|
||||
```
|
||||
|
||||
### Diagnostic Output Includes:
|
||||
1. **Raw Scraper Payload**: Unmodified JSON or HTML received from the upstream host.
|
||||
2. **Extraction Timings**: Milliseconds spent parsing DOM, solving tokens, and resolving URLs.
|
||||
3. **Phase 1 Verification**: Status code, response headers, and latency of the `HEAD` request.
|
||||
4. **Phase 2 Verification**: First 500 bytes of the master M3U8 playlist and test chunk fetch status.
|
||||
5. **Rewritten URLs**: Preview of how chunk URIs will be rewritten by the proxy.
|
||||
|
||||
:::callout-warning
|
||||
Do not enable the debug route on a public deployment. It can expose upstream request data, raw candidates, and headers. Enable it temporarily in a controlled environment, then disable it again.
|
||||
**Security Note**: Keep `ENABLE_DEBUG_ROUTE=false` in public production environments to avoid leaking internal scraper tokens or upstream endpoints.
|
||||
:::
|
||||
|
||||
## Production checklist
|
||||
---
|
||||
|
||||
- Set `TMDB_API_KEY` when metadata-dependent sources are required.
|
||||
- Keep `ENABLE_DEBUG_ROUTE=false` outside controlled troubleshooting.
|
||||
- Use TLS and enforce your own access policy before making the service remotely reachable.
|
||||
- Monitor health trends and playback failures separately.
|
||||
- Run updates and provider changes through your normal deployment process.
|
||||
- Build player fallbacks instead of assuming a specific provider is always available.
|
||||
## Cluster & Cache Architecture
|
||||
|
||||
### Primary Supervisor & Worker Pool Architecture
|
||||
- **Central In-Memory Cache**: Holds up to 1,500 verified title entries in a shared LRU table with a 5-minute TTL.
|
||||
- **Periodic Pruner**: Runs every 30 seconds to clean expired keys and enforce memory ceilings.
|
||||
- **Worker Lifecycle Supervisor**: Spawns and supervises $N$ worker processes matching CPU core count, automatically restarting terminated workers.
|
||||
- **IPC Message Dispatcher**: Routes `cache:get` and `cache:set` messages between worker threads and the primary process.
|
||||
|
||||
1. **IPC Cache Synchronization**: When a worker resolves a title, it sends a `cache:set` message to the primary process. When another worker receives a request for the same title, it sends a `cache:get` message and receives an instant `cache:hit` without re-scraping upstream hosts.
|
||||
2. **Cache Pruning**: The primary process evicts stale items older than 300,000ms (5 minutes). If total cache size exceeds 1,500 items, the oldest entries are evicted immediately.
|
||||
3. **Worker Auto-Restart**: If a worker process exits unexpectedly or exceeds memory limits, the primary cluster supervisor automatically spawns a replacement worker.
|
||||
|
||||
@@ -1,72 +1,156 @@
|
||||
# Paths and playback events
|
||||
# Paths & Playback Events
|
||||
|
||||
<p class="page-description">The complete Stream API playback contract, unified for movies and TV episodes.</p>
|
||||
<p class="page-description">Detailed API reference for Stream API streaming endpoints, Server-Sent Events contracts, and proxy query parameters.</p>
|
||||
|
||||
## Playback paths
|
||||
## Progressive Playback Endpoints
|
||||
|
||||
The API supports both the short paths below and `/api`-prefixed equivalents. A movie needs only `id`; a TV episode also needs `season` and `episode`.
|
||||
Stream API uses Server-Sent Events (SSE) to deliver real-time discovery events directly to player clients. This eliminates long HTTP timeouts and allows players to begin buffering the moment the first candidate resolves.
|
||||
|
||||
| Media | Path | Response |
|
||||
| --- | --- | --- |
|
||||
| Movie | `GET /movie?id=:id` | Server-Sent Events |
|
||||
| TV episode | `GET /tv?id=:id&season=:season&episode=:episode` | Server-Sent Events |
|
||||
---
|
||||
|
||||
`id` is a TMDB ID. For example:
|
||||
### 1. Movie Stream Endpoint: `GET /movie` (or `/api/movie`)
|
||||
|
||||
```bash
|
||||
curl -N "YOUR_API_URL/movie?id=155"
|
||||
curl -N "YOUR_API_URL/tv?id=1396&season=1&episode=1"
|
||||
Establishes an SSE event stream for a movie.
|
||||
|
||||
```http
|
||||
GET /movie?id=155 HTTP/1.1
|
||||
Host: your-api.example.com
|
||||
Accept: text/event-stream
|
||||
```
|
||||
|
||||
## Event contract
|
||||
#### Query Parameters
|
||||
| Parameter | Type | Required | Description | Example |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `id` | `integer` | Required | Numeric TMDB Movie ID | `155` (The Dark Knight) |
|
||||
|
||||
Playback routes use `text/event-stream`, not a single JSON document. Read each `data: ` line as JSON.
|
||||
---
|
||||
|
||||
### `meta`
|
||||
### 2. Television Stream Endpoint: `GET /tv` (or `/api/tv`)
|
||||
|
||||
Sent before source resolution completes. It can include TMDB metadata and an array of subtitle tracks.
|
||||
Establishes an SSE event stream for a television series episode.
|
||||
|
||||
```http
|
||||
GET /tv?id=1396&season=1&episode=1 HTTP/1.1
|
||||
Host: your-api.example.com
|
||||
Accept: text/event-stream
|
||||
```
|
||||
|
||||
#### Query Parameters
|
||||
| Parameter | Type | Required | Description | Example |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `id` | `integer` | Required | Numeric TMDB TV Series ID | `1396` (Breaking Bad) |
|
||||
| `season` | `integer` | Required | Season number | `1` |
|
||||
| `episode` | `integer` | Required | Episode number | `1` |
|
||||
|
||||
---
|
||||
|
||||
## Server-Sent Events (SSE) Contract
|
||||
|
||||
Connections to `/movie` and `/tv` return `Content-Type: text/event-stream` and emit events sequentially:
|
||||
|
||||
### Event 1: `meta` (Emitted Immediately)
|
||||
Delivers title metadata and parsed subtitle tracks while scrapers are still running.
|
||||
|
||||
```json
|
||||
{
|
||||
data: {
|
||||
"type": "meta",
|
||||
"meta": { "id": 155, "title": "The Dark Knight" },
|
||||
"subtitles": [{ "label": "English", "file": "https://…", "type": "vtt", "source": "v1" }]
|
||||
"meta": {
|
||||
"title": "The Dark Knight",
|
||||
"year": 2008,
|
||||
"tmdb_id": 155,
|
||||
"imdb_id": "tt0468569",
|
||||
"poster": "https://image.tmdb.org/t/p/w500/...",
|
||||
"backdrop": "https://image.tmdb.org/t/p/original/..."
|
||||
},
|
||||
"subtitles": [
|
||||
{
|
||||
"label": "English",
|
||||
"file": "https://your-api.example.com/api?url=...",
|
||||
"type": "vtt",
|
||||
"source": "opensubtitles"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `source`
|
||||
|
||||
One event for each candidate that passes the service's verification pipeline.
|
||||
### Event 2..N: `source` (Emitted Progressively)
|
||||
Emitted as each provider scraper successfully resolves and passes two-phase verification.
|
||||
|
||||
```json
|
||||
{
|
||||
data: {
|
||||
"type": "source",
|
||||
"source": {
|
||||
"source": "provider-key",
|
||||
"label": "Provider name",
|
||||
"url": "YOUR_API_URL/api?url=…"
|
||||
"source": "vidlink",
|
||||
"label": "VidLink Primary",
|
||||
"url": "https://your-api.example.com/api?url=https%3A%2F%2Fupstream.cdn%2Fmaster.m3u8&vl=1",
|
||||
"server": "vidlink-cf",
|
||||
"quality": "1080p",
|
||||
"type": "hls",
|
||||
"headers": {
|
||||
"User-Agent": "Mozilla/5.0 ...",
|
||||
"Referer": "https://vidlink.pro/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `url` is ready for a player. HLS sources may be wrapped by Vyla's proxy so manifest segments and keys can be fetched with the appropriate upstream headers. Some configured sources bypass proxying. Do not assume a filename extension alone determines playback behavior.
|
||||
|
||||
### `done`
|
||||
|
||||
Sent after the configured attempts are complete.
|
||||
### Event Final: `done` (Stream Closes)
|
||||
Emitted when all enabled scrapers have finished resolving or reached their maximum timeout. The server closes the SSE connection immediately after.
|
||||
|
||||
```json
|
||||
{ "type": "done", "total": 3 }
|
||||
data: {
|
||||
"type": "done",
|
||||
"total": 5
|
||||
}
|
||||
```
|
||||
|
||||
`total: 0` means no playable sources were produced for that request. The SSE connection itself can still be successful.
|
||||
---
|
||||
|
||||
## Client rules
|
||||
## The Universal Reverse Proxy: `GET /api`
|
||||
|
||||
1. Start playback as soon as the first `source` arrives.
|
||||
2. Queue every later source for manual switching or automatic fallback.
|
||||
3. Attach subtitle tracks from `meta` without blocking playback.
|
||||
4. Treat an empty result as a normal product state, not a malformed response.
|
||||
5. Abort the request when the viewer leaves the page or chooses another title.
|
||||
The `/api` route handles media proxying, M3U8 rewriting, segment stripping, and TMDB queries.
|
||||
|
||||
The [integration guide](/guides/integrations) contains a practical player pattern; [error handling](/guides/error-handling) covers fallback behavior.
|
||||
### 1. Media Proxying: `/api?url=:encoded_url`
|
||||
|
||||
```http
|
||||
GET /api?url=https%3A%2F%2Fcdn.example.com%2Fvideo.m3u8&proxyHeaders=%7B%22Referer%22%3A%22https%3A%2F%2Fsite.com%22%7D HTTP/1.1
|
||||
```
|
||||
|
||||
#### Query Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `url` (or `proxy`) | `string` | Required | URL-encoded destination target. |
|
||||
| `proxyHeaders` | `string` | Optional | URL-encoded JSON dictionary of custom headers to forward upstream. |
|
||||
| `<source_flag>` | `string` | Optional | Provider flag matching `proxyParam` (e.g. `&vl=1`, `&vx=1`) to apply source-specific referer rules. |
|
||||
| `tt` | `string` | Optional | Forces the 120-byte image de-masking routine on MPEG-TS video chunks. |
|
||||
|
||||
#### Proxy Behaviors
|
||||
- **M3U8 Playlists**: Rewrites all `.ts` chunk lines and AES `#EXT-X-KEY` URIs so that player requests route through the proxy with permissive CORS headers.
|
||||
- **Obfuscated Chunks**: If chunks begin with PNG (`0x89`), JPEG (`0xFF`), or null (`0x00`) fake headers, Stream API automatically slices off the leading 120 bytes.
|
||||
- **Range Seeking**: Forwards `Range: bytes=start-end` headers to upstream servers and returns HTTP `206 Partial Content`, enabling smooth scrubbing in MP4/MKV media.
|
||||
|
||||
---
|
||||
|
||||
### 2. Active Sources Listing: `/api?sources_meta=1`
|
||||
|
||||
Returns the list of active streaming sources currently loaded into the instance:
|
||||
|
||||
```json
|
||||
{
|
||||
"sources": [
|
||||
{ "key": "vidlink", "label": "VidLink", "timeout": 12000 },
|
||||
{ "key": "vixsrc", "label": "VixSrc", "timeout": 10000 },
|
||||
{ "key": "cinecat", "label": "CineCat", "timeout": 15000 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTTP Status Codes & Error Responses
|
||||
|
||||
| Status Code | Description | Example Payload |
|
||||
| :--- | :--- | :--- |
|
||||
| **`200 OK`** | Request succeeded or SSE stream opened | `text/event-stream` or JSON payload |
|
||||
| **`206 Partial Content`** | Byte-range media response for video scrubbing | Binary video stream chunk |
|
||||
| **`400 Bad Request`** | Missing required parameters or invalid TMDB ID | `{"error": "missing id", "route": "/movie?id=:tmdb_id"}` |
|
||||
| **`404 Not Found`** | Unknown route | `{"error": "not found"}` |
|
||||
| **`502 Bad Gateway`** | Upstream provider connection failed or timed out | `{"error": "upstream connection timeout"}` |
|
||||
|
||||