php - 如何通过在浏览器中粘贴 URL 来创建动态 m3u8?

标签 php m3u8

我想在调用 PHP 脚本时创建动态 m3u8。我不想将结果 m3u8 保存在服务器上,而是想将其推送 到浏览器以便可以下载。谁能告诉我如何完成这项任务?

要调用的 PHP 脚本示例:

http://www.asite.com/makeM3u8.php?videoId=1234

可下载的动态 m3u8 结构:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=900000
http://someserver/channelNameBandwith900000.m3u8?session=3495732948523984eriuwehiurweirew

最佳答案

在开始编写脚本之前,您必须决定一些事情:

1.- .ts 和 .aac 文件存储在哪里,它们的保护方案是什么,PHP 是否能够创建能够访问它们的 URL?

2.- 存储原始 m3u8 信息的位置(每个片段的目标持续时间、extinf 和名称)。在这种情况下,数据库比解析现有文件更快。

3.- 如果处理多比特率,您需要一个脚本来生成指向所有其他 m3u8 的主 m3u8。

话虽这么说,这是我想出的解决方案,并且已经使用了一段时间没有问题。不过有两件事,我使用 AWS S3 进行存储并使用 ffmpeg 转换现有的视频文件。这是一个相当漫长的过程 - 可能对你想要的东西有点矫枉过正 - 但它确实有效。

第 1 部分.- 编码文件 系统接收 MP4 视频并将其转换为所需格式。

function ffConvert($origin,$basedir,$res) {
switch($res) {
    // SET THE VARIABLES
    case "240p": $size = "426x240"; $vbit = "360k"; $level = "3.0"; $abit = "80k"; break;
    case "480p": $size = "854x480"; $vbit = "784k"; $level = "3.1"; $abit = "128k"; break;
    case "720p": $size = "1280x720"; $vbit = "1648k"; $level = "3.1"; $abit = "192k"; break;
}
// CONVERT THE FILES
exec('/usr/local/bin/ffmpeg -y -async 1 -vsync -1 -analyzeduration 999999999 -i "'.$origin.'" -force_key_frames "expr:gte(t,n_forced*10)" -pix_fmt yuv420p -s '.$size.' -r:v 30 -b:v '.$vbit.' -c:v libx264 -profile:v baseline -level '.$level.' -c:a libfaac -ac 2 -ar 48000 -b:a '.$abit.' -g 90 '.$base.$res.'.mp4');
// VERIFY AND RETURN
if(file_exists($basedir.$res.'.mp4')) {
    return TRUE;
} else {
    return FALSE;
}
}

第 2 部分.- 分割文件 系统获取转换后的 MP4 并分割它们。

function ffSegment($basedir,$res) {
// CREATE THE SEGMENTS
mkdir($basedir.$res, 0775);
exec('/usr/local/bin/ffmpeg -y -analyzeduration 999999999 -i "'.$basedir.$res.'.mp4" -codec copy -map 0 -f segment -segment_list "'.$basedir.$res.'/stream.m3u8" -segment_time 10 -segment_list_type m3u8 -bsf:v h264_mp4toannexb "'.$basedir.$res.'/segment%05d.ts"');
if(file_exists($basedir.$res.'/stream.m3u8')) {
    return TRUE;
} else {
    return FALSE;
}
}

第 3 部分.- 存储数据 系统解析生成的 m3u8 并将相关信息存储在数据库中。

表格:

CREATE TABLE IF NOT EXISTS `data_contenido_archivos` (
    `id` bigint(20) unsigned NOT NULL,
    `240p` mediumtext NOT NULL,
    `480p` mediumtext NOT NULL,
    `720p` mediumtext NOT NULL,
    `aac` mediumtext NOT NULL,
    UNIQUE KEY `id` (`id`)
) ENGINE=TokuDB DEFAULT CHARSET=utf8;

解析函数:

function parseHLS($file) {
$return = array();
$i = 0;
$handle = fopen($file, "r");
if($handle) {
    while(($line = fgets($handle)) !== FALSE) {
        if(strpos($line,"#EXTINF") !== FALSE) {
            $return['data'][$i]['inf'] = str_replace(array("#EXTINF:",",","\r","\n"),array("","","",""),$line);
        }
        if(strpos($line,".ts") !== FALSE) {
            $return['data'][$i]['ts'] = str_replace(array(".ts","segment","\r","\n"),array("","","",""),$line);
            $i++;
        }
        if(strpos($line,"TARGETDURATION") !== FALSE) {
            $return['duration'] = str_replace(array("#EXT-X-TARGETDURATION:","\r","\n"),array("","","",""),$line);
        }
    }
    fclose($handle);
} else {
    $retorno = FALSE;
}
return $return;
}

每个文件的结果作为 json 编码字符串存储在每个视频大小的相关列中,请注意,我存储了尽可能少的信息,以便最大限度地减少读取时间并使 dB 尽可能小。在此步骤中,速度并不重要,因为这是在提供文件之前完成的。

第 4 部分。- 提供文件 系统读取数据库并为每种尺寸提供正确的文件。

为此,我设置了一个 m3u8.domain.com,它将所有 *.m3u8 文件发送到 PHP 解释器,因此我不必费心重命名,它只提供以下文件:

  • 跨域.xml
  • 240p.m3u8:240p分辨率的脚本
  • 480p.m3u8:480p分辨率的脚本
  • 720p.m3u8:720p 分辨率的脚本
  • aac.m3u8:纯音频脚本
  • master.m3u8:master m3u8 脚本

每个都是它自己的文件,因为如果不同带宽的 m3u8 都具有相同的名称,一些播放器会遇到问题。

master.m3u8 这样做:

if($sesion !== FALSE) {
$hls = sql("SELECT A.240p,A.480p,A.720p,A.aac,B.duracion FROM data_contenido_archivos A, data_contenido B WHERE A.id = '".limpia($_GET['i'])."' AND B.id = '".limpia($_GET['i'])."'");
if($hls['status'] == "OK") {
    $return = "#EXTM3U\n";
    if($hls['data'][0]['240p'] != "{}") {
        $return .= "#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=440000, RESOLUTION=426x240\n";
        $return .= $domains['m3u8']."/240p.m3u8?i=".$id."&s=".$sesion."\n";
    }
    if($hls['data'][0]['480p'] != "{}") {
        $return .= "#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=912000, RESOLUTION=854x480\n";
        $return .= $domains['m3u8']."/480p.m3u8?i=".$id."&s=".$sesion."\n";
    }
    if($hls['data'][0]['720p'] != "{}") {
        $return .= "#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1840000, RESOLUTION=1280x720\n";
        $return .= $domains['m3u8']."/720p.m3u8?i=".$id."&s=".$sesion."\n";
    }
    if($hls['data'][0]['aac'] != "{}") {
        $retorno .= "#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=64000\n";
        $retorno .= $domains['m3u8']."/aac.m3u8?i=".$id."&s=".$sesion."\n";
    }
}
}
header("Content-type: application/x-mpegURL");
echo $return;

它在数据库中查询所有不同的分辨率,并为每个分辨率回显正确的 url。如果它不存在(在我的方案中设置为 {}),它就不会被回显。

然后每个 res.m3u8 执行以下操作(调用时):

if($sesion) {
$hls = sql("SELECT A.240p,B.duracion FROM data_contenido_archivos A, data_contenido B WHERE A.id = '".limpia($_GET['i'])."' AND B.id = '".limpia($_GET['i'])."'");
if($hls['estado'] == "OK") {
    $data = json_decode($hls['data'][0]['240p'],TRUE);
    // INICIAMOS EL ARCHIVO
    $retorno = "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-MEDIA-SEQUENCE:0\n#EXT-X-ALLOW-CACHE:YES\n#EXT-X-TARGETDURATION:".$data['duration']."\n";
    // CALCULAMOS EL VENCIMIENTO (1.5x DURACION DEL VIDEO)
    $vence = "+".floor($hls['data'][0]['duracion'] / 60)." minutes";
    foreach($data['data'] as $k=>$v) {
        $retorno .= "#EXTINF:".$v['inf'].",\n";
        $retorno .= S3URL("<BUCKET>",$_GET['i']."/240p/segment".$v['ts'].".ts",$vence)."\n";
    }
    $retorno .= "#EXT-X-ENDLIST\n";
}
}
header("Content-type: application/x-mpegURL");
echo $retorno;

这里发生了一些事情所以让我解释一下:

a.- 首先,脚本会检查是否存在有效 session ,如果不存在,它会为一个 10 秒的小视频提供一个 m3u8,说“你没有权限查看这个”。

b.- 如果 session checkout ,它会查询数据库并获取所有必需的 JSON。它还查询我存储文件持续时间的另一个表,以便填充 TARGETDURATION 字符串并计算要生成的安全 S3 URL 的生命周期。我将 URL 的生命周期设置为视频长度的 1.5 倍,它适用于我,您的体验可能会有所不同。

c.- 然后它遍历数据库中的每个组,回显 EXTINF 并为每个组生成一个安全 URL。

关于php - 如何通过在浏览器中粘贴 URL 来创建动态 m3u8?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25407474/

相关文章:

php - 打印时 IE 不重新发送 cookie

php - Doctrine2 多级继承

php - 如何使 jQuery load() 适用于数组的所有元素,而不仅仅是最后一个元素

ffmpeg - 流式传输视频时是否真正连接字幕(HLS,M3U8)

FFmpeg HLS 流式传输 - 为什么在中断后重新流式传输时旧段的节目日期时间会发生变化

php - Codeigniter 关联数组 - 在 foreach 循环中更改数组名称

php - 维基百科 Curl 请求错误

jwplayer - 在 FlowPlayer 或 JWPlayer 上播放 m3u8 和 RTMP 和 RTSP?

javascript - Node - 将 m3u8 流保存为 mp3 文件

java - 保存 HLS 流?