You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP基于session防视频下载Safari失效及文件路径配置问题咨询

PHP视频地址隐藏脚本问题修复方案

问题根因说明

  • 苹果设备/Safari无法播放:一是原代码存在$_SSEION拼写错误,Safari对会话校验逻辑更严格,直接导致验证失败;二是视频输出逻辑未支持Range分段请求,Safari播放视频强制要求支持分段加载,原代码一次性全量输出文件不符合要求。
  • 子目录路径失效:原代码文件存在检查路径和读取路径不一致的bug,校验时读取videos/目录下的文件,实际读取的时候直接读当前目录文件,因此需要同时在两个目录存放视频才能运行。

修复后完整代码

index.php(入口文件)

<?php 
/**
* index.php - 入口文件
**/
session_start();
session_regenerate_id();
// 修复会话变量拼写错误
$_SESSION['setToken'] = true;

// 可直接指定子目录路径,例:"subdir/vid.mp4"
$token = "vid.mp4";

$token_encrypted = openssl_encrypt($token, "aes128", session_id());
?>

<video width="320" height="240" controls>
  <!-- 补充video格式声明,兼容Safari识别 -->
  <source src="video.php?vid=<?php echo $token_encrypted; ?>" type="video/mp4">
</video>

video.php(跳转校验文件)

<?php
/**
* video.php - 跳转校验文件
**/
session_start();
$token = $_GET['vid'];
$prev_sid = session_id();

if(isset($_SESSION['setToken']))
{
    unset($_SESSION['setToken']);
    $origin_token = openssl_decrypt($token, "aes128", session_id());
    // 新增目录遍历防护
    if(strpos($origin_token, '..') !== false) {
        session_regenerate_id(true);
        die("非法请求");
    }
    session_regenerate_id();
    $new_token = openssl_encrypt($origin_token, "aes128", $prev_sid.session_id());
    // 参数做URL编码避免特殊字符异常
    header("Location: access.php?id=".urlencode($prev_sid)."&vid=".urlencode($new_token));
    exit;
}
else
{
    session_regenerate_id(true);
    die("无效请求");
}
?>

access.php(视频输出文件)

<?php
/**
* access.php - 视频输出文件
**/
session_start();

$token = openssl_decrypt($_GET['vid'], "aes128", $_GET['id'].session_id());
session_regenerate_id(true);

// 合法性校验
if(empty($token) || strpos($token, '..') !== false) {
    header("HTTP/1.1 403 Forbidden");
    die("非法请求");
}

// 统一文件路径,无需在多目录存放视频
$filePath = "videos/".$token;
if(!file_exists($filePath) || !is_file($filePath)) {
    header("HTTP/1.1 404 Not Found");
    die("Error: File Does not exist");
}

$fileSize = filesize($filePath);
$seekStart = 0;
$seekEnd = $fileSize - 1;

// 新增Range请求支持,兼容Safari分段加载要求
if(isset($_SERVER['HTTP_RANGE'])) {
    $range = $_SERVER['HTTP_RANGE'];
    list($sizeUnit, $rangeInfo) = explode('=', $range, 2);
    if($sizeUnit == 'bytes') {
        list($seekStart, $seekEnd) = explode('-', $rangeInfo);
        $seekStart = intval($seekStart);
        $seekEnd = ($seekEnd == '' ? $fileSize - 1 : intval($seekEnd));
        $seekEnd = min($seekEnd, $fileSize - 1);
        if($seekStart > $seekEnd || $seekStart >= $fileSize) {
            header("HTTP/1.1 416 Requested Range Not Satisfiable");
            header("Content-Range: bytes */$fileSize");
            exit;
        }
        header("HTTP/1.1 206 Partial Content");
        header("Content-Range: bytes $seekStart-$seekEnd/$fileSize");
    }
} else {
    header("HTTP/1.1 200 OK");
}

// 输出标准响应头
header("Content-Type: video/mp4");
header("Accept-Ranges: bytes");
header("Content-Length: " . ($seekEnd - $seekStart + 1));
header("Cache-Control: no-cache, no-store, must-revalidate");
header("Pragma: no-cache");
header("Expires: 0");

// 分段输出视频内容,避免大文件内存溢出
$fp = fopen($filePath, "rb");
fseek($fp, $seekStart);
$bufferSize = 1024 * 1024;
while(!feof($fp) && ($pos = ftell($fp)) <= $seekEnd) {
    if($pos + $bufferSize > $seekEnd) {
        $bufferSize = $seekEnd - $pos + 1;
    }
    echo fread($fp, $bufferSize);
    ob_flush();
    flush();
}
fclose($fp);
exit;
?>

内容的提问来源于stack exchange,提问作者murat tansu özyol

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 10:09:02