<?php
/**
 * Toffee Live TV — index.php
 * Features:
 *  - Quality selector (if multiple renditions available)
 *  - Always-live: on buffer/pause recovery, seek to live edge
 *  - Multi-referrer: proxy rotates through permitted referrer domains
 *  - Video.js + VHS, auto landscape fullscreen
 */

define('SOURCE_URL', 'https://raw.githubusercontent.com/sm-monirulislam/Toffee-Auto-Update-Playlist/refs/heads/main/toffee_data.json');
define('CACHE_FILE', __DIR__ . '/toffee_cache.json');
define('CACHE_TTL',  3600);

// ── SITE LOGO (URL or path) ────────────────────────────────────────────────
// Use a full URL: 'https://yourdomain.com/logo.png'
// Or leave empty '' to show text logo instead
define('SITE_LOGO_URL', 'https://i.ibb.co.com/Q3rp8ZXs/20260203-180035-0000.png');
define('SITE_LOGO_TEXT', '🎬 Toffee'); // fallback text if no logo URL

// ── ALLOWED DOMAINS (Anti-Embed / Anti-Iframe) ────────────────────────────
$ALLOWED_DOMAINS = [];

// ── DOMAIN CHECK ─────────────────────────────────────────────────────────
// Blocks iframe/embed from unauthorized domains.
// Sends X-Frame-Options + CSP headers AND JS top-frame check.
if (!empty($ALLOWED_DOMAINS) && !isset($_GET['proxy'])) {
    $thisHost = strtolower(preg_replace('/:\d+$/', '', $_SERVER['HTTP_HOST'] ?? ''));

    $allowed = false;
    foreach ($ALLOWED_DOMAINS as $d) {
        $d = strtolower(trim($d));
        // Exact match OR current host is a subdomain of allowed domain
        if ($thisHost === $d || str_ends_with($thisHost, '.'.$d)) {
            $allowed = true; break;
        }
    }

    if ($allowed) {
        $fa = implode(' ', array_map(fn($d)=>'https://'.trim($d).' http://'.trim($d), $ALLOWED_DOMAINS));
        header("X-Frame-Options: SAMEORIGIN");
        header("Content-Security-Policy: frame-ancestors 'self' $fa");
    } else {
        header("X-Frame-Options: DENY");
        header("Content-Security-Policy: frame-ancestors 'none'");
    }
}

// ── PERMITTED REFERRER DOMAINS ─────────────────────────────────────────────
// Add/remove as needed. Proxy will cycle through these on each request.
$REFERRER_DOMAINS = [
    'https://www.toffeelive.com',
    'https://toffeelive.com',
    'https://tv.toffeelive.com',
    'https://m.toffeelive.com',
];

// ── PROXY ──────────────────────────────────────────────────────────────────
if (isset($_GET['proxy'])) {
    $url    = trim($_GET['url']    ?? '');
    $cookie = trim($_GET['cookie'] ?? '');
    $ua     = trim($_GET['ua']     ?? 'Mozilla/5.0');
    $cah    = trim($_GET['cah']    ?? '');
    // Which referrer index to use (cycles via retry logic on client)
    $ri     = (int)($_GET['ri'] ?? 0) % count($REFERRER_DOMAINS);
    $ref    = $REFERRER_DOMAINS[$ri];
    $origin = preg_replace('#^(https?://[^/]+).*$#', '$1', $ref);

    if (!$url || !preg_match('#^https?://#i', $url)) { http_response_code(400); exit('Bad URL'); }

    $hdrs = [
        'Host: '         . parse_url($url, PHP_URL_HOST),
        'User-Agent: '   . $ua,
        'Referer: '      . $ref . '/',
        'Origin: '       . $origin,
        'Accept: */*',
        'Accept-Language: en-US,en;q=0.9',
        'Accept-Encoding: identity',
        'Connection: keep-alive',
    ];
    if ($cookie) $hdrs[] = 'Cookie: ' . $cookie;
    if ($cah && $cah !== 'null') $hdrs[] = 'client-api-header: ' . $cah;

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS => 5, CURLOPT_TIMEOUT => 20, CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_HTTPHEADER => $hdrs, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_ENCODING => 'identity',
    ]);
    $body  = curl_exec($ch);
    $info  = curl_getinfo($ch);
    $err   = curl_error($ch);
    $hcode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($body === false) { http_response_code(502); exit('cURL: '.$err); }
    if ($hcode >= 400)   { http_response_code($hcode); exit('HTTP '.$hcode.' ref='.$ref); }

    header('Access-Control-Allow-Origin: *');
    header('Cache-Control: no-cache, no-store');
    // Pass used referrer index back so client can track it
    header('X-Ref-Index: '.$ri);

    $ctype   = $info['content_type'] ?? '';
    $ext     = strtolower(pathinfo(strtok(parse_url($url, PHP_URL_PATH),'?'), PATHINFO_EXTENSION));
    $is_m3u8 = $ext==='m3u8' || strpos($ctype,'mpegurl')!==false || (strlen($body)<400000 && strpos($body,'#EXTM3U')!==false);

    if ($is_m3u8) {
        $purl    = parse_url($url);
        $base    = $purl['scheme'].'://'.$purl['host'].(isset($purl['port'])?':'.$purl['port']:'').rtrim(dirname($purl['path']),'/').'/';;
        $root    = $purl['scheme'].'://'.$purl['host'].'/';
        $proto2  = (!empty($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=='off')?'https':'http';
        $self    = $proto2.'://'.$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'];
        $ck=urlencode($cookie); $u=urlencode($ua); $c=urlencode($cah);
        $resolve = fn($h) => preg_match('#^https?://#i',$h) ? $h : ($h[0]==='/' ? $root.ltrim($h,'/') : $base.$h);

        $out = [];
        foreach (explode("\n", str_replace("\r",'',$body)) as $line) {
            $t = trim($line);
            if ($t==='') { $out[]=''; continue; }
            if ($t[0]==='#') {
                if (strpos($t,'URI="')!==false)
                    $line = preg_replace_callback('/URI="([^"]*)"/',
                        fn($m)=>'URI="'.$self.'?proxy&url='.urlencode($resolve($m[1])).'&cookie='.$ck.'&ua='.$u.'&cah='.$c.'&ri='.$ri.'"',
                        $line);
                $out[]=$line; continue;
            }
            $out[]=$self.'?proxy&url='.urlencode($resolve($t)).'&cookie='.$ck.'&ua='.$u.'&cah='.$c.'&ri='.$ri;
        }
        header('Content-Type: application/vnd.apple.mpegurl; charset=utf-8');
        echo implode("\n",$out);
    } elseif ($ext==='ts'  || strpos($ctype,'video/')!==false)  { header('Content-Type: video/MP2T'); echo $body; }
    elseif  ($ext==='aac'  || strpos($ctype,'audio/')!==false)  { header('Content-Type: audio/aac');  echo $body; }
    elseif  ($ext==='key')                                       { header('Content-Type: application/octet-stream'); echo $body; }
    else { header('Content-Type: '.($ctype ?: 'application/octet-stream')); echo $body; }
    exit;
}

// ── Load channels ──────────────────────────────────────────────────────────
function get_channels(): array {
    $needs = !file_exists(CACHE_FILE)||(time()-filemtime(CACHE_FILE))>CACHE_TTL;
    if ($needs) {
        $ctx = stream_context_create(['http'=>['method'=>'GET','timeout'=>15,'header'=>"User-Agent: Mozilla/5.0\r\n"]]);
        $raw = @file_get_contents(SOURCE_URL, false, $ctx);
        if ($raw) {
            $d = json_decode($raw, true);
            if (isset($d['response']) && is_array($d['response'])) {
                file_put_contents(CACHE_FILE, $raw);
                return $d['response'];
            }
        }
    }
    if (file_exists(CACHE_FILE)) { $d=json_decode(file_get_contents(CACHE_FILE),true); return $d['response']??[]; }
    return [];
}

if (isset($_GET['refresh'])) { if(file_exists(CACHE_FILE))unlink(CACHE_FILE); header('Location: index.php'); exit; }

$all      = array_values(array_filter(get_channels(), fn($c)=>!empty($c['link'])));
$updated  = file_exists(CACHE_FILE) ? date('d M Y, H:i', filemtime(CACHE_FILE)) : 'Never';
$proto    = (!empty($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=='off')?'https':'http';
$self_url = $proto.'://'.$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'];

$js_ch = [];
foreach ($all as $i => $c) {
    $h    = $c['headers'] ?? [];
    $ck2  = $h['cookie']            ?? '';
    $ua2  = $h['user-agent']        ?? 'Mozilla/5.0';
    $cah2 = $h['client-api-header'] ?? '';
    $js_ch[] = [
        'name'  => $c['name']          ?? 'Unknown',
        'logo'  => $c['logo']          ?? '',
        'cat'   => $c['category_name'] ?? '',
        'url'   => $c['link'],
        // Base proxy URL without ri — JS will append &ri=N
        'proxy' => $self_url.'?proxy&url='.urlencode($c['link'])
                   .'&cookie='.urlencode($ck2)
                   .'&ua='.urlencode($ua2)
                   .'&cah='.urlencode($cah2),
    ];
}

// ── PLAYER MODE ────────────────────────────────────────────────────────────
if (isset($_GET['watch'])) {
    if (empty($js_ch)) { header('Location: index.php'); exit; }
    $watchParam = trim($_GET['watch']);

    // Try to find by slug (channel name, lowercased, spaces→underscore)
    function make_slug(string $name): string {
        return preg_replace('/[^a-z0-9]+/', '_', strtolower(trim($name)));
    }

    $idx = -1;
    // First try exact slug match
    foreach ($js_ch as $i => $ch) {
        if (make_slug($ch['name']) === make_slug($watchParam)) {
            $idx = $i; break;
        }
    }
    // Fallback: numeric index (backwards-compat)
    if ($idx === -1 && ctype_digit($watchParam)) {
        $idx = max(0, min((int)$watchParam, count($js_ch)-1));
    }
    // Fallback: first channel
    if ($idx === -1) { $idx = 0; }

    $cur = $js_ch[$idx];
    $ref_count = count($REFERRER_DOMAINS);
    $logo_url  = SITE_LOGO_URL;
    $logo_text = SITE_LOGO_TEXT;
    $allowed_domains_json = json_encode($ALLOWED_DOMAINS);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="screen-orientation" content="landscape">
<meta name="theme-color" content="#000">
<title><?=htmlspecialchars($cur['name'])?></title>

<!-- Video.js -->
<link  href="https://cdn.jsdelivr.net/npm/video.js@8.10.0/dist/video-js.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/video.js@8.10.0/dist/video.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@videojs/http-streaming@3.12.1/dist/videojs-http-streaming.min.js"></script>

<style>
*{box-sizing:border-box;margin:0;padding:0}
html,body{width:100%;height:100%;background:#000;overflow:hidden;-webkit-tap-highlight-color:transparent}

#my-video,
.video-js{
  position:fixed !important;
  inset:0 !important;
  width:100vw !important;
  height:100vh !important;
  height:100dvh !important;
  background:#000 !important;
}
.video-js .vjs-tech{
  width:100% !important; height:100% !important; object-fit:fill !important;
}
.video-js .vjs-control-bar{
  background:linear-gradient(to top,rgba(0,0,0,.88),transparent) !important;
  height:46px !important;
}
.video-js .vjs-big-play-button{ display:none !important }
.vjs-live-display{ color:#fff !important; font-weight:700 !important; font-size:.75rem !important; letter-spacing:1px !important }

/* Quality menu button */
.vjs-quality-selector .vjs-menu-button{ font-size:.75rem !important }
.vjs-menu-item-text{ font-size:.78rem !important }

/* Fullscreen fill */
:-webkit-full-screen .video-js,
:fullscreen .video-js{
  width:100vw !important; height:100vh !important; object-fit:fill !important;
}
:-webkit-full-screen .vjs-tech,
:fullscreen .vjs-tech{
  object-fit:fill !important;
}

/* ── Logo overlay ── */
#site-logo{
  position:fixed;
  top:10px;       /* ← উপর-নিচ: বাড়ালে নিচে যাবে */
  right:12px;     /* ← ডানে: কমালে আরও ডানে যাবে। বামে রাখতে: right সরিয়ে left:12px দিন */
  z-index:200;
  display:flex;align-items:center;
  background:transparent;   /* কোনো background নেই */
  border-radius:0;
  padding:0;
  pointer-events:none;
  transition:opacity .3s;
}
#site-logo img{
  height:36px;    /* ← সাইজ: বাড়ালে বড়, কমালে ছোট */
  width:auto;
  display:block;
}
#site-logo span{ font-family:-apple-system,sans-serif;font-size:.8rem;font-weight:700;color:#fff;letter-spacing:.5px }
/* Hide logo when controls shown, show when idle */
.vjs-user-inactive #site-logo{ opacity:1 }
.vjs-user-active   #site-logo{ opacity:1 }

/* ── Anti-embed block screen ── */
#embed-block{
  display:none;position:fixed;inset:0;z-index:9999;background:#000;
  flex-direction:column;align-items:center;justify-content:center;gap:12px;
  font-family:-apple-system,sans-serif;color:#fff;text-align:center;padding:30px;
}
#embed-block.show{ display:flex }
#embed-block .eb-icon{ font-size:3rem }
#embed-block h2{ font-size:1.1rem;font-weight:700 }
#embed-block p{ font-size:.8rem;color:rgba(255,255,255,.45);max-width:280px;line-height:1.6 }
#embed-block a{ color:#00d4ff;font-size:.8rem;text-decoration:none;border:1px solid rgba(0,212,255,.3);padding:8px 20px;border-radius:8px;margin-top:4px }
#ld{
  position:fixed;inset:0;z-index:100;background:#000;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:18px;
  transition:opacity .4s;pointer-events:none;
}
#ld.h{opacity:0}
#ld-logo{ margin-bottom:4px; }
#ld-logo img{ height:64px;width:auto;display:block;filter:drop-shadow(0 0 18px rgba(0,212,255,.35)) }
#ld-logo span{ font-family:-apple-system,sans-serif;font-size:1.3rem;font-weight:700;color:#fff;letter-spacing:1px }
.ring{width:44px;height:44px;border:3px solid rgba(255,255,255,.07);
  border-top-color:#00d4ff;border-radius:50%;animation:spin .7s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
#ln{color:rgba(255,255,255,.55);font-size:.82rem;font-weight:500;text-align:center;
  padding:0 30px;font-family:-apple-system,sans-serif;letter-spacing:.3px}
#ls{color:rgba(255,255,255,.25);font-size:.70rem;font-family:-apple-system,sans-serif;margin-top:2px}

/* ── Error ── */
#er{
  position:fixed;inset:0;z-index:101;background:rgba(0,0,0,.96);
  display:none;flex-direction:column;align-items:center;justify-content:center;
  gap:16px;padding:30px;font-family:-apple-system,sans-serif;
}
#er.s{display:flex}
#er .icon{font-size:2.8rem}
#er .msg{color:rgba(255,255,255,.4);font-size:.8rem;text-align:center;line-height:1.7;max-width:300px}
.rbtn{background:rgba(0,212,255,.1);border:1px solid rgba(0,212,255,.3);
  border-radius:10px;padding:11px 28px;color:#00d4ff;font-size:.85rem;
  font-weight:600;cursor:pointer;font-family:inherit;text-decoration:none;
  text-align:center;display:inline-block}
</style>
</head>
<body>

<video id="my-video" class="video-js vjs-default-skin"
  playsinline webkit-playsinline autoplay muted></video>

<!-- Logo overlay -->
<div id="site-logo">
<?php if($logo_url): ?>
  <img src="<?=htmlspecialchars($logo_url)?>" alt="logo">
<?php else: ?>
  <span><?=htmlspecialchars($logo_text)?></span>
<?php endif; ?>
</div>

<!-- Anti-embed block screen -->
<div id="embed-block">
  <div class="eb-icon">🔒</div>
  <h2>Unauthorized Embed</h2>
  <p>This player is not allowed to be embedded on this domain.</p>
  <a id="eb-link" href="#">Watch on official site →</a>
</div>

<div id="ld">
  <div id="ld-logo">
  <?php if($logo_url): ?>
    <img src="<?=htmlspecialchars($logo_url)?>" alt="logo">
  <?php else: ?>
    <span><?=htmlspecialchars($logo_text)?></span>
  <?php endif; ?>
  </div>
  <div class="ring"></div>
  <div id="ln"><?=htmlspecialchars($cur['name'])?></div>
  <div id="ls">Connecting…</div>
</div>

<div id="er">
  <div class="icon">📡</div>
  <div class="msg" id="em">Stream unavailable or geo-blocked.</div>
  <button class="rbtn" onclick="initPlayer(true)">↺ Retry</button>
  <a class="rbtn" href="index.php">← Channels</a>
</div>

<script>
const PROXY_BASE      = <?=json_encode($cur['proxy'])?>;
const REF_COUNT       = <?=$ref_count?>;
const ALLOWED_DOMAINS = <?=$allowed_domains_json?>;
const SELF_URL        = <?=json_encode($self_url)?>;

let player        = null;
let retries       = 0;
let refIdx        = 0;
let launchedFs    = false;
let liveCheckInt  = null;
let selectedRepId = null;   // null = Auto, string = locked quality id

// ── Anti-embed check ───────────────────────────────────────────────────────
(function(){
  if(!ALLOWED_DOMAINS || ALLOWED_DOMAINS.length === 0) return; // no restriction
  const inFrame = window.self !== window.top;
  const host = window.location.hostname.toLowerCase().replace(/:\d+$/,'');
  const ok = ALLOWED_DOMAINS.some(d=>{
    d = d.toLowerCase().trim();
    // Exact match OR host is a subdomain of allowed domain
    return host === d || host.endsWith('.'+d);
  });
  if(!ok || inFrame){
    document.getElementById('embed-block').classList.add('show');
    document.getElementById('eb-link').href = SELF_URL;
    try{ const v=document.getElementById('my-video'); if(v){ v.pause(); v.src=''; } }catch(e){}
  }
})();

// ── Helpers ────────────────────────────────────────────────────────────────
function setSt(m){ document.getElementById('ls').textContent = m; }
function hideLoad(){ document.getElementById('ld').classList.add('h'); }
function showErr(m){ hideLoad(); document.getElementById('em').textContent = m; document.getElementById('er').classList.add('s'); }
function proxyUrl(){ return PROXY_BASE + '&ri=' + refIdx; }

// ── Always-live enforcement ────────────────────────────────────────────────
// After buffering/pause, snap back to live edge
function snapToLive(){
  if(!player) return;
  try{
    const dur = player.duration();
    const cur = player.currentTime();
    // If more than 8 seconds behind live edge, seek to live
    if(isFinite(dur) && dur - cur > 8){
      player.currentTime(dur - 1);
    }
    // Use VHS seekToLive if available
    const vhs = player.tech(true) && player.tech(true).vhs;
    if(vhs && typeof vhs.seekToLive === 'function') vhs.seekToLive();
  }catch(e){}
}

function startLiveWatch(){
  clearInterval(liveCheckInt);
  // Every 10s check if we drifted from live
  liveCheckInt = setInterval(snapToLive, 10000);
}

// ── Quality selector ───────────────────────────────────────────────────────
function applyQualityLock(vhs){
  if(!vhs || !vhs.representations) return;
  const allReps = vhs.representations();
  if(selectedRepId === null){
    allReps.forEach(r => r.enabled(true));
  } else {
    allReps.forEach(r => r.enabled(r.id === selectedRepId));
  }
}

function buildQualityMenu(){
  if(!player) return;
  const vhs = player.tech({IWillNotUseThisInPlugins:true})?.vhs
           || player.tech(true)?.vhs;
  if(!vhs) return;

  // Re-apply lock after playlist reload (prevents auto-reset)
  vhs.on('loadedplaylist', ()=>{ applyQualityLock(vhs); });

  // Remove existing menu if any
  try{ player.controlBar.removeChild('QualityMenu'); }catch(e){}

  const levels = vhs.representations ? vhs.representations() : [];
  if(levels.length < 2) return;

  const sorted = [...levels].sort((a,b)=>(b.height||0)-(a.height||0));

  const MenuButton = videojs.getComponent('MenuButton');
  const MenuItem   = videojs.getComponent('MenuItem');

  class QualityMenuItem extends MenuItem {
    constructor(player, options){
      super(player, options);
      this.rep = options.rep;
    }
    handleClick(){
      super.handleClick();
      const allReps = vhs.representations ? vhs.representations() : [];
      if(this.rep === null){
        selectedRepId = null;
        allReps.forEach(r => r.enabled(true));
        updateQualityLabel('AUTO');
      } else {
        selectedRepId = this.rep.id;
        allReps.forEach(r => r.enabled(r.id === selectedRepId));
        const lbl = this.rep.height ? this.rep.height+'p'
                  : (this.rep.bandwidth ? Math.round(this.rep.bandwidth/1000)+'k' : 'HD');
        updateQualityLabel(lbl);
      }
      // Do NOT call snapToLive() here — it triggers ABR reset
    }
  }
  videojs.registerComponent('QualityMenuItem', QualityMenuItem);

  class QualityMenu extends MenuButton {
    constructor(player, options){ super(player, options); }
    createItems(){
      const items = [];
      items.push(new QualityMenuItem(player, { label:'Auto', rep:null, selectable:true, selected:true }));
      sorted.forEach(r=>{
        const lbl = r.height ? r.height+'p' : (r.bandwidth ? Math.round(r.bandwidth/1000)+'k' : 'HD');
        items.push(new QualityMenuItem(player, { label:lbl, rep:r, selectable:true }));
      });
      return items;
    }
    buildCSSClass(){ return super.buildCSSClass()+' vjs-quality-selector'; }
  }
  videojs.registerComponent('QualityMenu', QualityMenu);

  const qBtn = player.controlBar.addChild('QualityMenu', {}, player.controlBar.children().length - 1);
  updateQualityLabel(selectedRepId === null ? 'AUTO' : (()=>{
    const r = sorted.find(r=>r.id===selectedRepId);
    return r ? (r.height ? r.height+'p' : 'HD') : 'AUTO';
  })());
  qBtn.el().style.cssText = 'font-size:.7rem;font-weight:700;color:#fff;cursor:pointer;padding:0 8px;line-height:46px;';
  // Re-apply current lock immediately
  applyQualityLock(vhs);
}

function updateQualityLabel(lbl){
  try{
    const btn = player.controlBar.getChild('QualityMenu');
    if(btn) btn.el().querySelector('.vjs-icon-placeholder').textContent = lbl;
  }catch(e){}
}

// ── Player init ────────────────────────────────────────────────────────────
function initPlayer(isRetry){
  document.getElementById('er').classList.remove('s');
  document.getElementById('ld').classList.remove('h');
  setSt(isRetry ? 'Retrying (ref '+refIdx+')…' : 'Loading…');

  clearInterval(liveCheckInt);
  if(!isRetry) selectedRepId = null; // reset quality lock on fresh load

  if(player){ try{ player.dispose(); }catch(e){} player=null; }

  let vid = document.getElementById('my-video');
  if(!vid){
    vid = document.createElement('video');
    vid.id='my-video'; vid.className='video-js vjs-default-skin';
    vid.setAttribute('playsinline',''); vid.setAttribute('webkit-playsinline','');
    vid.autoplay=true; vid.muted=true;
    document.body.insertBefore(vid, document.getElementById('ld'));
  }

  player = videojs('my-video', {
    autoplay:  true,
    muted:     true,         // start muted to allow autoplay, unmute after play
    controls:  true,
    preload:   'auto',
    liveui:    true,
    fluid:     false,
    fill:      true,
    html5:{
      vhs:{
        overrideNative:                   true,
        enableLowInitialPlaylist:          true,
        limitRenditionByPlayerDimensions:  false,
        smoothQualityChange:               true,
        allowSeeksWithinUnsafeLiveWindow:  true,
      },
      nativeAudioTracks: false,
      nativeVideoTracks: false,
    },
  });

  player.src({ src: proxyUrl(), type:'application/x-mpegURL' });

  player.ready(function(){
    setSt('Buffering…');
    player.play().then(()=>{
      player.muted(false); // unmute once playing
    }).catch(()=>{
      player.muted(false);
      player.play().catch(()=>{});
    });
  });

  // ── Playing: hide loader, go fullscreen, build quality menu ──
  player.on('playing', ()=>{
    hideLoad(); retries=0;
    player.muted(false);
    startLiveWatch();
    if(!launchedFs){ launchedFs=true; goLandscapeFullscreen(); }
    // Build quality menu after a short delay (VHS needs time to load levels)
    setTimeout(buildQualityMenu, 2500);
  });

  // ── Waiting/stalled: snap to live when resuming ──
  player.on('waiting', ()=>{
    setSt('Buffering…');
  });
  player.on('canplay', ()=>{
    snapToLive(); // always resume from live edge, not buffered position
  });

  // ── Pause: auto-resume at live edge ──
  player.on('pause', ()=>{
    setTimeout(()=>{
      if(player && player.paused()){
        snapToLive();
        player.play().catch(()=>{});
      }
    }, 500);
  });

  // ── Error: cycle referrer domains then give up ──
  player.on('error', ()=>{
    const e = player.error();
    console.warn('VJS error', e, 'refIdx='+refIdx);

    // First cycle through all referrer domains
    if(refIdx < REF_COUNT - 1){
      refIdx++;
      setSt('Trying referrer '+(refIdx+1)+'/'+REF_COUNT+'…');
      setTimeout(initPlayer, 1500);
      return;
    }
    // Then retry from start up to 2 times
    refIdx = 0;
    if(retries < 2){
      retries++;
      setSt('Retry '+retries+'/2…');
      setTimeout(initPlayer, 3000);
    } else {
      showErr('Stream unavailable (code '+(e?e.code:'?')+').\nAll referrers tried. May be offline or geo-blocked.');
    }
  });
}

// ── Auto landscape fullscreen ──────────────────────────────────────────────
function goLandscapeFullscreen(){
  if(screen.orientation && screen.orientation.lock)
    screen.orientation.lock('landscape').catch(()=>{});
  const el  = document.getElementById('my-video');
  const req = el.requestFullscreen||el.webkitRequestFullscreen||el.mozRequestFullScreen||el.msRequestFullscreen;
  if(req) req.call(el).catch(()=>{});
}

document.addEventListener('fullscreenchange',       handleFsChange);
document.addEventListener('webkitfullscreenchange', handleFsChange);
function handleFsChange(){
  const fs = document.fullscreenElement || document.webkitFullscreenElement;
  if(fs){
    // Lock to landscape when entering fullscreen
    if(screen.orientation && screen.orientation.lock)
      screen.orientation.lock('landscape').catch(()=>{});
  } else {
    if(screen.orientation && screen.orientation.unlock)
      screen.orientation.unlock();
  }
}

// ── Boot ───────────────────────────────────────────────────────────────────
window.addEventListener('DOMContentLoaded', ()=>{
  initPlayer(false);
  if('wakeLock' in navigator) navigator.wakeLock.request('screen').catch(()=>{});
});
</script>
</body></html>
<?php exit; }

// ── CHANNEL LIST ───────────────────────────────────────────────────────────
$categories=[];
foreach($all as $i=>$c) $categories[$c['category_name']??'Other'][]=$i;
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
<title>Toffee Live TV</title>
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=DM+Sans:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root{--bg:#080b14;--surf:#0e1220;--card:#111825;--bdr:#1c2338;--ac:#e91e8c;--bl:#00d4ff;--tx:#eaf0ff;--mu:#56607a;--rd:#ff3d5a}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;-webkit-tap-highlight-color:transparent}
body{background:var(--bg);color:var(--tx);font-family:'DM Sans',sans-serif;overflow-x:hidden}
body::before{content:'';position:fixed;inset:0;pointer-events:none;z-index:0;
  background:radial-gradient(ellipse 55% 35% at 85% 5%,rgba(233,30,140,.08) 0%,transparent 60%),
             radial-gradient(ellipse 45% 30% at 5% 90%,rgba(0,212,255,.06) 0%,transparent 60%)}
header{position:sticky;top:0;z-index:50;background:rgba(8,11,20,.93);backdrop-filter:blur(16px);
  border-bottom:1px solid var(--bdr);padding:11px 16px;display:flex;align-items:center;justify-content:space-between}
.logo{font-family:'Bebas Neue',sans-serif;font-size:1.5rem;letter-spacing:2px;
  background:linear-gradient(135deg,var(--ac),var(--bl));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.hmeta{font-size:.68rem;color:var(--mu);text-align:right;line-height:1.7}
.ldot{display:inline-block;width:6px;height:6px;background:var(--rd);border-radius:50%;margin-right:4px;animation:blink 1.4s infinite}
@keyframes blink{0%,100%{opacity:1}50%{opacity:.15}}
.sw{padding:13px 14px 0;position:relative;z-index:1}
.sw input{width:100%;background:var(--surf);border:1px solid var(--bdr);border-radius:12px;
  padding:10px 14px 10px 38px;color:var(--tx);font-family:'DM Sans',sans-serif;font-size:.86rem;outline:none;transition:border-color .2s}
.sw input:focus{border-color:var(--bl)}
.sw input::placeholder{color:var(--mu)}
.si2{position:absolute;left:24px;top:50%;transform:translateY(-50%);color:var(--mu);pointer-events:none}
.tabs{display:flex;gap:8px;padding:12px 14px 0;overflow-x:auto;scrollbar-width:none;position:relative;z-index:1}
.tabs::-webkit-scrollbar{display:none}
.tab{flex-shrink:0;padding:6px 14px;background:var(--surf);border:1px solid var(--bdr);
  border-radius:20px;font-size:.74rem;font-weight:500;color:var(--mu);cursor:pointer;transition:all .15s}
.tab.active{background:linear-gradient(135deg,rgba(233,30,140,.18),rgba(0,212,255,.12));border-color:var(--bl);color:var(--tx)}
.sb{display:flex;padding:10px 14px 2px;font-size:.71rem;color:var(--mu);position:relative;z-index:1}
.sb strong{color:var(--bl)}
.sb a{color:var(--mu);text-decoration:none;margin-left:auto}
.grid{display:flex;flex-direction:column;gap:10px;padding:10px 14px 80px;position:relative;z-index:1}
.card{background:var(--card);border:1px solid var(--bdr);border-radius:14px;overflow:hidden;
  animation:up .3s ease both;transition:border-color .15s;text-decoration:none;display:block;color:inherit}
.card:active{border-color:rgba(0,212,255,.5)}
@keyframes up{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
.thumb{width:100%;aspect-ratio:16/7;background:linear-gradient(135deg,#110c20,#061420);
  display:flex;align-items:center;justify-content:center;position:relative}
.thumb img{max-width:80px;max-height:62px;object-fit:contain;border-radius:8px;filter:drop-shadow(0 0 12px rgba(233,30,140,.28))}
.po{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:transparent;transition:background .2s}
.card:active .po{background:rgba(0,0,0,.3)}
.pb{width:44px;height:44px;background:rgba(0,212,255,.8);border-radius:50%;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .2s}
.card:active .pb{opacity:1}
.pb svg{width:18px;height:18px;fill:#000;margin-left:3px}
.cbody{padding:10px 13px 13px}
.cat-tag{display:inline-block;font-size:.61rem;font-weight:600;color:var(--bl);background:rgba(0,212,255,.08);
  border:1px solid rgba(0,212,255,.18);border-radius:4px;padding:1px 6px;margin-bottom:5px;letter-spacing:.5px;text-transform:uppercase}
.cname{font-weight:600;font-size:.92rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.nores{text-align:center;padding:50px 20px;color:var(--mu);font-size:.85rem;display:none}
</style>
</head>
<body>
<header>
  <div class="logo">
    <?php if(SITE_LOGO_URL): ?>
      <img src="<?=htmlspecialchars(SITE_LOGO_URL)?>" alt="logo" style="height:32px;width:auto;display:block">
    <?php else: ?>
      <?=htmlspecialchars(SITE_LOGO_TEXT)?>
    <?php endif; ?>
  </div>
  <div class="hmeta"><div><span class="ldot"></span>LIVE · <?=count($all)?> ch</div><div><?=$updated?></div></div>
</header>
<div class="sw"><span class="si2">🔍</span><input type="text" id="q" placeholder="Search channels…" oninput="filt()"></div>
<div class="tabs">
  <div class="tab active" onclick="fcat('all',this)">All</div>
  <?php foreach(array_keys($categories) as $cat): ?>
  <div class="tab" onclick="fcat(<?=json_encode($cat)?>,this)"><?=htmlspecialchars($cat)?></div>
  <?php endforeach; ?>
</div>
<div class="sb">
  <span id="cnt"><strong><?=count($all)?></strong> channels</span>
  <a href="?refresh">🔄 Refresh</a>
</div>
<div class="grid" id="grid">
<?php foreach($all as $i=>$c):
    $slug = trim(preg_replace('/[^a-z0-9]+/', '_', strtolower(trim($c['name'] ?? 'channel'))), '_');
?>
<a class="card" href="?watch=<?=htmlspecialchars($slug)?>"
   data-name="<?=htmlspecialchars(strtolower($c['name']??''))?>"
   data-cat="<?=htmlspecialchars($c['category_name']??'Other')?>">
  <div class="thumb">
    <?php if(!empty($c['logo'])): ?>
    <img src="<?=htmlspecialchars($c['logo'])?>" alt="" loading="lazy" onerror="this.style.display='none'">
    <?php endif; ?>
    <div class="po"><div class="pb"><svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg></div></div>
  </div>
  <div class="cbody">
    <div class="cat-tag"><?=htmlspecialchars($c['category_name']??'Other')?></div>
    <div class="cname"><?=htmlspecialchars($c['name']??'Unknown')?></div>
  </div>
</a>
<?php endforeach; ?>
<div class="nores" id="nores">No channels found 😕</div>
</div>
<script>
let cc='all';
function fcat(cat,el){
  cc=cat;
  document.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));
  el.classList.add('active');
  apply();
}
function filt(){ apply(); }
function apply(){
  const q=document.getElementById('q').value.toLowerCase(); let n=0;
  document.querySelectorAll('.card[data-name]').forEach(c=>{
    const s=c.dataset.name.includes(q)&&(cc==='all'||c.dataset.cat===cc);
    c.style.display=s?'':'none'; if(s)n++;
  });
  document.getElementById('nores').style.display=n?'none':'block';
  document.getElementById('cnt').innerHTML=`<strong>${n}</strong> channel${n!==1?'s':''}`;
}
</script>
</body>
</html>