目前可以正常去除西瓜资源、飞凡资源、电影天堂资源站的菠菜广告。(推荐用西瓜资源,晚高峰不卡,去广告简单)
服务器不能在欧美地区,因为飞凡资源站封禁了欧美地区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)