Files
AniKotoAPI/docs/streaming.md
T
Shinei Nouzen 0f2258a8d5 feat: v2.5.1 — domain sync, spotlight fix, embed providers, info IDs, latest-episode checker, full docs pass
- Sync mirror domains with official hub (anikoto.site): replace dead
  anikoto.se with official anikototv.se (dataUrl.js, render.yaml, README)
- Fix redesigned #hotest spotlight carousel selectors in homeInfo and
  spotlight extractors (dual-selector fallback for old layout)
- New GET /api/stream/embeds: iframe-ready Megavid/AniXo/MegaPlay URLs
  that bypass Cloudflare; ?slug= auto-resolves MAL/AniList IDs (cached),
  or pass ?mal=/?anilist= directly with zero scraping
- GET /api/info now returns externalIds { animeId, malId, anilistId }
  for direct feed into /api/stream/embeds (best-effort, 60min cache)
- New GET /api/episodes/latest?slug=: newest-episode checker for bot
  polling — latest episode + counts only (10min cache), registered
  before /episodes/:id so the literal path wins
- Top rankings ?sort=day|week|month verified already supported (docs)
- Full repo-style documentation on all new modules (box headers, FEATURE
  markers, NOTE/TIP inline notes, JSDoc, footers)
- Diagnostic sweep: all counts synced to 40 endpoints / 30 handlers /
  29 scrapers, stale badges and sample responses fixed, render.yaml EOF,
  Dockerfile node:18-alpine (EOL) -> node:22-alpine
- Tests: 38 passed / 0 failed / 2 optional skips; 77 JS files syntax-OK
2026-10-13 07:23:04 +05:30

36 KiB

╔══════════════════════════════════════════════════════════════════════════════╗
║  AniKotoAPI — Streaming Guide                                              ║
║  Base URL: https://anikototvapi.vercel.app/api                             ║
║  Free REST API scraping anime data from anikototv.to                        ║
╚══════════════════════════════════════════════════════════════════════════════╝

Streaming Guide

This document covers the complete streaming workflow for AniKotoAPI — from searching for an anime to playing the actual video stream.


⚠️ Streaming Status

Streaming endpoints (/api/servers, /api/stream, /api/stream/resolve) return errors when anikototv.to blocks AJAX requests from server-side IPs.

✅ Alternative: /api/stream/embeds never touches AniKoto's AJAX layer — see Direct Embed Providers.

What Works What's Blocked
Home, spotlight, trending /api/servers — server list
Search, suggestions, filter /api/stream — embed URLs
Info, episodes, schedule /api/stream/resolve — m3u8/mp4
Top 10, most popular, new release /api/mapper-servers — gogoanime servers
AZ list, random, genre list All AJAX-dependent endpoints
All HTML-scraped endpoints
/api/stream/embeds — direct provider iframes

To fix streaming, set one of these environment variables:

# Option 1: ScraperAPI (requires premium plan — $49/month)
SCRAPER_API_KEY=your_scraperapi_key

# Option 2: FlareSolverr (self-hosted, unlimited)
FLARESOLVERR_URL=http://your-flaresolverr-host:8191

Or use the Cloudflare-free embed providers below — no proxy needed.


Direct Embed Providers

GET /api/stream/embeds builds iframe-ready player URLs for three third-party providers. Playback happens inside the provider's own iframe, so nothing ever requests AniKoto's Cloudflare-protected AJAX endpoints at watch time.

Provider URL pattern Requires
MegaPlay megaplay.buzz/stream/mal|ani/{id}/{ep}/{lang} MAL or AniList ID
AniXo anixo.buzz/embed/ani/{anilistId}/{ep}/{lang} AniList ID
Megavid megavid.buzz/{mal|ani}/{id}/{ep}/{lang} MAL or AniList ID
# From an AniKoto slug (MAL/AniList IDs resolved automatically, cached 60min)
curl ".../api/stream/embeds?slug=one-piece-odmau&ep=1"

# From direct IDs — zero scraping, instant response
curl ".../api/stream/embeds?mal=21&ep=1&lang=dub&provider=megavid,megaplay"
{
  "results": {
    "ids": { "animeId": 1642, "malId": "21", "anilistId": "21" },
    "providers": [
      { "id": "megaplay", "url": "https://megaplay.buzz/stream/mal/21/1/sub", "iframeReady": true },
      { "id": "anixo", "url": "https://anixo.buzz/embed/ani/21/1/sub?color=%23e63946", "iframeReady": true },
      { "id": "megavid", "url": "https://megavid.buzz/mal/21/1/sub?color=%23e63946&autoplay=true", "iframeReady": true }
    ]
  }
}

Drop any url straight into an <iframe allow="autoplay; fullscreen"> — all three are embed-only players (Megavid rejects bare curl with 403 by design; browsers load it fine).


Table of Contents

  1. Architecture Overview
  2. Direct Embed Providers
  3. Streaming Workflow
  4. Endpoint Reference
  5. M3U8 Proxy
  6. Video Player Integration
  7. Error Handling
  8. Performance Tips

// ═══════════════════════════════════════════════════════════════════════════════ // === ARCHITECTURE OVERVIEW === // ═══════════════════════════════════════════════════════════════════════════════

Architecture Overview

┌─────────────────────────────────────────────────────────────────────────────┐
│                            CLIENT / BROWSER                                │
│                                                                             │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────────────────┐  │
│  │ Search / │───>│ Episode  │───>│ Servers  │───>│ Stream + Resolve     │  │
│  │ Browse   │    │ List     │    │ List     │    │ (get m3u8/mp4 URL)   │  │
│  └──────────┘    └──────────┘    └──────────┘    └──────────┬───────────┘  │
│                                                              │              │
│                                                              ▼              │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │                    VIDEO PLAYER                                      │   │
│  │           (HLS.js / Plyr / Video.js / native <video>)               │   │
│  │                                                                      │   │
│  │  ┌──────────────┐          ┌──────────────────────────────────────┐  │   │
│  │  │ Direct Play  │    OR    │  M3U8 Proxy ──> TS Proxy            │  │   │
│  │  │ (mp4, CORS)  │          │  (rewritten .m3u8 + .ts segments)   │  │   │
│  │  └──────────────┘          └──────────────────────────────────────┘  │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────────┘

                              │
                              ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                         AniKotoAPI Server                                   │
│                                                                             │
│  GET /episodes/{id}          ─── Episode list for an anime                  │
│  GET /servers?ids={ids}      ─── Available streaming servers               │
│  GET /stream?id={link_id}    ─── Embed URL + skip data                     │
│  GET /stream/resolve?id=...  ─── Actual m3u8/mp4 stream URL                │
│  GET /stream/qualities?url=  ─── Available quality levels                   │
│  GET /stream/proxy?url=      ─── CORS-free M3U8 proxy                      │
│  GET /stream/ts-proxy?url=   ─── CORS-free TS segment proxy                │
│  GET /watch?slug=...&ep=...  ─── Combined watch page data                  │
│  GET /download?slug=...&ep=..─── Download links                            │
└─────────────────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                    UPSTREAM SOURCE (anikototv.to)                            │
└─────────────────────────────────────────────────────────────────────────────┘

Data flow summary:

  1. Look up an anime → get animeId or slug
  2. Fetch episodes → get episode id with base64-encoded server_ids
  3. Decode server_ids → fetch /servers?ids=... to get streaming servers
  4. Pick a server → fetch /stream?id={link_id} for embed URL + skip data
  5. Resolve the embed → fetch /stream/resolve?id={link_id} for actual m3u8/mp4 URL
  6. (Optional) Get quality variants → /stream/qualities?url={m3u8_url}
  7. Play through the M3U8 proxy if CORS is an issue → /stream/proxy?url={m3u8_url}

// ═══════════════════════════════════════════════════════════════════════════════ // === STREAMING WORKFLOW === // ═══════════════════════════════════════════════════════════════════════════════

Streaming Workflow

Step 1 — Find Episodes

Use the anime's ID or slug to get the episode list.

curl -s "https://anikototvapi.vercel.app/api/episodes/one-piece"

Response:

{
  "results": {
    "animeId": 3,
    "slug": "one-piece",
    "totalEpisodes": 1122,
    "episodes": [
      {
        "id": 11188,
        "episode_no": 1118,
        "slug": "episode-1118",
        "title": "Episode 1118",
        "active": true,
        "href": "/watch/one-piece/episode-1118",
        "server_ids": "MTExODg6MTExOTQ=",
        "timestamp": 1722403200,
        "mal_id": 1234
      }
    ]
  }
}

Key field: server_ids is a base64-encoded string. Decode it to get the IDs needed for the /servers endpoint.

# Decode server_ids
echo "MTExODg6MTExOTQ=" | base64 -d
# Output: 11188:11194

Step 2 — Get Streaming Servers

Pass the decoded server_ids to /servers.

curl -s "https://anikototvapi.vercel.app/api/servers?ids=11188:11194"

Response:

{
  "results": [
    {
      "type": "hd",
      "ep_id": 11188,
      "link_id": 23001,
      "cmid": "abc123",
      "sv_id": "sv_01",
      "name": "HD-1"
    },
    {
      "type": "vidstream",
      "ep_id": 11188,
      "link_id": 23002,
      "cmid": "def456",
      "sv_id": "sv_02",
      "name": "Vidstream-2"
    },
    {
      "type": "vidcloud",
      "ep_id": 11188,
      "link_id": 23003,
      "cmid": "ghi789",
      "sv_id": "sv_03",
      "name": "VidCloud-1"
    }
  ]
}

Pick a server by its link_id. Different servers may have different CDN paths and quality options.

Step 3 — Get Stream Embed Info

Use the link_id from the chosen server.

curl -s "https://anikototvapi.vercel.app/api/stream?id=23001"

Response:

{
  "results": {
    "linkId": 23001,
    "url": "https://vidplay.site/embed/abc123",
    "skipData": {
      "intro": [0, 90],
      "outro": [1320, 1410]
    }
  }
}

skipData gives intro/outro timestamps in seconds. Use these to auto-skip in your player. url is an embed URL — you still need to resolve it to get the actual stream.

Step 4 — Resolve the Actual Stream URL

This is the critical step. Call /stream/resolve with the link_id.

curl -s "https://anikototvapi.vercel.app/api/stream/resolve?id=23001&slug=one-piece"

Response:

{
  "results": {
    "url": "https://vidtube.site/hls/abc123/master.m3u8",
    "type": "hls",
    "server": "HD-1",
    "subtitles": [
      {
        "label": "English",
        "src": "https://cdn.anipixcdn.co/subs/en/abc123.vtt",
        "srclang": "en"
      }
    ]
  }
}

type will be either "hls" (m3u8 playlist) or "mp4" (direct mp4 link). If type is "hls", you need to use an HLS player or the proxy endpoints.

Step 5 — Get Available Qualities (Optional)

If the stream is HLS, query available quality levels.

curl -s "https://anikototvapi.vercel.app/api/stream/qualities?url=https://vidtube.site/hls/abc123/master.m3u8"

Response:

{
  "results": {
    "url": "https://vidtube.site/hls/abc123/master.m3u8",
    "totalQualities": 4,
    "qualities": [
      { "url": "https://vidtube.site/hls/abc123/360p.m3u8", "quality": "360p", "width": 640, "height": 360 },
      { "url": "https://vidtube.site/hls/abc123/480p.m3u8", "quality": "480p", "width": 854, "height": 480 },
      { "url": "https://vidtube.site/hls/abc123/720p.m3u8", "quality": "720p", "width": 1280, "height": 720 },
      { "url": "https://vidtube.site/hls/abc123/1080p.m3u8", "quality": "1080p", "width": 1920, "height": 1080 }
    ]
  }
}

Step 6 — Play via Proxy (CORS-Free)

If the upstream CDN blocks browser CORS requests, route the stream through the proxy.

# Proxy the M3U8 playlist
curl -s "https://anikototvapi.vercel.app/api/stream/proxy?url=https://vidtube.site/hls/abc123/master.m3u8"

# Proxy individual TS segments (HLS player will call this automatically when proxied)
curl -s "https://anikototvapi.vercel.app/api/stream/ts-proxy?url=https://vidtube.site/hls/abc123/segment_001.ts"

Combined Watch Page (Shortcut)

For a single-call approach that bundles episode data + servers + recommendations:

curl -s "https://anikototvapi.vercel.app/api/watch?slug=one-piece&ep=1118"

Response:

{
  "results": {
    "episode": {
      "id": 11188,
      "episode_no": 1118,
      "title": "Episode 1118"
    },
    "servers": [ ... ],
    "trending": [ ... ],
    "recommendations": [ ... ]
  }
}

// ═══════════════════════════════════════════════════════════════════════════════ // === ENDPOINT REFERENCE === // ═══════════════════════════════════════════════════════════════════════════════

Endpoint Reference

GET /episodes/{id}

Param Type Description
id string Anime ID or slug

Returns the full episode list with server_ids (base64) for each episode.

curl -s "https://anikototvapi.vercel.app/api/episodes/demon-slayer"

GET /servers?ids={server_ids}

Param Type Description
ids string Decoded server_ids from episodes (colon-separated)

Returns available streaming servers with link_id values for each.

curl -s "https://anikototvapi.vercel.app/api/servers?ids=11188:11194"

Param Type Description
id number link_id from servers

Returns the embed URL and skip data (intro/outro timestamps).

curl -s "https://anikototvapi.vercel.app/api/stream?id=23001"

Param Type Description
id number link_id from servers (required)
slug string Anime slug (optional)

Resolves the actual stream URL (m3u8 or mp4) and subtitle tracks.

curl -s "https://anikototvapi.vercel.app/api/stream/resolve?id=23001&slug=one-piece"

GET /stream/qualities?url={m3u8_url}

Param Type Description
url string M3U8 playlist URL (required)

Returns all available quality levels for the given HLS playlist.

curl -s "https://anikototvapi.vercel.app/api/stream/qualities?url=https%3A%2F%2Fvidtube.site%2Fhls%2Fabc123%2Fmaster.m3u8"

GET /stream/proxy?url={m3u8_url}

Param Type Description
url string M3U8 URL (must be from an allowed domain)

Returns rewritten M3U8 content with Content-Type: application/vnd.apple.mpegurl.

Allowed domains: vidtube.site, vidplay.site, megaplay.buzz, megaplay-1.buzz, cdn.anipixcdn.co

curl -s "https://anikototvapi.vercel.app/api/stream/proxy?url=https://vidtube.site/hls/abc123/master.m3u8"

Rewritten output example:

#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360
https://anikototvapi.vercel.app/api/stream/ts-proxy?url=https%3A%2F%2Fvidtube.site%2Fhls%2Fabc123%2F360p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1400000,RESOLUTION=854x480
https://anikototvapi.vercel.app/api/stream/ts-proxy?url=https%3A%2F%2Fvidtube.site%2Fhls%2Fabc123%2F480p.m3u8

The proxy rewrites all internal URLs to route through /stream/ts-proxy, so every TS segment fetch also goes through the proxy — fully CORS-free.


GET /stream/ts-proxy?url={ts_url}

Param Type Description
url string .ts segment URL (required)

Returns binary TS video data with Content-Type: video/mp2t.

curl -s "https://anikototvapi.vercel.app/api/stream/ts-proxy?url=https://vidtube.site/hls/abc123/segment_001.ts" --output segment.ts

GET /watch?slug={slug}&ep={n}

Param Type Description
slug string Anime slug (required)
ep number Episode number (required)

Single-call endpoint returning episode data, available servers, trending sidebar, and recommendations.

curl -s "https://anikototvapi.vercel.app/api/watch?slug=one-piece&ep=1118"

GET /download?slug={slug}&ep={n}

Param Type Description
slug string Anime slug (required)
ep number Episode number (required)

Returns download links for the specified episode.

curl -s "https://anikototvapi.vercel.app/api/download?slug=one-piece&ep=1118"

Response:

{
  "results": {
    "downloadLinks": [
      { "quality": "1080p", "url": "https://..." },
      { "quality": "720p", "url": "https://..." }
    ],
    "episodeNumber": 1118,
    "title": "Episode 1118"
  }
}

GET /mapper-servers?malId={n}&slug={s}&timestamp={ts}

Param Type Description
malId number MyAnimeList ID (required)
slug string Anime slug (required)
timestamp number Unix timestamp (required)

Cross-server mapping for resolving servers across different backends.

curl -s "https://anikototvapi.vercel.app/api/mapper-servers?malId=21&slug=one-piece&timestamp=1722403200"

// ═══════════════════════════════════════════════════════════════════════════════ // === M3U8 PROXY === // ═══════════════════════════════════════════════════════════════════════════════

M3U8 Proxy

Why Use It?

Upstream video CDNs (vidtube.site, vidplay.site, etc.) typically set restrictive CORS headers that block browser requests. The M3U8 proxy solves this by:

  1. Fetching the M3U8 playlist server-side
  2. Rewriting all internal URLs to point back through the API proxy
  3. Serving the rewritten playlist with proper CORS headers
  4. Proxying every .ts segment request through /stream/ts-proxy

How It Works

Browser                AniKotoAPI Proxy           Upstream CDN
  │                         │                         │
  │  GET /stream/proxy?url= │                         │
  │────────────────────────>│                         │
  │                         │  GET master.m3u8        │
  │                         │────────────────────────>│
  │                         │<────────────────────────│
  │  rewritten m3u8         │                         │
  │<────────────────────────│                         │
  │                         │                         │
  │  GET /stream/ts-proxy?url=segment_001.ts          │
  │────────────────────────>│                         │
  │                         │  GET segment_001.ts     │
  │                         │────────────────────────>│
  │  binary TS data         │<────────────────────────│
  │<────────────────────────│                         │
  │  ... (repeat for each segment)                    │

Allowed Domains

The proxy only serves URLs from these whitelisted domains:

  • vidtube.site
  • vidplay.site
  • megaplay.buzz
  • megaplay-1.buzz
  • cdn.anipixcdn.co

Requests with URLs from other domains will be rejected.

Usage Example

// After resolving a stream URL:
const streamUrl = "https://vidtube.site/hls/abc123/master.m3u8";

// Build the proxied URL
const proxyUrl = `https://anikototvapi.vercel.app/api/stream/proxy?url=${encodeURIComponent(streamUrl)}`;

// Use this in your HLS player
const hls = new Hls();
hls.loadSource(proxyUrl);
hls.attachMedia(videoElement);

// ═══════════════════════════════════════════════════════════════════════════════ // === VIDEO PLAYER INTEGRATION === // ═══════════════════════════════════════════════════════════════════════════════

Video Player Integration

HLS.js

HLS.js is the most common choice for playing HLS streams in the browser.

<!DOCTYPE html>
<html>
<head>
  <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
</head>
<body>
  <video id="video" controls width="100%"></video>

  <script>
    const video = document.getElementById('video');
    const streamUrl = 'https://vidtube.site/hls/abc123/master.m3u8';

    // Use the proxy for CORS-free playback
    const proxyUrl = `https://anikototvapi.vercel.app/api/stream/proxy?url=${encodeURIComponent(streamUrl)}`;

    if (Hls.isSupported()) {
      const hls = new Hls({
        xhrSetup: (xhr, url) => {
          // All requests automatically route through the proxy
          // No additional setup needed when using the proxy URL as source
        }
      });
      hls.loadSource(proxyUrl);
      hls.attachMedia(video);

      // Auto-skip intro (from skipData)
      hls.on(Hls.Events.MANIFEST_PARSED, () => {
        const introEnd = 90; // seconds — from skipData.intro[1]
        video.currentTime = introEnd;
        video.play();
      });

    } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
      // Native HLS support (Safari)
      video.src = proxyUrl;
    }
  </script>
</body>
</html>

Quality switching with HLS.js:

// After loading the manifest
hls.on(Hls.Events.MANIFEST_PARSED, (event, data) => {
  const levels = data.levels; // array of quality levels
  // levels[0] = 360p, levels[1] = 720p, levels[2] = 1080p, etc.

  // Auto-select highest quality
  hls.currentLevel = levels.length - 1;

  // Or let the user pick
  document.getElementById('quality-select').addEventListener('change', (e) => {
    hls.currentLevel = parseInt(e.target.value);
  });
});

Plyr

Plyr is a simple, clean video player. It uses HLS.js under the hood for non-native browsers.

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" />
</head>
<body>
  <video id="player" controls crossorigin playsinline></video>

  <script src="https://cdn.plyr.io/3.7.8/plyr.polyfilled.js"></script>

  <script>
    const player = new Plyr('#player', {
      quality: {
        default: 720,
        options: [360, 480, 720, 1080]
      }
    });

    // Set the proxied HLS source
    const streamUrl = 'https://vidtube.site/hls/abc123/master.m3u8';
    const proxyUrl = `https://anikototvapi.vercel.app/api/stream/proxy?url=${encodeURIComponent(streamUrl)}`;

    player.source = {
      type: 'video',
      sources: [
        {
          src: proxyUrl,
          type: 'application/x-mpegURL',
          size: 720
        }
      ],
      // Optional: subtitle tracks
      tracks: [
        {
          kind: 'subtitles',
          label: 'English',
          srclang: 'en',
          src: 'https://cdn.anipixcdn.co/subs/en/abc123.vtt',
          default: true
        }
      ]
    };

    // Apply skip data
    const introEnd = 90;  // from skipData.intro[1]
    const outroStart = 1320; // from skipData.outro[0]

    player.on('timeupdate', () => {
      // Auto-skip intro
      if (player.currentTime < introEnd && player.currentTime < 5) {
        player.currentTime = introEnd;
      }
      // Auto-skip outro
      if (player.currentTime >= outroStart) {
        // Navigate to next episode or show end screen
      }
    });
  </script>
</body>
</html>

Video.js

Video.js is another popular choice with a large plugin ecosystem.

<!DOCTYPE html>
<html>
<head>
  <link href="https://vjs.zencdn.net/8.6.1/video-js.css" rel="stylesheet" />
</head>
<body>
  <video
    id="my-video"
    class="video-js vjs-default-skin vjs-big-play-centered"
    controls
    preload="auto"
    crossorigin="anonymous"
  >
    <track kind="subtitles" label="English" srclang="en"
           src="https://cdn.anipixcdn.co/subs/en/abc123.vtt" default />
  </video>

  <script src="https://vjs.zencdn.net/8.6.1/video.min.js"></script>

  <script>
    const streamUrl = 'https://vidtube.site/hls/abc123/master.m3u8';
    const proxyUrl = `https://anikototvapi.vercel.app/api/stream/proxy?url=${encodeURIComponent(streamUrl)}`;

    const player = videojs('my-video', {
      html5: {
        vhs: {
          overrideNative: true,
          smoothQualityChange: true
        }
      },
      sources: [{
        src: proxyUrl,
        type: 'application/x-mpegURL'
      }]
    });

    player.ready(() => {
      // Apply intro skip
      const introEnd = 90;
      player.currentTime(introEnd);
    });

    // Quality selection (requires videojs-contrib-quality-levels plugin)
    const qualityLevels = player.qualityLevels();
    qualityLevels.on('addqualitylevel', () => {
      // Available quality levels loaded
    });
  </script>
</body>
</html>

MP4 (Direct Play)

When the resolved stream type is "mp4", no HLS player is needed — use a standard <video> tag:

const streamUrl = 'https://...video.mp4';
video.src = streamUrl;
video.play();

MP4 files typically have CORS enabled, so no proxy is needed.


// ═══════════════════════════════════════════════════════════════════════════════ // === ERROR HANDLING === // ═══════════════════════════════════════════════════════════════════════════════

Error Handling

HTTP Status Codes

Code Meaning Action
200 Success Process response
400 Bad request — missing/invalid params Check query parameters
403 Forbidden — domain not in allowlist Use an allowed domain for proxy URLs
404 Resource not found Verify slug, episode number, or link_id
429 Rate limited Implement backoff / retry logic
500 Server error Retry after delay; report if persistent

Common Error Response Format

{
  "error": true,
  "message": "Invalid or expired session",
  "status": 403
}

Client-Side Error Handling (JavaScript)

async function fetchStream(slug, episode) {
  try {
    // Step 1: Get episodes
    const epRes = await fetch(`https://anikototvapi.vercel.app/api/episodes/${slug}`);
    if (!epRes.ok) throw new Error(`Episodes fetch failed: ${epRes.status}`);
    const epData = await epRes.json();

    const episodeEntry = epData.results.episodes.find(e => e.episode_no === episode);
    if (!episodeEntry) throw new Error(`Episode ${episode} not found`);

    // Step 2: Decode server_ids and fetch servers
    const decodedIds = atob(episodeEntry.server_ids);
    const srvRes = await fetch(`https://anikototvapi.vercel.app/api/servers?ids=${decodedIds}`);
    if (!srvRes.ok) throw new Error(`Servers fetch failed: ${srvRes.status}`);
    const srvData = await srvRes.json();

    const firstServer = srvData.results[0];
    if (!firstServer) throw new Error('No servers available');

    // Step 3: Resolve stream
    const streamRes = await fetch(
      `https://anikototvapi.vercel.app/api/stream/resolve?id=${firstServer.link_id}&slug=${slug}`
    );
    if (!streamRes.ok) throw new Error(`Stream resolve failed: ${streamRes.status}`);
    const streamData = await streamRes.json();

    return streamData.results;

  } catch (err) {
    console.error('Stream error:', err.message);
    // Show error to user, retry, or try next server
  }
}

Retry with Exponential Backoff

async function fetchWithRetry(url, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const res = await fetch(url);
      if (res.status === 429) {
        const delay = Math.pow(2, attempt) * 1000;
        await new Promise(r => setTimeout(r, delay));
        continue;
      }
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return await res.json();
    } catch (err) {
      if (attempt === maxRetries - 1) throw err;
      await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
    }
  }
}

Fallback Server Strategy

async function playWithFallback(servers, slug, videoEl) {
  for (const server of servers) {
    try {
      const res = await fetch(
        `https://anikototvapi.vercel.app/api/stream/resolve?id=${server.link_id}&slug=${slug}`
      );
      const data = await res.json();
      const streamUrl = data.results.url;

      if (data.results.type === 'hls') {
        const proxyUrl = `https://anikototvapi.vercel.app/api/stream/proxy?url=${encodeURIComponent(streamUrl)}`;
        const hls = new Hls();
        hls.loadSource(proxyUrl);
        hls.attachMedia(videoEl);
        hls.on(Hls.Events.MANIFEST_PARSED, () => videoEl.play());
        return; // success
      } else {
        videoEl.src = streamUrl;
        videoEl.play();
        return; // success
      }
    } catch (err) {
      console.warn(`Server ${server.name} failed, trying next...`);
    }
  }
  console.error('All servers failed');
}

// ═══════════════════════════════════════════════════════════════════════════════ // === PERFORMANCE TIPS === // ═══════════════════════════════════════════════════════════════════════════════

Performance Tips

  1. Cache episode data. Episode lists don't change often — cache the response for 5-10 minutes client-side.

  2. Use /watch for initial load. The combined /watch endpoint saves 2-3 round trips when loading a new episode page.

  3. Prefer direct m3u8 when possible. Only use the proxy when CORS blocks direct access. Test direct first, fall back to proxy on error.

  4. Preload the next episode. After playback starts, prefetch the next episode's server list and resolved URL.

  5. Lazy-load subtitles. Subtitle VTT files are small but only load them when the user enables them.

  6. Use poster thumbnails. Set a poster image on the <video> element to show a thumbnail while the stream loads.

  7. Implement quality auto-selection. Default to 720p for mobile, 1080p for desktop. Let users override.

  8. Handle network errors gracefully. When the player stalls, try re-resolving the stream URL before giving up — the upstream URL may have expired.

  9. Respect rate limits. The API may rate-limit aggressive polling. Use exponential backoff on 429 responses.

  10. Clean up HLS instances. When navigating away or switching episodes, always call hls.destroy() to free memory and network connections.


╔══════════════════════════════════════════════════════════════════════════════╗
║  End of Streaming Guide                                                     ║
║  AniKotoAPI — https://anikototvapi.vercel.app/api                           ║
╚══════════════════════════════════════════════════════════════════════════════╝

// ══════ END: streaming.md ══════