NADIPLAYER — Stream Proxy upgrade kit (v1.3.1 — HARDENED + SIGN COUNTER)
========================================================================

WHAT'S NEW IN THIS BUILD vs the previous hardened kit
-----------------------------------------------------
• /api/proxy/stats now returns "sign_requests" — a counter that
  increments every time ANY device hits POST /api/proxy/sign.
• The dashboard widget shows it as the FIRST KPI tile, color-coded:
     green  → device(s) talking to the proxy ✓
     amber  → "APK may be stale — rebuild" (counter still at 0)
• proxy_signer.php is UNCHANGED — port 8080 has always worked,
  the signer just stores whatever 'server' you put in the Shared
  Profile config. We verified end-to-end:
     ub = "http://sts.mydroon.com:8080/live/Meskidau/m5n4oCPAt1rBb3I"
  is preserved verbatim in the signed token.

WHERE TO UPLOAD (cPanel File Manager)
-------------------------------------
public_html/<your-domain>/nadiplayer/api/index.php                     (OVERWRITE)
public_html/<your-domain>/nadiplayer/api/routes/proxy.php              (OVERWRITE)
public_html/<your-domain>/nadiplayer/api/lib/proxy_signer.php          (OVERWRITE)
public_html/<your-domain>/nadiplayer/api/lib/segment_cache.php         (OVERWRITE)
public_html/<your-domain>/nadiplayer/admin_panel/views/dashboard.php   (OVERWRITE)


====================================================================
ABOUT THE BLACK SCREEN — IT'S NOT THE PHP, IT'S THE APK
====================================================================

What we proved end-to-end on the Emergent preview server (which
runs the IDENTICAL proxy code as your cPanel):

  $ POST /api/proxy/sign  with profile_id=shared:playlist
  → 200 { "proxy_url": "/api/proxy/live/<token>.m3u8", "expires_in": 1800 }

  $ The signed token decoded:
    {
      "exp": <30-min-from-now>,
      "k":   "live",
      "pid": "shared:playlist",
      "sid": "7757",
      "ub":  "http://sts.mydroon.com:8080/live/Meskidau/m5n4oCPAt1rBb3I",
      "v":   1
    }
    ↑ port 8080 IS preserved correctly.

  $ GET /api/proxy/live/<token>.m3u8
  → HTTP 200, content-type=application/vnd.apple.mpegurl, 1834 bytes,
    valid #EXTM3U body with all segment URIs rewritten to
    /api/proxy/seg/<seg-token>. Plays without errors.

So your PHP backend is fine. The Black Screen on the device =
ExoPlayer is connecting DIRECTLY to http://sts.mydroon.com:8080
(NOT through the proxy), which immediately gets BAD_HTTP_STATUS
because your provider's max_connections = 1 (proven by their own
player_api.php response).


====================================================================
HOW TO USE THE NEW "SIGN REQUESTS" COUNTER
====================================================================

After uploading these 5 files:

1. Open /admin_panel/ → look at the Stream Proxy widget. The first
   KPI tile is now "Sign requests".
2. Have one subscriber tap a channel on their device.
3. Refresh the dashboard.

   Counter went UP (e.g. 0 → 1)
   ─────────────────────────────────────
   ✓ APK is calling /api/proxy/sign correctly. If "Active upstreams"
     is still 0, the issue is the segment fetch (firewall, SSL,
     allow_url_fopen, etc.). Check /home/<USER>/error_log for the
     actual upstream curl error.

   Counter STILL at 0
   ─────────────────────────────────────
   ✗ The installed APK was built BEFORE the proxy substitution code
     was added to LiveTV.tsx. The device is going direct to upstream.
     SOLUTION: rebuild + reinstall the APK via your GitHub Actions
     workflow (`.github/workflows/android-build.yml`). No amount of
     PHP edits will help — the APK has to ship the new JS bundle.


====================================================================
SUBSCRIBER CONFIG — FOR THE PROXY TO BE USED
====================================================================

For a subscriber to be eligible for proxy routing, in the admin
Subscribers panel, set:

  Xtream mode:        مشترك (عام) / Shared (public)    ← screenshot 4
  السيرفر / server:   leave EMPTY
  مستخدم XT:          leave EMPTY
  كلمة XT:            leave EMPTY

Per-user Xtream credentials (when those fields are filled in) are
intentionally bypassed by the proxy — they're private to that user.
Only the admin's curated Shared Profile flows through the proxy and
benefits from single-fetch dedupe + caching.


====================================================================
SMOKE TEST — RUN THIS FROM ANY MACHINE WITH cURL
====================================================================

YOUR_DOMAIN=https://your-domain.com   # ← change this
ADMIN_PW=your-admin-password          # ← change this

TOKEN=$(curl -s -X POST $YOUR_DOMAIN/api/auth/login \
  -H "Content-Type: application/json" \
  -d "{\"username\":\"admin\",\"password\":\"$ADMIN_PW\"}" | jq -r .access_token)

# Confirm the new field is exposed:
curl -s -H "Authorization: Bearer $TOKEN" \
  $YOUR_DOMAIN/api/proxy/stats | jq '.sign_requests, .backend'

# Sign a test URL (replace SP_ID with your shared profile id):
curl -s -X POST $YOUR_DOMAIN/api/proxy/sign \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"profile_id":"shared:SP_ID","kind":"live","stream_id":"7757"}' | jq

# Now /api/proxy/stats sign_requests should have gone UP by 1.
