查看: 6|回复: 0

视频PHP

[复制链接]

2

主题

0

回帖

8

积分

管理员

积分
8
发表于 昨天 11:15 | 显示全部楼层 |阅读模式

https://5kds.com/thread-3449-1-1.html

  1. <?php
  2. date_default_timezone_set('Asia/Shanghai');
  3. $id = isset($_GET['id']) ? $_GET['id'] : 'cctv1';
  4. $n = [
  5.     'cctv1' => ['2024078201', '600001859', 'fhd'], //CCTV-1高清
  6.     'cctv2' => ['2024075401', '600001800', 'fhd'], //CCTV-2高清
  7.     'cctv3' => ['2024068501', '600001801', 'fhd'], //CCTV-3高清
  8.     'cctv4' => ['2029797101', '600001814', 'fhd'], //CCTV-4高清
  9.     'cctv5' => ['2024078401', '600001818', 'fhd'], //CCTV-5高清
  10.     'cctv5p' => ['2024078001', '600001817', 'fhd'], //CCTV-5+高清
  11.     'cctv6' => ['2013693901', '600108442', 'fhd'], //CCTV-6高清
  12.     'cctv7' => ['2024072001', '600004092', 'fhd'], //CCTV-7高清
  13.     'cctv8' => ['2029793001', '600001803', 'fhd'], //CCTV-8高清
  14.     'cctv9' => ['2024078601', '600004078', 'fhd'], //CCTV-9高清
  15.     'cctv10' => ['2024078701', '600001805', 'fhd'], //CCTV-10高清
  16.     'cctv11' => ['2027248701', '600001806', 'fhd'], //CCTV-11高清
  17.     'cctv12' => ['2027248801', '600001807', 'fhd'], //CCTV-12高清
  18.     'cctv13' => ['2029797201', '600001811', 'fhd'], //CCTV-13高清
  19.     'cctv14' => ['2027248901', '600001809', 'fhd'], //CCTV-14高清
  20.     'cctv15' => ['2027249001', '600001815', 'fhd'], //CCTV-15高清
  21.     'cctv16' => ['2027249101', '600098637', 'fhd'], //CCTV-16高清
  22.     'cctv164k' => ['2027249301', '600099502', 'fhd'], //CCTV-16(4K)
  23.     'cctv17' => ['2027249401', '600001810', 'fhd'], //CCTV-17高清
  24.     'cctv4k' => ['2029810301', '600002264', 'fhd'], //CCTV-4K
  25.     'cctv8k' => ['2026774101', '600156816', 'fhd'], //CCTV-8K
  26.     'cgtn' => ['2024181701', '600014550', 'fhd'], //CGTN
  27.     'cgtnfy' => ['2024181801', '600084704', 'fhd'], //CGTN法语频道
  28.     'cgtney' => ['2024181901', '600084758', 'fhd'], //CGTN俄语频道
  29.     'cgtnalby' => ['2024182001', '600084782', 'fhd'], //CGTN阿拉伯语频道
  30.     'cgtnxby' => ['2024182101', '600084744', 'fhd'], //CGTN西班牙语频道
  31.     'cgtnwyjl' => ['2024182301', '600084781', 'fhd'], //CGTN外语纪录频道
  32.     'cctvfyjc' => ['2025637103', '600099658', 'shd'], //CCTV风云剧场频道
  33.     'cctvdyjc' => ['2026874203', '600099655', 'shd'], //CCTV第一剧场频道
  34.     'cctvhjjc' => ['2026874303', '600099620', 'shd'], //CCTV怀旧剧场频道
  35.     'cctvsjdl' => ['2026874403', '600099637', 'shd'], //CCTV世界地理频道
  36.     'cctvfyyy' => ['2026874503', '600099660', 'shd'], //CCTV风云音乐频道
  37.     'cctvbqkj' => ['2026874603', '600099649', 'shd'], //CCTV兵器科技频道
  38.     'cctvfyzq' => ['2026966203', '600099636', 'shd'], //CCTV风云足球频道
  39.     'cctvgeqwq' => ['2026874703', '600099659', 'shd'], //CCTV高尔夫·网球频道
  40.     'cctvnxss' => ['2026874803', '600099650', 'shd'], //CCTV女性时尚频道
  41.     'cctvyswhjp' => ['2026874903', '600099653', 'shd'], //CCTV央视文化精品频道
  42.     'cctvystq' => ['2026875003', '600099652', 'shd'], //CCTV央视台球频道
  43.     'cctvdszn' => ['2026875103', '600099656', 'shd'], //CCTV电视指南频道
  44.     'cctvwsjk' => ['2025637003', '600099651', 'shd'], //CCTV卫生健康频道
  45.     'bjws' => ['2024052703', '600002309', 'fhd'], //北京卫视
  46.     'jsws' => ['2024171103', '600002521', 'fhd'], //江苏卫视
  47.     'dfws' => ['2024054503', '600002483', 'fhd'], //东方卫视
  48.     'zjws' => ['2024054703', '600002520', 'fhd'], //浙江卫视
  49.     'hnws' => ['2024054803', '600002475', 'fhd'], //湖南卫视
  50.     'hbws' => ['2024171203', '600002508', 'fhd'], //湖北卫视
  51.     'gdws' => ['2024060903', '600002485', 'fhd'], //广东卫视
  52.     'gxws' => ['2024060703', '600002509', 'fhd'], //广西卫视
  53.     'hljws' => ['2029797003', '600002498', 'fhd'], //黑龙江卫视
  54.     'hnws2' => ['2024055603', '600002506', 'fhd'], //海南卫视
  55.     'cqws' => ['2024061103', '600002531', 'fhd'], //重庆卫视
  56.     'szws' => ['2024061303', '600002481', 'fhd'], //深圳卫视
  57.     'scws' => ['2024061403', '600002516', 'fhd'], //四川卫视
  58.     'henanws' => ['2029797303', '600002525', 'fhd'], //河南卫视
  59.     'fjdnhz' => ['2024061503', '600002484', 'fhd'], //福建东南卫视
  60.     'gzhws' => ['2024061603', '600002490', 'fhd'], //贵州卫视
  61.     'jxws' => ['2024061703', '600002503', 'fhd'], //江西卫视
  62.     'lnws' => ['2024171303', '600002505', 'fhd'], //辽宁卫视
  63.     'ahws' => ['2024171403', '600002532', 'fhd'], //安徽卫视
  64.     'hbws2' => ['2024171503', '600002493', 'fhd'], //河北卫视
  65.     'sdws' => ['2029787903', '600002513', 'fhd'], //山东卫视
  66.     'tjws' => ['2019927003', '600152137', 'fhd'], //天津卫视
  67.     'jlws' => ['2025561503', '600190405', 'fhd'], //吉林卫视
  68.     'shanxiws' => ['2029795103', '600190400', 'fhd'], //陕西卫视
  69.     'nxws' => ['2025608503', '600190737', 'fhd'], //宁夏卫视
  70.     'nmgws' => ['2025561203', '600190401', 'fhd'], //内蒙古卫视
  71.     'ynws' => ['2025561303', '600190402', 'fhd'], //云南卫视
  72.     'shanxiws2' => ['2025560803', '600190407', 'fhd'], //山西卫视
  73.     'qhws' => ['2025559103', '600190406', 'fhd'], //青海卫视
  74.     'xzws' => ['2025558003', '600190403', 'fhd'], //西藏卫视
  75.     'cetv1' => ['2022823801', '600171827', 'fhd'], //中国教育电视台1频道
  76.     'gxpd' => ['2029360403', '600213139', 'fhd'], //国学频道
  77.     'xjws' => ['2019927403', '600152138', 'fhd'] //新疆卫视
  78. ];

  79. class CKeyManager
  80. {
  81.     // 常量定义
  82.     const DELTA = 0x9e3779b9;
  83.     const ROUNDS = 16;
  84.     const LOG_ROUNDS = 4;
  85.     const SALT_LEN = 2;
  86.     const ZERO_LEN = 7;
  87.     const TEA_CKEY = '59b2f7cf725ef43c34fdd7c123411ed3';
  88.    
  89.     private $xorKey = [0x84, 0x2E, 0xED, 0x08, 0xF0, 0x66, 0xE6, 0xEA, 0x48, 0xB4, 0xCA, 0xA9, 0x91, 0xED, 0x6F, 0xF3];
  90.     private $standardAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
  91.     private $customAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-=';
  92.    
  93.     // 当前使用的GUID
  94.     private $guid = '';
  95.    
  96.     /**
  97.      * 构造函数
  98.      */
  99.     public function __construct()
  100.     {
  101.         error_reporting(E_ALL);
  102.         ini_set('display_errors', 1);
  103.         date_default_timezone_set('Asia/Shanghai');
  104.         // 初始化时生成一个随机GUID
  105.         $this->generateGuid();
  106.     }
  107.    
  108.     /**
  109.      * 生成随机GUID
  110.      */
  111.     private function generateGuid()
  112.     {
  113.         $this->guid = sprintf('%08s%04s%04s%04s%12s',
  114.             dechex(mt_rand(0, 0xffffffff)),
  115.             dechex(mt_rand(0, 0xffff)),
  116.             dechex(mt_rand(0, 0xffff)),
  117.             dechex(mt_rand(0, 0xffff)),
  118.             dechex(mt_rand(0, 0xffffffffffff))
  119.         );
  120.         
  121.         // 确保GUID是32位十六进制字符串(不带连字符)
  122.         if (strlen($this->guid) !== 32) {
  123.             $this->guid = str_pad($this->guid, 32, '0', STR_PAD_LEFT);
  124.         }
  125.         
  126.         return $this->guid;
  127.     }
  128.    
  129.     /**
  130.      * 获取当前GUID
  131.      */
  132.     public function getGuid()
  133.     {
  134.         return $this->guid;
  135.     }
  136.    
  137.     /**
  138.      * 设置自定义GUID
  139.      */
  140.     public function setGuid($guid)
  141.     {
  142.         $this->guid = $guid;
  143.     }
  144.    
  145.     /**
  146.      * 重置GUID(生成新的随机GUID)
  147.      */
  148.     public function resetGuid()
  149.     {
  150.         return $this->generateGuid();
  151.     }
  152.    
  153.     // ================== 辅助函数 ===================
  154.    
  155.     /**
  156.      * 生成随机十六进制字符串
  157.      */
  158.     private function randomHexStr($length)
  159.     {
  160.         $hex = '';
  161.         for ($i = 0; $i < $length; $i++) {
  162.             $hex .= dechex(mt_rand(0, 15));
  163.         }
  164.         return strtoupper($hex);
  165.     }

  166.     /**
  167.      * 生成spvcode
  168.      */
  169.     private function spvcode($defn) {
  170.         $height = 1080;
  171.         if (preg_match('/(4k|8k|hdr)/i', $defn)) {
  172.             $height = 2160;
  173.         }
  174.         $frame_rates = [30, 60, 90, 120];
  175.         $h264_parts = [];
  176.         $h265_parts = [];
  177.         foreach ($frame_rates as $fps) {
  178.             $h264_parts[] = "{$fps}:{$height}";
  179.             $h265_parts[] = "{$fps}:{$height}";
  180.         }
  181.         $h264_str = implode(',', $h264_parts);
  182.         $h265_str = implode(',', $h265_parts);
  183.         $spvcode_raw = "H({$h264_str}|{$h264_str});2({$h265_str}|{$h265_str})";
  184.         return base64_encode($spvcode_raw);
  185. }

  186.     /**
  187.      * 计算签名
  188.      */
  189.     private function calcSignature($buffer)
  190.     {
  191.         $signature = 0;
  192.         foreach ($buffer as $byte) {
  193.             $signature = (0x83 * $signature + ($byte & 0xFF)) & 0x7FFFFFFF;
  194.         }
  195.         return $signature;
  196.     }
  197.    
  198.     /**
  199.      * 自定义Base64解码
  200.      */
  201.     private function customDecode($text)
  202.     {
  203.         if (empty($text)) return '';
  204.         $text = rtrim($text, '=');
  205.         if (strlen($text) % 4 != 0) {
  206.             $text .= str_repeat('=', 4 - (strlen($text) % 4));
  207.         }
  208.         
  209.         $translationTable = [];
  210.         $len = min(strlen($this->customAlphabet), strlen($this->standardAlphabet));
  211.         for ($i = 0; $i < $len; $i++) {
  212.             $translationTable[$this->customAlphabet[$i]] = $this->standardAlphabet[$i];
  213.         }
  214.         
  215.         $translatedStr = strtr($text, $translationTable);
  216.         return base64_decode($translatedStr);
  217.     }
  218.    
  219.     /**
  220.      * 自定义Base64编码
  221.      */
  222.     private function customEncode($text)
  223.     {
  224.         $encoded = base64_encode($text);
  225.         
  226.         $translationTable = [];
  227.         $len = min(strlen($this->standardAlphabet), strlen($this->customAlphabet));
  228.         for ($i = 0; $i < $len; $i++) {
  229.             $translationTable[$this->standardAlphabet[$i]] = $this->customAlphabet[$i];
  230.         }
  231.         
  232.         return rtrim(strtr($encoded, $translationTable), '=');
  233.     }
  234.    
  235.     /**
  236.      * XOR加密/解密
  237.      */
  238.     private function xorArray($byteArray)
  239.     {
  240.         $retArray = [];
  241.         $len = count($byteArray);
  242.         for ($i = 0; $i < $len; $i++) {
  243.             $retArray[] = $byteArray[$i] ^ $this->xorKey[$i & 0xF];
  244.         }
  245.         return $retArray;
  246.     }
  247.    
  248.     // ================== TEA加解密函数 ===================
  249.    
  250.     /**
  251.      * TEA ECB模式加密
  252.      */
  253.     private function teaEncryptECB($pInBuf, $pKey)
  254.     {
  255.         if (strlen($pInBuf) < 8) {
  256.             $pInBuf = str_pad($pInBuf, 8, "\0");
  257.         }
  258.         
  259.         $unpacked = unpack('N2', $pInBuf);
  260.         $y = $unpacked[1];
  261.         $z = $unpacked[2];
  262.         
  263.         $k = [
  264.             unpack('N', substr($pKey, 0, 4))[1],
  265.             unpack('N', substr($pKey, 4, 4))[1],
  266.             unpack('N', substr($pKey, 8, 4))[1],
  267.             unpack('N', substr($pKey, 12, 4))[1]
  268.         ];
  269.         
  270.         $sum = 0;
  271.         for ($i = 0; $i < self::ROUNDS; $i++) {
  272.             $sum = ($sum + self::DELTA) & 0xFFFFFFFF;
  273.             $y = ($y + ((($z << 4) + $k[0]) ^ ($z + $sum) ^ (($z >> 5) + $k[1]))) & 0xFFFFFFFF;
  274.             $z = ($z + ((($y << 4) + $k[2]) ^ ($y + $sum) ^ (($y >> 5) + $k[3]))) & 0xFFFFFFFF;
  275.         }
  276.         
  277.         return pack('N2', $y, $z);
  278.     }
  279.    
  280.     /**
  281.      * TEA ECB模式解密
  282.      */
  283.     private function teaDecryptECB($pInBuf, $pKey)
  284.     {
  285.         $unpacked = unpack('N2', $pInBuf);
  286.         $y = $unpacked[1];
  287.         $z = $unpacked[2];
  288.         
  289.         $k = [
  290.             unpack('N', substr($pKey, 0, 4))[1],
  291.             unpack('N', substr($pKey, 4, 4))[1],
  292.             unpack('N', substr($pKey, 8, 4))[1],
  293.             unpack('N', substr($pKey, 12, 4))[1]
  294.         ];
  295.         
  296.         $sum = (self::DELTA << self::LOG_ROUNDS) & 0xFFFFFFFF;
  297.         
  298.         for ($i = 0; $i < self::ROUNDS; $i++) {
  299.             $z = ($z - ((($y << 4) + $k[2]) ^ ($y + $sum) ^ (($y >> 5) + $k[3]))) & 0xFFFFFFFF;
  300.             $y = ($y - ((($z << 4) + $k[0]) ^ ($z + $sum) ^ (($z >> 5) + $k[1]))) & 0xFFFFFFFF;
  301.             $sum = ($sum - self::DELTA) & 0xFFFFFFFF;
  302.         }
  303.         
  304.         return pack('N2', $y, $z);
  305.     }
  306.    
  307.     // ================== CBC模式加解密 ===================
  308.    
  309.     /**
  310.      * CBC模式加密
  311.      */
  312.     private function oiSymmetryEncrypt2($pInBuf, $nInBufLen, $pKey)
  313.     {
  314.         // 计算填充长度
  315.         $nPadSaltBodyZeroLen = $nInBufLen + 1 + self::SALT_LEN + self::ZERO_LEN;
  316.         $nPadlen = $nPadSaltBodyZeroLen % 8;
  317.         if ($nPadlen) {
  318.             $nPadlen = 8 - $nPadlen;
  319.         }
  320.         
  321.         $pOutBuf = '';
  322.         
  323.         // 第一块数据
  324.         $src_buf = array_fill(0, 8, 0);
  325.         $src_buf[0] = (mt_rand(0, 255) & 0xF8) | $nPadlen;
  326.         $src_i = 1;
  327.         
  328.         // 填充
  329.         while ($nPadlen) {
  330.             $src_buf[$src_i] = mt_rand(0, 255);
  331.             $src_i++;
  332.             $nPadlen--;
  333.         }
  334.         
  335.         $iv_plain = array_fill(0, 8, 0);
  336.         $iv_crypt = $iv_plain;
  337.         
  338.         // 处理Salt
  339.         $i = 0;
  340.         while ($i < self::SALT_LEN) {
  341.             if ($src_i < 8) {
  342.                 $src_buf[$src_i] = mt_rand(0, 255);
  343.                 $src_i++;
  344.                 $i++;
  345.             }
  346.             
  347.             if ($src_i == 8) {
  348.                 // 异或前一块密文
  349.                 for ($j = 0; $j < 8; $j++) {
  350.                     $src_buf[$j] ^= $iv_crypt[$j];
  351.                 }
  352.                
  353.                 $temp_out = $this->teaEncryptECB(pack('C*', ...$src_buf), $pKey);
  354.                 $temp_bytes = array_values(unpack('C*', $temp_out));
  355.                
  356.                 // 异或前一块明文
  357.                 for ($j = 0; $j < 8; $j++) {
  358.                     $temp_bytes[$j] ^= $iv_plain[$j];
  359.                 }
  360.                
  361.                 $iv_plain = $src_buf;
  362.                 $iv_crypt = $temp_bytes;
  363.                 $pOutBuf .= pack('C*', ...$temp_bytes);
  364.                 $src_i = 0;
  365.             }
  366.         }
  367.         
  368.         // 处理主体数据
  369.         $pInBufIndex = 0;
  370.         while ($nInBufLen) {
  371.             if ($src_i < 8) {
  372.                 $src_buf[$src_i] = ord($pInBuf[$pInBufIndex]);
  373.                 $pInBufIndex++;
  374.                 $src_i++;
  375.                 $nInBufLen--;
  376.             }
  377.             
  378.             if ($src_i == 8) {
  379.                 // 异或前一块密文
  380.                 for ($j = 0; $j < 8; $j++) {
  381.                     $src_buf[$j] ^= $iv_crypt[$j];
  382.                 }
  383.                
  384.                 $temp_out = $this->teaEncryptECB(pack('C*', ...$src_buf), $pKey);
  385.                 $temp_bytes = array_values(unpack('C*', $temp_out));
  386.                
  387.                 // 异或前一块明文
  388.                 for ($j = 0; $j < 8; $j++) {
  389.                     $temp_bytes[$j] ^= $iv_plain[$j];
  390.                 }
  391.                
  392.                 $iv_plain = $src_buf;
  393.                 $iv_crypt = $temp_bytes;
  394.                 $pOutBuf .= pack('C*', ...$temp_bytes);
  395.                 $src_i = 0;
  396.             }
  397.         }
  398.         
  399.         // 处理Zero填充
  400.         $i = 0;
  401.         while ($i < self::ZERO_LEN) {
  402.             if ($src_i < 8) {
  403.                 $src_buf[$src_i] = 0;
  404.                 $src_i++;
  405.                 $i++;
  406.             }
  407.             
  408.             if ($src_i == 8) {
  409.                 // 异或前一块密文
  410.                 for ($j = 0; $j < 8; $j++) {
  411.                     $src_buf[$j] ^= $iv_crypt[$j];
  412.                 }
  413.                
  414.                 $temp_out = $this->teaEncryptECB(pack('C*', ...$src_buf), $pKey);
  415.                 $temp_bytes = array_values(unpack('C*', $temp_out));
  416.                
  417.                 // 异或前一块明文
  418.                 for ($j = 0; $j < 8; $j++) {
  419.                     $temp_bytes[$j] ^= $iv_plain[$j];
  420.                 }
  421.                
  422.                 $iv_plain = $src_buf;
  423.                 $iv_crypt = $temp_bytes;
  424.                 $pOutBuf .= pack('C*', ...$temp_bytes);
  425.                 $src_i = 0;
  426.             }
  427.         }
  428.         
  429.         // 处理最后一组
  430.         if ($src_i > 0) {
  431.             // 填充剩余字节
  432.             for ($j = $src_i; $j < 8; $j++) {
  433.                 $src_buf[$j] = 0;
  434.             }
  435.             
  436.             // 异或前一块密文
  437.             for ($j = 0; $j < 8; $j++) {
  438.                 $src_buf[$j] ^= $iv_crypt[$j];
  439.             }
  440.             
  441.             $temp_out = $this->teaEncryptECB(pack('C*', ...$src_buf), $pKey);
  442.             $temp_bytes = array_values(unpack('C*', $temp_out));
  443.             
  444.             // 异或前一块明文
  445.             for ($j = 0; $j < 8; $j++) {
  446.                 $temp_bytes[$j] ^= $iv_plain[$j];
  447.             }
  448.             
  449.             $pOutBuf .= pack('C*', ...$temp_bytes);
  450.         }
  451.         
  452.         return $pOutBuf;
  453.     }
  454.    
  455.     /**
  456.      * CBC模式解密
  457.      */
  458.     private function oiSymmetryDecrypt2($pInBuf, $nInBufLen, $pKey)
  459.     {
  460.         if (($nInBufLen % 8) != 0 || $nInBufLen < 16) {
  461.             return false;
  462.         }
  463.         
  464.         // 解密第一个块
  465.         $dest_buf_str = $this->teaDecryptECB(substr($pInBuf, 0, 8), $pKey);
  466.         $dest_buf = array_values(unpack('C*', $dest_buf_str));
  467.         
  468.         $nPadLen = $dest_buf[0] & 0x07;
  469.         
  470.         // 计算明文长度
  471.         $i = $nInBufLen - 1;
  472.         $i = $i - $nPadLen - self::SALT_LEN - self::ZERO_LEN;
  473.         
  474.         if ($i < 0) {
  475.             return false;
  476.         }
  477.         
  478.         $pOutBufLen = $i;
  479.         
  480.         // 初始化IV
  481.         $iv_pre_crypt = array_fill(0, 8, 0);
  482.         $iv_cur_crypt = array_values(unpack('C*', substr($pInBuf, 0, 8)));
  483.         
  484.         $pInBufOffset = 8;
  485.         $dest_i = 1;
  486.         
  487.         // 跳过Padding
  488.         $dest_i += $nPadLen;
  489.         
  490.         // 跳过Salt
  491.         $salt_count = 1;
  492.         while ($salt_count <= self::SALT_LEN) {
  493.             if ($dest_i < 8) {
  494.                 $dest_i++;
  495.                 $salt_count++;
  496.             } elseif ($dest_i == 8) {
  497.                 $iv_pre_crypt = $iv_cur_crypt;
  498.                 $iv_cur_crypt = array_values(unpack('C*', substr($pInBuf, $pInBufOffset, 8)));
  499.                
  500.                 for ($j = 0; $j < 8; $j++) {
  501.                     if ($pInBufOffset + $j >= $nInBufLen) {
  502.                         return false;
  503.                     }
  504.                     $dest_buf[$j] ^= $iv_cur_crypt[$j];
  505.                 }
  506.                
  507.                 $temp_buf = $this->teaDecryptECB(pack('C*', ...$dest_buf), $pKey);
  508.                 $dest_buf = array_values(unpack('C*', $temp_buf));
  509.                
  510.                 $pInBufOffset += 8;
  511.                 $dest_i = 0;
  512.             }
  513.         }
  514.         
  515.         // 还原明文
  516.         $nPlainLen = $pOutBufLen;
  517.         $plain_bytes = [];
  518.         
  519.         while ($nPlainLen > 0) {
  520.             if ($dest_i < 8) {
  521.                 $plain_bytes[] = $dest_buf[$dest_i] ^ $iv_pre_crypt[$dest_i];
  522.                 $dest_i++;
  523.                 $nPlainLen--;
  524.             } elseif ($dest_i == 8) {
  525.                 $iv_pre_crypt = $iv_cur_crypt;
  526.                 $iv_cur_crypt = array_values(unpack('C*', substr($pInBuf, $pInBufOffset, 8)));
  527.                
  528.                 for ($j = 0; $j < 8; $j++) {
  529.                     if ($pInBufOffset + $j >= $nInBufLen) {
  530.                         return false;
  531.                     }
  532.                     $dest_buf[$j] ^= $iv_cur_crypt[$j];
  533.                 }
  534.                
  535.                 $temp_buf = $this->teaDecryptECB(pack('C*', ...$dest_buf), $pKey);
  536.                 $dest_buf = array_values(unpack('C*', $temp_buf));
  537.                
  538.                 $pInBufOffset += 8;
  539.                 $dest_i = 0;
  540.             }
  541.         }
  542.         
  543.         return pack('C*', ...$plain_bytes);
  544.     }
  545.    
  546.     // ================== 公开的加解密方法 ===================
  547.    
  548.     /**
  549.      * 加密数据生成cKey
  550.      * @param string $data 要加密的数据
  551.      * @return string 生成的cKey
  552.      */
  553.     public function encryptDataToCKey($data)
  554.     {
  555.         $teaCkey = hex2bin(self::TEA_CKEY);
  556.         
  557.         // 计算数据长度
  558.         $data_len = strlen($data);
  559.         
  560.         // 计算校验和
  561.         $data_array = array_values(unpack('C*', $data));
  562.         $checksum = $this->calcSignature($data_array);
  563.         
  564.         // TEA加密
  565.         $encrypted = $this->oiSymmetryEncrypt2($data, $data_len, $teaCkey);
  566.         
  567.         // 添加校验和
  568.         $encrypted .= pack('N', $checksum);
  569.         
  570.         // XOR加密
  571.         $encrypted_array = array_values(unpack('C*', $encrypted));
  572.         $xor_array = $this->xorArray($encrypted_array);
  573.         $xor_encrypted = pack('C*', ...$xor_array);
  574.         
  575.         // Base64编码
  576.         $base64_encoded = $this->customEncode($xor_encrypted);
  577.         
  578.         return "--01" . $base64_encoded;
  579.     }
  580.    
  581.     /**
  582.      * 解密cKey获取数据
  583.      * @param string $ckey 要解密的cKey
  584.      * @return array|false 解密后的数据和校验和,或false表示失败
  585.      */
  586.     public function decryptCKeyToData($ckey)
  587.     {
  588.         $teaCkey = hex2bin(self::TEA_CKEY);
  589.         
  590.         // 移除前缀
  591.         $ckey_without_prefix = substr($ckey, 4);
  592.         
  593.         // 自定义Base64解码
  594.         $base64_decoded = $this->customDecode($ckey_without_prefix);
  595.         if (!$base64_decoded) {
  596.             return false;
  597.         }
  598.         
  599.         // XOR解密
  600.         $xor_array = array_values(unpack('C*', $base64_decoded));
  601.         $xor_decrypted_array = $this->xorArray($xor_array);
  602.         $xor_decrypted = pack('C*', ...$xor_decrypted_array);
  603.         
  604.         // 分离数据和校验和
  605.         $data_len = strlen($xor_decrypted) - 4;
  606.         $encrypted_data = substr($xor_decrypted, 0, $data_len);
  607.         $checksum_bytes = substr($xor_decrypted, $data_len);
  608.         $checksum = unpack('N', $checksum_bytes)[1];
  609.         
  610.         // TEA解密
  611.         $decrypted = $this->oiSymmetryDecrypt2($encrypted_data, $data_len, $teaCkey);
  612.         
  613.         return [
  614.             'data' => $decrypted,
  615.             'checksum' => $checksum
  616.         ];
  617.     }
  618.    
  619.     // ================== 数据包构建方法 ===================
  620.    
  621.     /**
  622.      * 构建数据包
  623.      * @param array $params 参数数组
  624.      * @return string 构建的数据包
  625.      */
  626.     public function buildPacket($params)
  627.     {
  628.         $data = '';
  629.         
  630.         // 1. 头部 (12字节) - 固定值
  631.         $data .= hex2bin('0000004200000004000004d2');
  632.         
  633.         // 2. Platform (4字节)
  634.         $data .= pack('N', $params['Platform']);
  635.         
  636.         // 3. Signature (4字节) - 先置0,后面计算
  637.         $data .= pack('N', 0);
  638.         
  639.         // 4. Timestamp (4字节)
  640.         $data .= pack('N', $params['Timestamp']);
  641.         
  642.         // 5. Sdtfrom (长度+字符串)
  643.         $sdtfrom = $params['Sdtfrom'];
  644.         $data .= pack('n', strlen($sdtfrom)) . $sdtfrom;
  645.         
  646.         // 6. randFlag (长度+字符串) - 使用传入的值
  647.         $randFlag = $params['randFlag'];
  648.         $data .= pack('n', strlen($randFlag)) . $randFlag;
  649.         
  650.         // 7. appVer (长度+字符串)
  651.         $appVer = $params['appVer'];
  652.         $data .= pack('n', strlen($appVer)) . $appVer;
  653.         
  654.         // 8. vid (长度+字符串)
  655.         $vid = $params['vid'];
  656.         $data .= pack('n', strlen($vid)) . $vid;
  657.         
  658.         // 9. guid (长度+字符串)
  659.         $guid = $params['guid'];
  660.         $data .= pack('n', strlen($guid)) . $guid;
  661.         
  662.         // 10. part1 (4字节)
  663.         $data .= pack('N', 1);
  664.         
  665.         // 11. isDlna (4字节) - 根据原始样本是0
  666.         $data .= pack('N', 1);
  667.         
  668.         // 12. uid (长度+字符串)
  669.         $uid = "2622783A";
  670.         $data .= pack('n', strlen($uid)) . $uid;
  671.         
  672.         // 13. bundleID (长度+字符串)
  673.         $bundleID = "nil";
  674.         $data .= pack('n', strlen($bundleID)) . $bundleID;
  675.         
  676.         // 14. uuid4 (长度+字符串)
  677.         $uuid4 = $params['uuid4'];
  678.         $data .= pack('n', strlen($uuid4)) . $uuid4;
  679.         
  680.         // 15. bundleID1 (长度+字符串) - 重复bundleID
  681.         $data .= pack('n', strlen($bundleID)) . $bundleID;
  682.         
  683.         // 16. ckeyVersion (长度+字符串)
  684.         $ckeyVersion = "v0.1.000";
  685.         $data .= pack('n', strlen($ckeyVersion)) . $ckeyVersion;
  686.         
  687.         // 17. packageName (长度+字符串)
  688.         $packageName = "com.cctv.yangshipin.app.iphone";
  689.         $data .= pack('n', strlen($packageName)) . $packageName;
  690.         
  691.         // 18. platform_str (长度+字符串)
  692.         $platform_str = "4330403";
  693.         $data .= pack('n', strlen($platform_str)) . $platform_str;
  694.         
  695.         // 19. ex_json_bus (长度+字符串)
  696.         $ex_json_bus = "ex_json_bus";
  697.         $data .= pack('n', strlen($ex_json_bus)) . $ex_json_bus;
  698.         
  699.         // 20. ex_json_vs (长度+字符串)
  700.         $ex_json_vs = "ex_json_vs";
  701.         $data .= pack('n', strlen($ex_json_vs)) . $ex_json_vs;
  702.         
  703.         // 21. ck_guard_time (长度+字符串) - 88个字符
  704.         $ck_guard_time = $params['ck_guard_time'];
  705.         $data .= pack('n', strlen($ck_guard_time)) . $ck_guard_time;
  706.         
  707.         // 验证主体长度
  708.         $body_length = strlen($data);
  709.         
  710.         // 添加长度头
  711.         $buffer = pack('n', $body_length) . $data;
  712.         
  713.         // 计算签名
  714.         $buffer_array = array_values(unpack('C*', $buffer));
  715.         $signature = $this->calcSignature($buffer_array);
  716.         
  717.         // 更新签名(位置:跳过长度头2字节+头部12字节+Platform4字节=18字节处)
  718.         $buffer = substr($buffer, 0, 18) . pack('N', $signature) . substr($buffer, 22);
  719.         
  720.         return $buffer;
  721.     }
  722.    
  723.     /**
  724.      * 生成完整的cKey
  725.      * @param string $cnlid 频道ID
  726.      * @param int|null $timestamp 时间戳
  727.      * @return array 包含cKey、参数和数据包的数组
  728.      */
  729.     public function generateCKey($cnlid, $timestamp = null)
  730.     {
  731.         if ($timestamp === null) {
  732.             $timestamp = time();
  733.         }
  734.         
  735.         // 生成所有随机值
  736.         $randFlag = base64_encode(random_bytes(18));
  737.         $uuid4 = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
  738.             mt_rand(0, 0xffff), mt_rand(0, 0xffff),
  739.             mt_rand(0, 0xffff),
  740.             mt_rand(0, 0x0fff) | 0x4000,
  741.             mt_rand(0, 0x3fff) | 0x8000,
  742.             mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
  743.         );
  744.         //$ck_guard_time = $this->randomHexStr(88); // 88个字符
  745.         $ck_guard_time = '1907CEBB43DD91205C0AA24CAA050DCE0EA64FEA1AB8F3D20C45B08B35952308456EE297396350DAA26DDC14';
  746.         $randFlag='_zj1A5Gh6QYcxWjIUGos2w==';
  747.         $params = [
  748.             'Platform' => 4330403,
  749.             'Timestamp' => $timestamp,
  750.             'Sdtfrom' => 'dcgh',
  751.             'vid' => $cnlid,
  752.             'guid' => $this->guid, // 使用当前GUID
  753.             'appVer' => 'V8.22.1035.3031',
  754.             'randFlag' => $randFlag,
  755.             'uuid4' => '57eab0c4-2c58-44c6-8ae9-dd2757525dc5',
  756.             'ck_guard_time' => $ck_guard_time
  757.         ];
  758.         
  759.         $buffer = $this->buildPacket($params);
  760.         $ckey = $this->encryptDataToCKey($buffer);
  761.         
  762.         return [
  763.             'ckey' => $ckey,
  764.             'params' => $params,
  765.             'buffer' => $buffer
  766.         ];
  767.     }
  768.    
  769.     // ================== 网络请求方法(优化版本) ===================
  770.    
  771.     /**
  772.      * 发起直播或回看请求(优化版)
  773.      * @param string $cnlid 频道ID
  774.      * @param string $livepid 直播ID
  775.      * @param string $defn 清晰度
  776.      * @param string|null $playseek 回看时间(格式:YYYYMMDDHHMMSS-YYYYMMDDHHMMSS)
  777.      * @return array 请求结果
  778.      */
  779.     public function makeLiveRequest($cnlid, $livepid = '600001859', $defn = 'fhd', $playseek = null)
  780.     {
  781.         // 每次请求生成新的随机GUID
  782.         $this->generateGuid();
  783.         
  784.         // 只生成一次cKey(直播和回看使用相同的cKey参数)
  785.         $ckeyResult = $this->generateCKey($cnlid);
  786.         $ckey = $ckeyResult['ckey'];
  787.         $params = $ckeyResult['params'];
  788.         
  789.         // 生成flowid
  790.         $flowid = sprintf('%s_%d',
  791.             sprintf('%04X%04X-%04X-%04X-%04X-%04X%04X%04X',
  792.                 mt_rand(0, 0xffff), mt_rand(0, 0xffff),
  793.                 mt_rand(0, 0xffff),
  794.                 mt_rand(0, 0x0fff) | 0x4000,
  795.                 mt_rand(0, 0x3fff) | 0x8000,
  796.                 mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
  797.             ),
  798.             4330403
  799.         );
  800.         
  801.         // 提前判断模式并设置参数
  802.         $isPlayback = !empty($playseek);
  803.         $playbackTimestamp = null;
  804.         
  805.         // 处理回看时间
  806.         if ($isPlayback) {
  807.             try {
  808.                 $parts = explode('-', $playseek);
  809.                 $startTimeStr = $parts[0];
  810.                 $dateTime = DateTime::createFromFormat('YmdHis', $startTimeStr, new DateTimeZone('Asia/Shanghai'));
  811.                 if ($dateTime === false) {
  812.                     throw new Exception("回看时间格式错误: " . $startTimeStr);
  813.                 }
  814.                 $playbackTimestamp = $dateTime->getTimestamp();
  815.             } catch (Exception $e) {
  816.                 return [
  817.                     'success' => false,
  818.                     'error' => '回看时间处理失败: ' . $e->getMessage(),
  819.                     'playseek' => $playseek
  820.                 ];
  821.             }
  822.         }
  823.         
  824.         // 构建基础请求参数(根据模式设置不同参数)
  825.         $spvcode = $this->spvcode($defn);
  826.         $request_params = [
  827.             "atime" => "120",
  828.             "livepid" => $livepid,
  829.             "cnlid" => $cnlid,
  830.             "appVer" => "V8.22.1035.3031",
  831.             "app_version" => "300090",
  832.             "caplv" => "1",
  833.             "cmd" => "2",
  834.             "defn" => $defn,
  835.             "device" => "iPhone",
  836.             "encryptVer" => "4.2",
  837.             "getpreviewinfo" => "0",
  838.             "hevclv" => "33",
  839.             "lang" => "zh-Hans_JP",
  840.             "livequeue" => "0",
  841.             "logintype" => "1",
  842.             "nettype" => "1",
  843.             "newnettype" => "1",
  844.             "newplatform" => "4330403",
  845.             "platform" => "4330403",
  846.             "sdtfrom" => "v3021",
  847.             "spacode" => "23",
  848.             "spaudio" => "1",
  849.             "spdemuxer" => "6",
  850.             "spdrm" => "2",
  851.             "spdynamicrange" => "7",
  852.             "spflv" => "1",
  853.             "spflvaudio" => "1",
  854.             "sphdrfps" => "60",
  855.             "sphttps" => "0",
  856.             "spvcode" => "MSgzMDoyMTYwLDYwOjIxNjB8MzA6MjE2MCw2MDoyMTYwKTsyKDMwOjIxNjAsNjA6MjE2MHwzMDoyMTYwLDYwOjIxNjAp",
  857.             "spvideo" => "4",
  858.             "stream" => "1",
  859.             "system" => "1",
  860.             "sysver" => "ios18.2.1",
  861.             "uhd_flag" => "4",
  862.             "cKey" => $ckey,
  863.             "guid" => $this->guid,
  864.             "fntick" => $params['Timestamp'],
  865.             "flowid" => $flowid,
  866.         ];
  867.         // 根据模式设置不同的参数
  868.         if ($isPlayback) {
  869.             // 回看模式:第一次尝试 - 添加playbacktime参数
  870.             $request_params['playbacktime'] = $playbackTimestamp;
  871.             
  872.             // 发送第一次请求
  873.             $response = $this->sendHttpRequest($request_params);
  874.             
  875.             if ($response['success'] && isset($response['response']['playurl'])) {
  876.                 return $response;
  877.             } else {
  878.                 // 第二次尝试 - 不添加playbacktime参数
  879.                 unset($request_params['playbacktime']);
  880.                
  881.                 // 发送第二次请求
  882.                 $response = $this->sendHttpRequest($request_params);
  883.                
  884.                 if ($response['success'] && isset($response['response']['playurl'])) {
  885.                     // 手动处理URL:修改域名并添加starttime参数
  886.                     $playurl = $response['response']['playurl'];
  887.                     $playurl = $this->processPlaybackUrl($playurl, $playbackTimestamp);
  888.                     $response['response']['playurl'] = $playurl;
  889.                     return $response;
  890.                 } else {
  891.                     return [
  892.                         'success' => false,
  893.                         'error' => '无法获取回看地址',
  894.                         'playseek' => $playseek,
  895.                         'response' => $response['response'] ?? null
  896.                     ];
  897.                 }
  898.             }
  899.         } else {
  900.             // 直播模式:设置playbacktime为0
  901.             $request_params['playbacktime'] = "0";
  902.             
  903.             // 发送请求
  904.             return $this->sendHttpRequest($request_params);
  905.         }
  906.     }
  907.    
  908.     /**
  909.      * 处理回看URL
  910.      * @param string $playurl 原始播放URL
  911.      * @param int $playbackTimestamp 回看时间戳
  912.      * @return string 处理后的URL
  913.      */
  914.     private function processPlaybackUrl($playurl, $playbackTimestamp)
  915.     {
  916.         // 修改域名
  917.         $urlParts = explode('/', $playurl);
  918.         if (count($urlParts) >= 3) {
  919.             $urlParts[2] = 'tlivecloud-playback-cdn.ysp.cctv.cn/tcloud.cctv.com';
  920.             $playurl = implode('/', $urlParts);
  921.             
  922.             // 添加starttime参数
  923.             if (strpos($playurl, '?') !== false) {
  924.                 $playurl .= '&starttime=' . $playbackTimestamp;
  925.             } else {
  926.                 $playurl .= '?starttime=' . $playbackTimestamp;
  927.             }
  928.         }
  929.         
  930.         return $playurl;
  931.     }
  932.    
  933.     /**
  934.      * 发送HTTP请求
  935.      * @param array $params 请求参数
  936.      * @return array 请求结果
  937.      */
  938.     private function sendHttpRequest($params)
  939.     {
  940.         $url = "https://bkliveinfo.ysp.cctv.cn";
  941.         $query_string = http_build_query($params);
  942.         
  943.         $ch = curl_init();
  944.         curl_setopt($ch, CURLOPT_URL, $url . '?' . $query_string);
  945.         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  946.         curl_setopt($ch, CURLOPT_HTTPHEADER, [
  947.             'User-Agent: qqlive',
  948.             'Connection: Keep-Alive',
  949.             'Accept: application/json'
  950.         ]);
  951.         curl_setopt($ch, CURLOPT_TIMEOUT, 15);
  952.         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  953.         curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
  954.         curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  955.         
  956.         $response = curl_exec($ch);
  957.         $error = curl_error($ch);
  958.         $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  959.         curl_close($ch);
  960.         
  961.         if ($error) {
  962.             return [
  963.                 'success' => false,
  964.                 'error' => 'cURL错误: ' . $error,
  965.                 'http_code' => $http_code
  966.             ];
  967.         }
  968.         
  969.         $data = json_decode($response, true);
  970.         if ($data) {
  971.             if (isset($data['iretcode'])) {
  972.                 $result = [
  973.                     'success' => $data['iretcode'] == 0,
  974.                     'iretcode' => $data['iretcode'],
  975.                     'http_code' => $http_code,
  976.                     'response' => $data
  977.                 ];
  978.                
  979.                 if ($data['iretcode'] == 0) {
  980.                     $result['playurl'] = $data['playurl'] ?? null;
  981.                 } else {
  982.                     $result['error'] = $data['errinfo'] ?? '未知错误';
  983.                 }
  984.                
  985.                 return $result;
  986.             }
  987.         }
  988.         
  989.         return [
  990.             'success' => false,
  991.             'error' => '无效的JSON响应',
  992.             'http_code' => $http_code,
  993.             'raw_response' => substr($response, 0, 500)
  994.         ];
  995.     }
  996.    
  997.     /**
  998.      * 简化版获取播放地址(支持回看)
  999.      * @param string $cnlid 频道ID
  1000.      * @param string $livepid 直播ID(可选,默认600001859)
  1001.      * @param string $defn 清晰度(可选,默认fhd)
  1002.      * @param string|null $playseek 回看时间(可选,null表示直播)
  1003.      * @return string|null 播放地址或null
  1004.      */
  1005.     public function getPlayUrl($cnlid, $livepid = '600001859', $defn = 'fhd', $playseek = null)
  1006.     {
  1007.         $result = $this->makeLiveRequest($cnlid, $livepid, $defn, $playseek);
  1008.         if ($result['success'] && isset($result['playurl'])) {
  1009.             return $result['playurl'];
  1010.         }
  1011.         return null;
  1012.     }
  1013.    
  1014.     // ================== 数据包解析方法 ===================
  1015.    
  1016.     /**
  1017.      * 解析数据包
  1018.      * @param string $data 数据包二进制数据
  1019.      * @return array 解析后的字段数组
  1020.      */
  1021.     public function parsePacket($data)
  1022.     {
  1023.         if (strlen($data) < 2) {
  1024.             return false;
  1025.         }
  1026.         
  1027.         $pos = 0;
  1028.         $result = [];
  1029.         
  1030.         // 1. 长度头 (2字节)
  1031.         $result['packet_len'] = unpack('n', substr($data, $pos, 2))[1];
  1032.         $pos += 2;
  1033.         
  1034.         // 2. 固定头部 (12字节)
  1035.         $result['header'] = substr($data, $pos, 12);
  1036.         $pos += 12;
  1037.         
  1038.         // 3. Platform (4字节)
  1039.         $result['platform'] = unpack('N', substr($data, $pos, 4))[1];
  1040.         $pos += 4;
  1041.         
  1042.         // 4. Signature (4字节)
  1043.         $result['signature'] = unpack('N', substr($data, $pos, 4))[1];
  1044.         $pos += 4;
  1045.         
  1046.         // 5. Timestamp (4字节)
  1047.         $result['timestamp'] = unpack('N', substr($data, $pos, 4))[1];
  1048.         $pos += 4;
  1049.         
  1050.         // 6. Sdtfrom (长度+字符串)
  1051.         $sdtfrom_len = unpack('n', substr($data, $pos, 2))[1];
  1052.         $pos += 2;
  1053.         $result['sdtfrom'] = substr($data, $pos, $sdtfrom_len);
  1054.         $pos += $sdtfrom_len;
  1055.         
  1056.         // 7. randFlag (长度+字符串)
  1057.         $randFlag_len = unpack('n', substr($data, $pos, 2))[1];
  1058.         $pos += 2;
  1059.         $result['randFlag'] = substr($data, $pos, $randFlag_len);
  1060.         $pos += $randFlag_len;
  1061.         
  1062.         // 8. appVer (长度+字符串)
  1063.         $appVer_len = unpack('n', substr($data, $pos, 2))[1];
  1064.         $pos += 2;
  1065.         $result['appVer'] = substr($data, $pos, $appVer_len);
  1066.         $pos += $appVer_len;
  1067.         
  1068.         // 9. vid (长度+字符串)
  1069.         $vid_len = unpack('n', substr($data, $pos, 2))[1];
  1070.         $pos += 2;
  1071.         $result['vid'] = substr($data, $pos, $vid_len);
  1072.         $pos += $vid_len;
  1073.         
  1074.         // 10. guid (长度+字符串)
  1075.         $guid_len = unpack('n', substr($data, $pos, 2))[1];
  1076.         $pos += 2;
  1077.         $result['guid'] = substr($data, $pos, $guid_len);
  1078.         $pos += $guid_len;
  1079.         
  1080.         // 11. part1 (4字节)
  1081.         $result['part1'] = unpack('N', substr($data, $pos, 4))[1];
  1082.         $pos += 4;
  1083.         
  1084.         // 12. isDlna (4字节)
  1085.         $result['isDlna'] = unpack('N', substr($data, $pos, 4))[1];
  1086.         $pos += 4;
  1087.         
  1088.         // 13. uid (长度+字符串)
  1089.         $uid_len = unpack('n', substr($data, $pos, 2))[1];
  1090.         $pos += 2;
  1091.         $result['uid'] = substr($data, $pos, $uid_len);
  1092.         $pos += $uid_len;
  1093.         
  1094.         // 14. bundleID (长度+字符串)
  1095.         $bundleID_len = unpack('n', substr($data, $pos, 2))[1];
  1096.         $pos += 2;
  1097.         $result['bundleID'] = substr($data, $pos, $bundleID_len);
  1098.         $pos += $bundleID_len;
  1099.         
  1100.         // 15. uuid4 (长度+字符串)
  1101.         $uuid4_len = unpack('n', substr($data, $pos, 2))[1];
  1102.         $pos += 2;
  1103.         $result['uuid4'] = substr($data, $pos, $uuid4_len);
  1104.         $pos += $uuid4_len;
  1105.         
  1106.         // 16. bundleID1 (长度+字符串)
  1107.         $bundleID1_len = unpack('n', substr($data, $pos, 2))[1];
  1108.         $pos += 2;
  1109.         $result['bundleID1'] = substr($data, $pos, $bundleID1_len);
  1110.         $pos += $bundleID1_len;
  1111.         
  1112.         // 17. ckeyVersion (长度+字符串)
  1113.         $ckeyVersion_len = unpack('n', substr($data, $pos, 2))[1];
  1114.         $pos += 2;
  1115.         $result['ckeyVersion'] = substr($data, $pos, $ckeyVersion_len);
  1116.         $pos += $ckeyVersion_len;
  1117.         
  1118.         // 18. packageName (长度+字符串)
  1119.         $packageName_len = unpack('n', substr($data, $pos, 2))[1];
  1120.         $pos += 2;
  1121.         $result['packageName'] = substr($data, $pos, $packageName_len);
  1122.         $pos += $packageName_len;
  1123.         
  1124.         // 19. platform_str (长度+字符串)
  1125.         $platform_str_len = unpack('n', substr($data, $pos, 2))[1];
  1126.         $pos += 2;
  1127.         $result['platform_str'] = substr($data, $pos, $platform_str_len);
  1128.         $pos += $platform_str_len;
  1129.         
  1130.         // 20. ex_json_bus (长度+字符串)
  1131.         $ex_json_bus_len = unpack('n', substr($data, $pos, 2))[1];
  1132.         $pos += 2;
  1133.         $result['ex_json_bus'] = substr($data, $pos, $ex_json_bus_len);
  1134.         $pos += $ex_json_bus_len;
  1135.         
  1136.         // 21. ex_json_vs (长度+字符串)
  1137.         $ex_json_vs_len = unpack('n', substr($data, $pos, 2))[1];
  1138.         $pos += 2;
  1139.         $result['ex_json_vs'] = substr($data, $pos, $ex_json_vs_len);
  1140.         $pos += $ex_json_vs_len;
  1141.         
  1142.         // 22. ck_guard_time (长度+字符串)
  1143.         $ck_guard_time_len = unpack('n', substr($data, $pos, 2))[1];
  1144.         $pos += 2;
  1145.         $result['ck_guard_time'] = substr($data, $pos, $ck_guard_time_len);
  1146.         $pos += $ck_guard_time_len;
  1147.         
  1148.         $result['total_size'] = strlen($data);
  1149.         $result['parsed_size'] = $pos;
  1150.         $result['remaining'] = substr($data, $pos);
  1151.         
  1152.         return $result;
  1153.     }
  1154.    
  1155.     /**
  1156.      * 验证cKey
  1157.      * @param string $ckey cKey字符串
  1158.      * @return bool 是否验证通过
  1159.      */
  1160.     public function verifyCKey($ckey)
  1161.     {
  1162.         $decrypt_result = $this->decryptCKeyToData($ckey);
  1163.         if (!$decrypt_result) {
  1164.             return false;
  1165.         }
  1166.         
  1167.         $calculated_checksum = $this->calcSignature(array_values(unpack('C*', $decrypt_result['data'])));
  1168.         return $decrypt_result['checksum'] == $calculated_checksum;
  1169.     }
  1170.    
  1171.     /**
  1172.      * 解析回看时间字符串
  1173.      * @param string $playseek 回看时间字符串(格式:YYYYMMDDHHMMSS-YYYYMMDDHHMMSS)
  1174.      * @return array 包含开始时间和结束时间的数组
  1175.      */
  1176.     public function parsePlayseek($playseek)
  1177.     {
  1178.         $parts = explode('-', $playseek);
  1179.         if (count($parts) !== 2) {
  1180.             throw new Exception("回看时间格式错误,应为: YYYYMMDDHHMMSS-YYYYMMDDHHMMSS");
  1181.         }
  1182.         
  1183.         $startTimeStr = $parts[0];
  1184.         $endTimeStr = $parts[1];
  1185.         
  1186.         $startTime = DateTime::createFromFormat('YmdHis', $startTimeStr, new DateTimeZone('Asia/Shanghai'));
  1187.         $endTime = DateTime::createFromFormat('YmdHis', $endTimeStr, new DateTimeZone('Asia/Shanghai'));
  1188.         
  1189.         if ($startTime === false || $endTime === false) {
  1190.             throw new Exception("回看时间解析失败");
  1191.         }
  1192.         
  1193.         return [
  1194.             'start_time' => $startTime,
  1195.             'end_time' => $endTime,
  1196.             'start_timestamp' => $startTime->getTimestamp(),
  1197.             'end_timestamp' => $endTime->getTimestamp(),
  1198.             'start_str' => $startTime->format('Y-m-d H:i:s'),
  1199.             'end_str' => $endTime->format('Y-m-d H:i:s'),
  1200.             'duration' => $endTime->getTimestamp() - $startTime->getTimestamp()
  1201.         ];
  1202.     }
  1203.    
  1204.     /**
  1205.      * 生成回看时间字符串
  1206.      * @param string $startDateTime 开始时间(格式:Y-m-d H:i:s)
  1207.      * @param string $endDateTime 结束时间(格式:Y-m-d H:i:s)
  1208.      * @return string 回看时间字符串
  1209.      */
  1210.     public function generatePlayseek($startDateTime, $endDateTime)
  1211.     {
  1212.         $startTime = DateTime::createFromFormat('Y-m-d H:i:s', $startDateTime, new DateTimeZone('Asia/Shanghai'));
  1213.         $endTime = DateTime::createFromFormat('Y-m-d H:i:s', $endDateTime, new DateTimeZone('Asia/Shanghai'));
  1214.         
  1215.         if ($startTime === false || $endTime === false) {
  1216.             throw new Exception("时间格式错误,应为: Y-m-d H:i:s");
  1217.         }
  1218.         
  1219.         return $startTime->format('YmdHis') . '-' . $endTime->format('YmdHis');
  1220.     }
  1221. }

  1222. $ckeyManager = new CKeyManager();
  1223. $playseek = $_GET['playseek'] ?? null;
  1224. // 缓存配置(仅用于直播)
  1225. $cookieKey = 'playurl_cache';
  1226. $cacheTimeoutLive = 80; // 直播缓存超时 4分钟
  1227. $cookieExpire = time() + 3600; // Cookie本身有效期1小时

  1228. // 读取缓存
  1229. $cacheJson = $_COOKIE[$cookieKey] ?? '{}';
  1230. $cache = json_decode($cacheJson, true) ?: [];

  1231. $now = time();
  1232. $isLive = ($playseek === null || $playseek === ''); // 无playseek参数视为直播

  1233. $playUrl = null;
  1234. $m3u8Content = false;
  1235. $maxAttempts = 2;

  1236. for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
  1237.     $needRefresh = true; // 默认需要刷新

  1238.     // 仅在第一次尝试且为直播时检查缓存
  1239.     if ($attempt == 1 && $isLive) {
  1240.         if (isset($cache[$id]) && is_array($cache[$id])) {
  1241.             $entry = $cache[$id];
  1242.             if (($now - $entry['time']) <= $cacheTimeoutLive) {
  1243.                 $needRefresh = false;
  1244.                 $playUrl = $entry['url'];
  1245.             }
  1246.         }
  1247.     }
  1248.     // 点播模式或直播缓存失效/不存在时,needRefresh保持true

  1249.     if ($needRefresh) {
  1250.         $playUrl = $ckeyManager->getPlayUrl($n[$id][0], $n[$id][1], $n[$id][2], $playseek);
  1251.         if (!$playUrl) {
  1252.             die("获取播放地址失败\n");
  1253.         }

  1254.         // 只有直播才更新缓存
  1255.         if ($isLive) {
  1256.             $cache[$id] = [
  1257.                 'url' => $playUrl,
  1258.                 'time' => $now
  1259.             ];
  1260.             setcookie($cookieKey, json_encode($cache), $cookieExpire, '/');
  1261.         } else {
  1262.             // 点播模式直接跳转(不输出M3U8)
  1263.             header("Location: " . $playUrl);
  1264.             exit();
  1265.         }
  1266.     }

  1267.     // 获取 M3U8 内容(仅直播模式会走到这里,点播已在上面跳转)
  1268.     $m3u8Content = @file_get_contents($playUrl);
  1269.     if ($m3u8Content === false) {
  1270.         $m3u8Content = false; // 明确设为false,进入后续重试判断
  1271.     }

  1272.     if ($m3u8Content !== false) {
  1273.         break; // 成功获取,跳出循环
  1274.     }

  1275.     // 获取失败:如果是第一次尝试且使用了缓存地址,则清除缓存
  1276.     if ($attempt == 1 && $isLive && !$needRefresh) {
  1277.         unset($cache[$id]);
  1278.         setcookie($cookieKey, json_encode($cache), $cookieExpire, '/');
  1279.     }
  1280.     // 继续下一次尝试
  1281. }

  1282. if ($m3u8Content === false) {
  1283.     die("无法获取 M3U8 内容,请稍后重试\n");
  1284. }

  1285. // 补全 TS 路径(仅直播)
  1286. $baseUrl = substr($playUrl, 0, strrpos($playUrl, '/') + 1);
  1287. header('Content-Type: application/vnd.apple.mpegurl');
  1288. print_r(preg_replace("/(.*?.ts)/i", $baseUrl."$1",$m3u8Content));
  1289. exit();
复制代码


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关注公众号

相关侵权、举报、投诉及建议等,请发 E-mail:admin@discuz.vip

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖
关注公众号
返回顶部