Skip to content

sharoon7171/ppv-stream-resolver

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ppv-stream-resolver

Reference implementation of a ppv.to HLS stream resolver: reverse-engineered embed handshake, WebAssembly decrypt, protobuf decode, and an HLS proxy layer for integration into a custom player or backend.


Problem

ppv.to live pages do not publish a plain m3u8 URL. Playback is gated behind a third-party embed that:

  1. Returns encrypted stream metadata from a /fetch endpoint
  2. Decrypts it inside a WebAssembly player module to produce a signed CDN index.m3u8
  3. Serves HLS segments that require embed referer headers and are often PNG-wrapped MPEG-TS

A custom player cannot use the page URL directly. It needs a resolver that reproduces the embed protocol and a proxy that normalizes playlists and segments for playback.


Capabilities

Capability Implementation
Stream metadata discovery ppv.to REST API → default iframe embed source
Embed origin derivation Parsed from API source.data URL — no hardcoded host
/fetch protocol replay Protobuf-encoded POST body, island session header
WASM decrypt happy-dom sandbox + bundled gasm module → index.m3u8 URL from linear memory
Slug-aware extraction Protobuf field 2 selects the correct CDN path from WASM output
HLS upstream access impit Chrome impersonation with embed Origin / Referer
Manifest rewriting Master and media m3u8 URIs rewritten for proxy chaining
Live playlist handling Incomplete tail segment trimmed by EXTINF vs TARGETDURATION ratio
Segment decode PNG container stripped to raw 0x47 MPEG-TS sync bytes
Poison playlist detection Static-image and empty-media manifests rejected before playback

Import the resolver and proxy modules into your own backend and player. The bundled HTTP server and web page are for local pipeline testing.


Architecture

flowchart TB
  Player["Your player / app"]
  Resolve["Resolve API"]
  PpvApi["api.ppv.to"]
  Embed["Embed /fetch"]
  Wasm["WASM decrypt"]
  Proxy["HLS proxy"]
  Cdn["Stream CDN"]

  Player -->|"ppv.to URL"| Resolve
  Resolve --> PpvApi
  Resolve --> Embed
  Embed --> Wasm
  Wasm -->|"streamUrl + embed context"| Player
  Player --> Proxy
  Proxy --> Cdn
Loading

Resolve — src/stream/resolve.js

Step Action
Parse Full URL, /live/… path, or slug. Strip live/; normalize 24/7-247-.
Metadata GET https://api.ppv.to/api/streams/{uri}
Source data.sources entry where default: true
Embed context source.data{ origin, path } from /embed/ URL path
Decrypt resolveEmbedStreamUrl(embed) → CDN index.m3u8
Output streamUrl, embed, optional proxiedUrl when an origin is supplied

Errors return { ok: false, stage, error } with stage: input · meta · source · decrypt.

Decrypt — src/embed/decrypt.js

/fetch request

POST {embed.origin}/fetch
Content-Type: application/octet-stream
Origin: {embed.origin}
Referer: {embed.origin}/embed/{embed.path}

Body: 0x0a | u8(path.length) | path UTF-8

/fetch response

  • Binary body (protobuf wire payload)
  • island header — session token consumed by WASM

Protobuf decode (slugFromFetchBody)

  • Walk length-delimited fields; field 2 string that does not start with { → stream slug

WASM execution (src/embed/wasm/gasm.js, gasm.wasm)

  • happy-dom Window at embed page URL
  • Mock jwplayer, P2PEngineHls, fetch returning the /fetch body + island
  • Patch WASM linear memory at fixed offsets
  • set_stream_jw(island, body) → scrape memory for https://…/secure/…/index.m3u8
  • Slug match when multiple URLs present in memory

HLS proxy — src/hls/proxy.js, src/embed/upstream.js, src/embed/media.js

Required because CDN requests need embed headers and segments arrive PNG-wrapped.

Stage Behavior
Upstream impit fetch with embed Origin / Referer; 120s timeout for multi-MB segments
Playlist Detect #EXTM3U; rewrite media URIs to proxy URLs; pass through HLS tags
Live sync Drop last segment only when EXTINF < 95% of TARGETDURATION
Segments Strip PNG (IEND boundary or TS 0x47 scan) → video/mp2t
Guard Reject poison playlists (image-only or no media URIs)

Typical resolved structure: master index.m3u8 → variant tracks-v1a1/mono.ts.m3u8 → CDN segment objects.


Integrating into your own player

1. Resolve

import { resolveStream } from './src/stream/resolve.js'

const result = await resolveStream(
  'https://ppv.to/live/your-event',
  'https://your-api.example'   // origin for proxied URL generation; omit if not needed
)

On success you receive:

{
  "ok": true,
  "uri": "your-event",
  "contentPath": "/live/your-event",
  "embed": { "origin": "https://embedhost.example", "path": "your-event" },
  "streamUrl": "https://cdn.example/secure/…/index.m3u8",
  "proxiedUrl": "https://your-api.example/api/hls?url=…&embed=…&embedOrigin=…"
}
  • streamUrl — direct CDN master playlist (token-bound, short-lived)
  • embed — required for all upstream CDN requests (referer contract)
  • proxiedUrl — entry point when your stack uses the reference proxy routes

2. Play

Option A — Reference proxy (recommended pattern)

Point your HLS player at proxiedUrl. The proxy handles referer headers, manifest rewriting, PNG unwrap, and CORS response headers. Implement GET /api/hls using writeProxyHlsResponse from src/hls/proxy.js on your backend.

Option B — Direct CDN (server-side only)

Fetch streamUrl and child playlists/segments from your backend with upstreamFetch(url, embed) from src/embed/upstream.js. Apply rewriteM3u8 and segmentBody logic before forwarding to the client. Never expose raw CDN URLs to a browser without the embed referer chain.

Player requirements

  • HLS client (hls.js, Video.js, native Safari HLS, etc.)
  • Live tuning for short sliding-window playlists (~4×4s segments): low liveSyncDurationCount, generous fragLoadingTimeOut (segments are multi-MB)
  • Backend proxy for any browser-based player

3. Proxy route contract

GET /api/hls?url={upstreamUrl}&embed={path}&embedOrigin={origin}
Response Content-Type Body
Playlist application/vnd.apple.mpegurl Rewritten m3u8
Segment video/mp2t Stripped MPEG-TS

Reference API

Shipped with the demo server (src/http/router.js) for pipeline verification.

POST /api/stream

{ "url": "https://ppv.to/live/your-event" }

GET /api/hls

Param Required
url Upstream m3u8 or segment URL
embed From resolve embed.path
embedOrigin From resolve embed.origin

Supported ppv.to input

https://ppv.to/live/{slug}
https://ppv.to/live/247-{channel}
/live/{slug}
{slug}

Project layout

src/
  server.js              Demo HTTP entry
  config.js              API_BASE, USER_AGENT
  http/router.js         Reference routes
  stream/resolve.js      Resolve orchestration
  embed/
    context.js           Embed URL parse, proxy URL builder
    decrypt.js           /fetch, WASM, m3u8 extraction
    upstream.js          CDN client (impit + referer headers)
    media.js             Playlist sniff, poison detect
    wasm/                gasm.js + gasm.wasm
  hls/proxy.js           Manifest rewrite, live sync, TS strip
public/
  index.html             Dev verification UI only

Stack

Layer Technology
Runtime Node.js 18+ ES modules
ppv.to / embed HTTP Native fetch
WASM sandbox happy-dom
Decrypt module Bundled gasm WASM (set_stream_jw)
CDN upstream impit (Chrome TLS fingerprint)
Manifest / segment Custom m3u8 rewrite, PNG → MPEG-TS

happy-dom ^20.0.10 · impit ^0.14.1


Development verification

The demo server is for local pipeline testing only.

npm install
npm start   # http://127.0.0.1:8788 — HOST / PORT env overrides
curl -s -X POST http://127.0.0.1:8788/api/stream \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://ppv.to/live/your-event"}'

Web UI (public/index.html)

Minimal page used to confirm resolve → proxy → hls.js playback during development. Not part of the integration surface — wire resolveStream and writeProxyHlsResponse into your own stack instead.


Environment (demo server)

Variable Default
HOST 127.0.0.1
PORT 8788

Set X-Forwarded-Proto behind a reverse proxy so generated proxiedUrl values use https://.


Disclaimer

This repository documents stream-resolution and playback-integration techniques discovered through independent analysis.

  • No affiliation — This project is not affiliated with, endorsed by, or sponsored by ppv.to, its embed providers, CDNs, or rights holders.
  • Your responsibility — You are solely responsible for how you use this code. Comply with applicable laws, platform terms of service, and content licensing in your jurisdiction. Do not use this project to circumvent access controls, redistribute copyrighted streams, or violate third-party rights.
  • No warranty — The software is provided as-is, without warranty of any kind. Functionality may break without notice if upstream APIs, embed hosts, or protection schemes change.
  • Trademarks — Third-party names and marks belong to their respective owners. References are for identification only.

If you are a rights holder and believe this repository infringes your rights, please open an issue or contact the maintainer.

About

Resolve ppv.to live stream URLs to playable HLS m3u8 links via WASM embed decryption and a CORS-friendly HLS proxy. Node.js HTTP server, stream API, and HLS.js web player.

Topics

Resources

Stars

Watchers

Forks

Contributors