Как создать динамический m3u8, вставив URL-адрес в браузер?

Я хочу создать динамический m3u8 при вызове PHP-скрипта. Я не хочу сохранять результат 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

person user1788736    schedule 20.08.2014    source источник
comment
Я так понимаю, вы хотите динамически загрузить файл M3U, преобразовать его в реальном времени и отправить пользователю для загрузки? Это возможно. Какие размеры файлов у ваших входных файлов? Какие исследования вы уже провели для решения этой проблемы?   -  person halfer    schedule 20.08.2014


Ответы (1)


Прежде чем перейти к сценарию, вы должны решить несколько вещей:

1.- Где хранятся файлы .ts и .aac, какова их схема защиты и может ли PHP создать URL-адрес, который может получить к ним доступ?

2.- Где вы храните необработанную информацию m3u8 (целевая продолжительность, расширение и имя для каждого сегмента). В этом случае база данных работает быстрее, чем анализ существующих файлов.

3.- Если вы имеете дело с мультибитрейтом, вам нужен скрипт, который также генерирует мастер 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. Обратите внимание, что я храню минимально возможную информацию, чтобы минимизировать время чтения и сделать дБ как можно меньше. На этом шаге скорость не имеет большого значения, так как это делается до подачи файла.

Часть 4. – Подача файла. Система считывает базу данных и подает правильный файл для каждого размера.

Для этого у меня настроен m3u8.domain.com, который отправляет все файлы *.m3u8 интерпретатору PHP, поэтому я не беспокоюсь о переименовании, это обслуживает только следующие файлы:

  • crossdomain.xml
  • 240p.m3u8: скрипт для разрешения 240p
  • 480p.m3u8: скрипт для разрешения 480p
  • 720p.m3u8: скрипт для разрешения 720p
  • aac.m3u8: скрипт только для аудио
  • master.m3u8: скрипт для мастера m3u8

Каждый из них представляет собой отдельный файл, потому что у некоторых игроков были проблемы, если все m3u8 с разной пропускной способностью имели одинаковое имя.

Мастер.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.- Сначала скрипт проверяет действующий сеанс, если он не существует, он подает m3u8 для небольшого 10-секундного видео, в котором говорится: «У вас нет разрешения на просмотр этого».

b.- Если сеанс завершается, он запрашивает базу данных и получает все необходимые JSON. Он также запрашивает другую таблицу, в которой я храню продолжительность файла, чтобы заполнить строку TARGETDURATION, а также рассчитать срок действия безопасного URL-адреса S3 для создания. Я установил время жизни URL-адреса в 1,5 раза больше длины видео, это работает для меня, ваш опыт может отличаться.

c.- Затем он выполняет итерацию по каждой группе из базы данных, повторяет EXTINF и генерирует безопасный URL-адрес для каждой из них.

person hdezela    schedule 12.03.2015
comment
Как выглядит ваш aac.m3u8? Вы не объяснили звуковые шаги aac! - person jankal; 29.11.2016