- Overview
- Architecture
- Provider API
- Stream Objects
- Building Providers
- Advanced Features
- Troubleshooting
- API Reference
FilmPlus Nuvio Provider is a collection of streaming provider integrations for the Nuvio app. It aggregates multiple streaming sources discovered in the FilmPlus mobile application.
- Multiple Sources: Supports 8+ streaming platforms
- Async Operations: Full async/await support with error handling
- Retry Logic: Automatic retry with exponential backoff
- Metadata Integration: TMDB API for rich content information
- Format Support: M3U8, MP4, MKV, and more
filmplus-nuvio-provider/
├── src/
│ └── filmplus/
│ ├── index.js # Main provider logic
│ ├── http.js # HTTP utilities
│ ├── extractor.js # Stream extraction
│ └── config.js # Configuration
├── providers/ # Compiled output
├── manifest.json # Provider registry
├── build.js # Build system
├── package.json # Dependencies
└── README.md # Quick start guide
User Request
↓
getStreams(tmdbId, mediaType, season, episode)
↓
getTmdbInfo() ← Get metadata
↓
searchOnSource() ← Query each platform
↓
extractStreamsFromLink() ← Parse HTML/API
↓
Return Stream Objects
↓
Nuvio Player
/**
* @async
* @param {string} tmdbId - TMDB database identifier
* @param {string} mediaType - 'movie' or 'tv'
* @param {number} season - Season number (TV only)
* @param {number} episode - Episode number (TV only)
* @returns {Promise<Array>} Array of stream objects
*/
async function getStreams(tmdbId, mediaType, season, episode)The function MUST:
- Accept four parameters as shown above
- Return a Promise resolving to an array
- Return an empty array on error (not throw)
- Handle both movies and TV shows
- Include error logging for debugging
async function getStreams(tmdbId, mediaType, season, episode) {
try {
// Get content metadata
const content = await getTmdbInfo(tmdbId, mediaType);
if (!content) {
console.log('[Provider] Could not fetch metadata');
return [];
}
// Build search query
const query = buildQuery(content, season, episode);
// Search available sources
const results = await searchSources(query);
// Extract stream URLs
const streams = [];
for (const result of results) {
const pageStreams = await extractStreams(result.url);
streams.push(...pageStreams);
}
return streams;
} catch (error) {
console.error('[Provider] Error:', error.message);
return [];
}
}{
name: "ProviderName", // string - Provider identifier
title: "1080p Stream", // string - Display title
url: "https://...", // string - Playback URL
quality: "1080p" // string - Quality label
}{
size: "2.5 GB", // string - File size estimate
format: "mp4", // string - File format
headers: { // object - HTTP headers
"Referer": "https://...",
"User-Agent": "Mozilla/..."
},
subtitles: [{ // array - Subtitle URLs
language: "en",
url: "https://..."
}],
duration: 9000, // number - Duration in seconds
type: "direct" // string - "direct", "hls", "dash"
}Use standard quality labels:
4Kor2160p- Ultra HD1080p- Full HD720p- HD480p- Standard360p- MobileAuto- Adaptive bitrate
// Direct MP4 stream
{
name: "FilmPlus",
title: "1080p Direct Link",
url: "https://example.com/movie.mp4",
quality: "1080p",
format: "mp4",
headers: {
"Referer": "https://example.com"
}
}
// HLS adaptive stream
{
name: "FilmPlus",
title: "Adaptive Quality",
url: "https://example.com/playlist.m3u8",
quality: "Auto",
type: "hls",
headers: {
"User-Agent": "Mozilla/5.0..."
}
}
// Embedded player
{
name: "FilmPlus",
title: "Player Stream",
url: "https://embed.example.com/video?id=123",
quality: "720p",
headers: {
"Referer": "https://source.example.com",
"Authorization": "Bearer token123"
}
}Simplest approach for basic providers:
// providers/simple-provider.js
const axios = require('axios');
async function getStreams(tmdbId, mediaType, season, episode) {
// Implementation
return [];
}
module.exports = { getStreams };Build:
node build.js --transpile simple-provider.jsBetter for complex providers with multiple utilities:
mkdir -p src/my-providersrc/my-provider/index.js
const { fetchText } = require('./http.js');
const { extractStreams } = require('./extractor.js');
async function getStreams(tmdbId, mediaType, season, episode) {
const html = await fetchText(`https://api.example.com/search?q=${tmdbId}`);
return extractStreams(html);
}
module.exports = { getStreams };src/my-provider/http.js
const axios = require('axios');
async function fetchText(url) {
const response = await axios.get(url);
return response.data;
}
module.exports = { fetchText };src/my-provider/extractor.js
const cheerio = require('cheerio-without-node-native');
function extractStreams(html) {
const $ = cheerio.load(html);
const streams = [];
$('a[href*=".m3u8"]').each((i, el) => {
streams.push({
name: "MyProvider",
title: `Stream ${i + 1}`,
url: $(el).attr('href'),
quality: "Auto"
});
});
return streams;
}
module.exports = { extractStreams };Build:
npm run buildconst { fetchText, fetchJSON, postJSON } = require('./http');
// Fetch HTML
const html = await fetchText('https://example.com/search?q=movie', {
headers: { 'Authorization': 'Bearer token' },
retries: 3,
delay: 1000
});
// Fetch JSON
const data = await fetchJSON('https://api.example.com/data');
// POST request
const result = await postJSON('https://api.example.com/search', {
query: 'movie title'
});const {
extractStreams,
extractSearchResults,
detectQuality
} = require('./extractor');
// Extract streams from page
const streams = extractStreams(html, 'ProviderName', 'https://referer.url');
// Extract search results
const results = extractSearchResults(html, {
titleSelector: 'a.title',
limit: 10
});
// Detect quality from URL
const quality = detectQuality('https://example.com/1080p/movie.mp4');
// Returns: "1080p"async function fetchWithRetry(url, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fetch(url);
} catch (error) {
if (attempt === maxRetries - 1) throw error;
const delay = 1000 * Math.pow(2, attempt);
await new Promise(r => setTimeout(r, delay));
}
}
}const cache = new Map();
async function getStreamsWithCache(tmdbId, mediaType) {
const cacheKey = `${tmdbId}-${mediaType}`;
if (cache.has(cacheKey)) {
return cache.get(cacheKey);
}
const streams = await getStreams(tmdbId, mediaType);
cache.set(cacheKey, streams);
// Clear cache after 1 hour
setTimeout(() => cache.delete(cacheKey), 3600000);
return streams;
}// Debugging checklist:
// 1. Check TMDB metadata retrieval
console.log('[Provider] TMDB data:', content);
// 2. Verify search results
console.log('[Provider] Search results:', results);
// 3. Check stream extraction
console.log('[Provider] Extracted streams:', streams);
// 4. Verify stream URL format
streams.forEach(s => {
if (!s.url.startsWith('http')) {
console.warn('[Provider] Invalid URL:', s.url);
}
});// Solution: Add proper retry logic with delays
async function fetchWithRetry(url, options = {}) {
const { retries = 3, delay = 1000 } = options;
for (let i = 0; i < retries; i++) {
try {
return await fetch(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)...'
}
});
} catch (error) {
console.log(`Attempt ${i + 1} failed: ${error.message}`);
if (i < retries - 1) {
await new Promise(r => setTimeout(r, delay * Math.pow(2, i)));
}
}
}
return null;
}Ensure all URLs use HTTPS:
const url = url.replace(/^http:/, 'https:');Enable detailed logging:
const DEBUG = true;
function log(message) {
if (DEBUG) console.log(`[Provider] ${message}`);
}
async function getStreams(tmdbId, mediaType, season, episode) {
log(`Searching: TMDB ${tmdbId}, ${mediaType}`);
try {
const results = await search();
log(`Found ${results.length} results`);
// ...
} catch (error) {
log(`Error: ${error.message}`);
}
}- url (string) - URL to fetch
- options (object) - Optional config
- headers (object) - Custom headers
- retries (number) - Retry count (default: 3)
- delay (number) - Retry delay in ms (default: 1000)
- timeout (number) - Request timeout in ms (default: 10000)
- Returns: Promise
- url (string) - JSON endpoint URL
- options (object) - Same as fetchText
- Returns: Promise
- url (string) - POST endpoint
- data (object) - JSON payload
- options (object) - Config (no retries for POST)
- Returns: Promise
- html (string) - Page HTML
- source (string) - Provider name (default: "FilmPlus")
- referer (string) - Referer header value
- Returns: Array of stream objects
- html (string) - Search page HTML
- options (object):
- titleSelector (string) - CSS selector for results
- linkSelector (string) - Link selector
- limit (number) - Max results (default: 10)
- Returns: Array of result objects
- text (string) - URL or text to analyze
- Returns: string - Detected quality ("1080p", "720p", etc.)
- url (string) - URL to check
- Returns: boolean - True if video file
For more information, see README.md or open an issue on GitHub.