资源站视频去广告PHP代码(加强版)

目前可以正常去除西瓜资源、飞凡资源、电影天堂资源站的菠菜广告。(推荐用西瓜资源,晚高峰不卡,去广告简单)

服务器不能在欧美地区,因为飞凡资源站封禁了欧美地区IP,另外由于curl有时限,如果服务器加载m3u8列表时间长,可能会无法完成加载。

使用方法:
1.json

https://yourdomain.com/process.php?url=https://svip.xgplay17.com/2026-08-31/37579_YAI_uDcRGlnxTtKkyo/index.m3u8

返回json格式如下,可用于typecho插件VideoCollector中的artplayer模式

{
    code: 200,
    url: "https://yourdomain.com/process.php?raw=1&url=https://svip.xgplay17.com/2026-08-31/37579_YAI_uDcRGlnxTtKkyo/index.m3u8"
}

2.链接带 raw=1 参数

https://yourdomain.com/process.php?raw=1&url=https://svip.xgplay17.com/2026-08-31/37579_YAI_uDcRGlnxTtKkyo/index.m3u8

返回m3u8格式,可直接用到网页播放器

代码如下:

<?php
/**
 * M3U8处理工具
 * 功能:
 * 1. 获取m3u8文件内容(自动跟随302跳转,以最终地址为基准;若为主m3u8则自动转到第一个子m3u8)
 * 2. 片段移除:域名包含xgplay时固定移除第150-152个片段(连同其关联的#EXTINF等标签行);
 *    域名包含ffzy或dytt或phimgood时,改为删除特定的EXT-X-DISCONTINUITY包裹组(第二和第三个,第四和第五个,以此类推);
 *    其他域名不移除任何片段,仅补全片段地址
 * 3. 按HLS规范将片段相对地址补全为完整URL(/开头用域名,其余用m3u8所在目录)
 * 4. 不在服务器缓存任何文件,处理结果由本工具实时输出
 * 5. 以JSON格式返回:{code: 200, url: 临时合并的m3u8地址}
 *    (该地址指向本工具的实时输出模式?raw=1,播放器直接请求该地址即可播放,每次访问均实时生成)
 */

// 兼容PHP 7.x的前缀检查函数
if (!function_exists('str_starts_with')) {
    function str_starts_with($haystack, $needle) {
        return substr($haystack, 0, strlen($needle)) === $needle;
    }
}

// 设置错误报告
ini_set('display_errors', 0);
error_reporting(E_ALL);
@set_time_limit(150); // 主/子m3u8两次请求各最长60秒,放宽脚本执行时间避免被PHP提前掐断(被禁用时静默跳过,避免警告污染JSON输出)

// raw=1时为实时输出模式:直接输出处理后的m3u8内容(供播放器请求);否则以JSON格式返回地址
$isRaw = isset($_GET['raw']);

header('Access-Control-Allow-Origin: *'); // 允许跨域访问
header('Content-Type: ' . ($isRaw ? 'application/vnd.apple.mpegurl' : 'application/json') . '; charset=utf-8');

// 检查是否提供了m3u8 URL参数
if (!isset($_GET['url']) || empty($_GET['url'])) {
    dieJson(400, '请提供m3u8文件URL');
}

$m3u8Url = $_GET['url'];

// 判断url参数的域名:包含ffzy/dytt/phimgood则使用EXT-X-DISCONTINUITY包裹组删除方案;
// 包含xgplay则使用固定移除第150-152个片段方案;否则不移除任何片段(仅补全地址)
$m3u8Host = (string)parse_url($m3u8Url, PHP_URL_HOST);
$useDiscontinuityScheme = stripos($m3u8Host, 'ffzy') !== false
    || stripos($m3u8Host, 'dytt') !== false
    || stripos($m3u8Host, 'phimgood') !== false;
$useSegmentRemovalScheme = !$useDiscontinuityScheme && stripos($m3u8Host, 'xgplay') !== false;

// 计算本工具的Web访问地址(用于生成实时输出的m3u8地址)
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
    $scheme = $_SERVER['HTTP_X_FORWARDED_PROTO'];
}
$scriptPath = $_SERVER['SCRIPT_NAME'] ?? '';

/**
 * 以JSON格式输出错误信息并终止
 * @param int $code 错误码
 * @param string $msg 错误信息
 */
function dieJson($code, $msg) {
    http_response_code($code);
    echo json_encode(['code' => $code, 'msg' => $msg], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    exit;
}

/**
 * 获取m3u8文件内容(自动跟随跳转)
 * @param string $url m3u8文件URL
 * @return array|null ['content' => 文件内容, 'url' => 跟随跳转后的最终URL],失败返回null
 */
function getM3u8Content($url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 忽略SSL证书验证
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_TIMEOUT, 60);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
        'Accept: */*',
    ]);

    $content = curl_exec($ch);

    if (curl_errno($ch)) {
        error_log('获取m3u8失败: ' . curl_error($ch));
        return null;
    }

    $statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    $effectiveUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);

    // HTTP错误状态直接视为失败,避免把错误页当m3u8处理
    if ($statusCode >= 400) {
        error_log("获取m3u8失败: HTTP {$statusCode} - {$effectiveUrl}");
        return null;
    }

    // 内容校验:必须包含#EXTM3U标记
    if (strpos($content, '#EXTM3U') === false) {
        error_log("获取m3u8失败: 返回内容不是有效的m3u8格式 - {$effectiveUrl}");
        return null;
    }

    return ['content' => $content, 'url' => $effectiveUrl];
}

/**
 * 按HLS规范将相对地址解析为完整URL
 * - 绝对地址(http://、https://)直接返回
 * - 以/开头按域名根路径解析
 * - 其他相对路径按m3u8所在目录解析(支持../、./)
 * @param string $baseUrl m3u8文件URL(跳转后的最终地址)
 * @param string $relative 片段或子m3u8的地址
 * @return string 完整URL
 */
function resolveUrl($baseUrl, $relative) {
    if (str_starts_with($relative, 'http://') || str_starts_with($relative, 'https://')) {
        return $relative;
    }

    $parts = parse_url($baseUrl);
    $scheme = $parts['scheme'] ?? '';
    $host = $parts['host'] ?? '';
    $port = isset($parts['port']) ? ':' . $parts['port'] : '';
    $origin = "{$scheme}://{$host}{$port}";

    if (str_starts_with($relative, '/')) {
        return $origin . $relative;
    }

    // 取m3u8所在目录并拼接相对路径
    $path = $parts['path'] ?? '/';
    $dir = substr($path, 0, (int)strrpos($path, '/') + 1);

    // 规范化路径中的 ../ 和 ./
    $segments = explode('/', $dir . $relative);
    $resolved = [];
    foreach ($segments as $segment) {
        if ($segment === '..') {
            array_pop($resolved);
        } elseif ($segment !== '.') {
            $resolved[] = $segment;
        }
    }

    return $origin . implode('/', $resolved);
}

/**
 * 从主m3u8中提取第一个子m3u8地址(#EXT-X-STREAM-INF标签后的URI行)
 * @param string $content 主m3u8内容
 * @return string|null 子m3u8地址或null
 */
function getFirstStreamUri($content) {
    $lines = explode("\n", $content);
    $lineCount = count($lines);
    for ($i = 0; $i < $lineCount; $i++) {
        $trimmedLine = trim($lines[$i]);
        if (str_starts_with($trimmedLine, '#EXT-X-STREAM-INF')) {
            // 子m3u8地址为该标签后的下一个非空、非注释行
            for ($j = $i + 1; $j < $lineCount; $j++) {
                $uriLine = trim($lines[$j]);
                if ($uriLine !== '' && !str_starts_with($uriLine, '#')) {
                    return $uriLine;
                }
            }
        }
    }
    return null;
}

/**
 * 处理m3u8内容,可选移除第150-152个片段,并将片段地址补全为完整URL
 * @param string $content m3u8文件内容
 * @param string $m3u8Url 该m3u8文件自身的URL(跳转后的最终地址)
 * @param bool $removeSegments 是否移除第150-152个片段(域名包含xgplay时为true,否则false)
 * @return string 处理后的m3u8内容
 */
function processM3u8Content($content, $m3u8Url, $removeSegments = true) {
    // 固定移除第150-152个片段(从1开始计数)
    $removeFrom = 150;
    $removeTo = 152;

    // 按行分割内容
    $lines = explode("\n", $content);

    // 标记要删除的行
    $linesToDelete = [];

    // 找到要移除的片段及其关联的标签行(如#EXTINF、#EXT-X-BYTERANGE等);不移除片段时跳过该步骤
    if ($removeSegments) {
        $segmentIndex = 0;
        $groupStart = 0; // 当前片段关联标签行的起始位置
        foreach ($lines as $index => $line) {
            $trimmedLine = trim($line);
            if ($trimmedLine !== '' && !str_starts_with($trimmedLine, '#')) {
                // 片段URI行
                $segmentIndex++;
                if ($segmentIndex >= $removeFrom && $segmentIndex <= $removeTo) {
                    // 删除该片段的关联标签行及URI行(保留#EXT-X-DISCONTINUITY作为切割边界的编码切换提示)
                    for ($j = $groupStart; $j <= $index; $j++) {
                        if (str_starts_with(trim($lines[$j]), '#EXT-X-DISCONTINUITY')) {
                            continue;
                        }
                        $linesToDelete[$j] = true;
                    }
                }
                $groupStart = $index + 1;
            }
        }
    }
    
    // 重新构建m3u8内容,同时为ts文件添加前缀
    $processedLines = [];
    foreach ($lines as $index => $line) {
        // 跳过要删除的行
        if (isset($linesToDelete[$index])) {
            continue;
        }
        
        // 将片段地址按HLS规范补全为完整URL
        $trimmedLine = trim($line);
        if (!empty($trimmedLine) && !str_starts_with($trimmedLine, '#')) {
            $processedLines[] = resolveUrl($m3u8Url, $trimmedLine);
        } else {
            $processedLines[] = $line;
        }
    }
    
    return implode("\n", $processedLines);
}

/**
 * 处理m3u8内容(域名包含ffzy/dytt/phimgood时使用):删除特定的EXT-X-DISCONTINUITY包裹组
 * (第二和第三个,第四和第五个,以此类推),并将片段地址补全为完整URL
 * @param string $content m3u8文件内容
 * @param string $m3u8Url 该m3u8文件自身的URL(跳转后的最终地址)
 * @return string 处理后的m3u8内容
 */
function processM3u8ContentByDiscontinuity($content, $m3u8Url) {
    // 按行分割内容
    $lines = explode("\n", $content);

    // 找到所有EXT-X-DISCONTINUITY标记和ENDLIST标记的位置
    $discontinuityPositions = [];
    $endListPosition = -1;
    foreach ($lines as $index => $line) {
        $trimmedLine = trim($line);
        if ($trimmedLine === '#EXT-X-DISCONTINUITY') {
            $discontinuityPositions[] = $index;
        } elseif ($trimmedLine === '#EXT-X-ENDLIST') {
            $endListPosition = $index;
        }
    }

    // 标记要删除的行
    $linesToDelete = [];
    $discontinuityCount = count($discontinuityPositions);

    // 删除第二和第三个(索引1和2)、第四和第五个(索引3和4)……包裹组之间的所有行(含这两个标记本身)
    for ($i = 1; $i < $discontinuityCount; $i += 2) {
        $startIndex = $discontinuityPositions[$i];

        // 结束索引:有下一对时为下一对的起始标记,否则到ENDLIST
        $endIndex = ($i + 1 < $discontinuityCount) ? $discontinuityPositions[$i + 1] : $endListPosition;

        if ($endIndex > $startIndex) {
            for ($j = $startIndex; $j <= $endIndex; $j++) {
                // 保留#EXT-X-ENDLIST标记
                if ($j === $endListPosition) {
                    continue;
                }
                $linesToDelete[$j] = true;
            }
        }
    }

    // 重新构建m3u8内容,同时将片段地址按HLS规范补全为完整URL
    $processedLines = [];
    foreach ($lines as $index => $line) {
        // 跳过要删除的行
        if (isset($linesToDelete[$index])) {
            continue;
        }

        $trimmedLine = trim($line);
        if (!empty($trimmedLine) && !str_starts_with($trimmedLine, '#')) {
            $processedLines[] = resolveUrl($m3u8Url, $trimmedLine);
        } else {
            $processedLines[] = $line;
        }
    }

    return implode("\n", $processedLines);
}

// 获取主m3u8内容
$master = getM3u8Content($m3u8Url);

if ($master === null) {
    dieJson(502, '无法获取m3u8文件内容');
}

// 以跟随跳转后的最终地址为基准
$m3u8Content = $master['content'];
$m3u8Url = $master['url'];

// 若为主m3u8(包含#EXT-X-STREAM-INF),则取第一个子m3u8地址并再次获取
if (strpos($m3u8Content, '#EXT-X-STREAM-INF') !== false) {
    $subM3u8Uri = getFirstStreamUri($m3u8Content);

    if ($subM3u8Uri === null) {
        dieJson(502, '主m3u8中未找到子m3u8地址');
    }

    // 按HLS规范解析子m3u8地址
    $subM3u8Url = resolveUrl($m3u8Url, $subM3u8Uri);

    // 获取子m3u8内容
    $sub = getM3u8Content($subM3u8Url);

    if ($sub === null) {
        dieJson(502, '无法获取子m3u8文件内容');
    }

    // 以子m3u8跳转后的最终地址为基准
    $m3u8Content = $sub['content'];
    $m3u8Url = $sub['url'];
}

// 处理m3u8内容:域名包含ffzy/dytt/phimgood时删除EXT-X-DISCONTINUITY包裹组(第二和第三个,第四和第五个,以此类推);
// 域名包含xgplay时固定移除第150-152个片段;否则不移除任何片段(仅补全地址)
if ($useDiscontinuityScheme) {
    $processedContent = processM3u8ContentByDiscontinuity($m3u8Content, $m3u8Url);
} elseif ($useSegmentRemovalScheme) {
    $processedContent = processM3u8Content($m3u8Content, $m3u8Url);
} else {
    $processedContent = processM3u8Content($m3u8Content, $m3u8Url, false);
}

// 实时输出模式:直接输出处理后的m3u8内容,不在服务器缓存任何文件
if ($isRaw) {
    echo $processedContent;
    exit;
}

// 以JSON格式返回临时合并的m3u8地址(指向本工具实时输出模式,每次访问实时生成)
// 传入原始url参数而非跳转后的最终地址,保证实时输出时的域名判断(ffzy/dytt/phimgood)与首次请求一致
$rawUrl = "{$scheme}://{$_SERVER['HTTP_HOST']}{$scriptPath}?raw=1&url=" . urlencode($_GET['url']);
echo json_encode(['code' => 200, 'url' => $rawUrl], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

评论 (0)