2012-10-24 15 views
6

Sto tentando di modificare un'app server Web personalizzata per lavorare con video HTML5.Richieste HTTP di video HTML e intervallo parziale

Serve una pagina HTML5 con un tag di base <video> e quindi deve gestire le richieste per il contenuto effettivo.

L'unico modo per farlo funzionare è caricare l'intero file video nella memoria e quindi inviarlo di nuovo in una singola risposta. Non è un'opzione pratica. Voglio servirlo pezzo per pezzo: rispedire, ad esempio, 100 kb, e attendere che il browser richieda di più.

vedo una richiesta con le seguenti intestazioni:

http_version = 1.1 
request_method = GET 

Host = ###.###.###.###:## 
User-Agent = Mozilla/5.0 (Windows NT 6.1; WOW64; rv:16.0) Gecko/20100101 Firefox/16.0 
Accept = video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5 
Accept-Language = en-US,en;q=0.5 
Connection = keep-alive 
Range = bytes=0- 

ho cercato di inviare una risposta contenuto parziale:

HTTP/1.1 206 Partial content 
Content-Type: video/mp4 
Content-Range: bytes 0-99999/232725251 
Content-Length: 100000 

ho un paio di richieste GET, come segue

Cache-Control = no-cache 
Connection = Keep-Alive 
Pragma = getIfoFileURI.dlna.org 
Accept = */* 
User-Agent = NSPlayer/12.00.7601.17514 WMFSDK/12.00.7601.17514 
GetContentFeatures.DLNA.ORG = 1 
Host = ###.###.###.###:## 

(con nessuna indicazione che il browser desidera una parte specifica del file.) Non importa cosa invio bac k al browser, il video non viene riprodotto.

Come detto sopra, lo stesso video verrà riprodotto correttamente se provo a inviare l'intero file da 230 MB nello stesso pacchetto HTTP.

C'è un modo per far funzionare tutto correttamente attraverso richieste di contenuto parziale? Sto usando Firefox a scopo di test, ma alla fine deve funzionare con tutti i browser.

+0

Trovo strano che si sta utilizzando Firefox per i test e l'invio di un file MP4, che Firefox non supporta, quindi dovrebbe mai funzionare. –

+0

Finora ho provato Firefox (con plug-in H264) e IE9 sul lato browser, e MP4 e WebM sul lato server. Penso che mi manchi qualcosa. – user434507

+0

Perché si desidera ottenere richieste di intervallo parziale dai client? Consentitegli semplicemente di chiedere ciò che vogliono e inviarlo a loro nel modo più conveniente. Ho lavorato con streaming audio (richiesto da Chrome) e un server personalizzato - ha inviato suoni in tempo reale. Penso che questo sia il modo predefinito e accettabile. Se ti preoccupi del carico della rete, utilizza la regolazione della velocità fino a una velocità sufficiente per una riproduzione video fluida. – Stan

risposta

4

So che questa è una vecchia domanda, ma se ti aiuta puoi provare il seguente "Modello" che utilizziamo nella nostra base di codice.

class Model_DownloadableFile { 
private $full_path; 

function __construct($full_path) { 
    $this->full_path = $full_path; 
} 

public function get_full_path() { 
    return $this->full_path; 
} 

// Function borrowed from (been cleaned up and modified slightly): http://stackoverflow.com/questions/157318/resumable-downloads-when-using-php-to-send-the-file/4451376#4451376 
// Allows for resuming paused downloads etc 
public function download_file_in_browser() { 
    // Avoid sending unexpected errors to the client - we should be serving a file, 
    // we don't want to corrupt the data we send 
    @error_reporting(0); 

    // Make sure the files exists, otherwise we are wasting our time 
    if (!file_exists($this->full_path)) { 
     header('HTTP/1.1 404 Not Found'); 
     exit; 
    } 

    // Get the 'Range' header if one was sent 
    if (isset($_SERVER['HTTP_RANGE'])) { 
     $range = $_SERVER['HTTP_RANGE']; // IIS/Some Apache versions 
    } else if ($apache = apache_request_headers()) { // Try Apache again 
     $headers = array(); 
     foreach ($apache as $header => $val) { 
      $headers[strtolower($header)] = $val; 
     } 
     if (isset($headers['range'])) { 
      $range = $headers['range']; 
     } else { 
      $range = false; // We can't get the header/there isn't one set 
     } 
    } else { 
     $range = false; // We can't get the header/there isn't one set 
    } 

    // Get the data range requested (if any) 
    $filesize = filesize($this->full_path); 
    $length = $filesize; 
    if ($range) { 
     $partial = true; 
     list($param, $range) = explode('=', $range); 
     if (strtolower(trim($param)) != 'bytes') { // Bad request - range unit is not 'bytes' 
      header("HTTP/1.1 400 Invalid Request"); 
      exit; 
     } 
     $range = explode(',', $range); 
     $range = explode('-', $range[0]); // We only deal with the first requested range 
     if (count($range) != 2) { // Bad request - 'bytes' parameter is not valid 
      header("HTTP/1.1 400 Invalid Request"); 
      exit; 
     } 
     if ($range[0] === '') { // First number missing, return last $range[1] bytes 
      $end = $filesize - 1; 
      $start = $end - intval($range[0]); 
     } else if ($range[1] === '') { // Second number missing, return from byte $range[0] to end 
      $start = intval($range[0]); 
      $end = $filesize - 1; 
     } else { // Both numbers present, return specific range 
      $start = intval($range[0]); 
      $end = intval($range[1]); 
      if ($end >= $filesize || (!$start && (!$end || $end == ($filesize - 1)))) { 
       $partial = false; 
      } // Invalid range/whole file specified, return whole file 
     } 
     $length = $end - $start + 1; 
    } else { 
     $partial = false; // No range requested 
    } 

    // Determine the content type 
    $finfo = finfo_open(FILEINFO_MIME_TYPE); 
    $contenttype = finfo_file($finfo, $this->full_path); 
    finfo_close($finfo); 

    // Send standard headers 
    header("Content-Type: $contenttype"); 
    header("Content-Length: $length"); 
    header('Content-Disposition: attachment; filename="' . basename($this->full_path) . '"'); 
    header('Accept-Ranges: bytes'); 

    // if requested, send extra headers and part of file... 
    if ($partial) { 
     header('HTTP/1.1 206 Partial Content'); 
     header("Content-Range: bytes $start-$end/$filesize"); 
     if (!$fp = fopen($this->full_path, 'r')) { // Error out if we can't read the file 
      header("HTTP/1.1 500 Internal Server Error"); 
      exit; 
     } 
     if ($start) { 
      fseek($fp, $start); 
     } 
     while ($length) { // Read in blocks of 8KB so we don't chew up memory on the server 
      $read = ($length > 8192) ? 8192 : $length; 
      $length -= $read; 
      print(fread($fp, $read)); 
     } 
     fclose($fp); 
    } else { 
     readfile($this->full_path); // ...otherwise just send the whole file 
    } 

    // Exit here to avoid accidentally sending extra content on the end of the file 
    exit; 
} 
} 

È quindi possibile utilizzare in questo modo:

(new Model_DownloadableFile('FULL/PATH/TO/FILE'))->download_file_in_browser(); 

Si tratterà di inviare una parte del file o il file etc pieno e funziona bene per noi in questo e un sacco di altre situazioni. Spero che sia d'aiuto.

+1

La riga che legge l'intestazione ("Content-Length: $ filesize"); dovrebbe effettivamente essere intestazione ("Lunghezza contenuto: $ lunghezza"); poiché sono i byte totali consegnati, non le dimensioni del file. Il lettore video di Chrome si blocca su questo se non impostato correttamente. – frankieandshadow

+0

Buon posto, ho corretto il post originale. $ length viene impostato solo se l'intervallo $ è true, quindi ho verificato che sia impostato su $ filesize per primo e venga sovrascritto se $ range è true. –

+0

Il mio problema è che php consegna Content-Type: text/html anche se header_remove ('Conten-Type'); –

1

Desidero richieste di intervallo parziale, perché eseguirò la transcodifica in tempo reale, non posso avere il file completamente transcodificato e disponibile su richiesta.

Per una risposta che non si conosce il contenuto completo del corpo ancora (non si può indovinare la Content-Length, codifica dal vivo), l'uso pezzo codifica:

HTTP/1.1 200 OK 
Content-Type: video/mp4 
Transfer-Encoding: chunked 
Trailer: Expires 

1E; 1st chunk 
...binary....data...chunk1..my 
24; 2nd chunk 
video..binary....data....chunk2..con 
22; 3rd chunk 
tent...binary....data....chunk3..a 
2A; 4th chunk 
nd...binary......data......chunk4...etc... 
0 
Expires: Wed, 21 Oct 2015 07:28:00 GMT 

Ogni pezzo è inviare quando è disponibile : quando pochi fotogrammi sono codificati o quando il buffer di uscita è pieno, 100kB vengono generati, ecc

22; 3rd chunk 
tent...binary....data....chunk3..a 

Dove 22 invia la lunghezza chunk byte hexa (0x22 = 34 byte), ; 3rd chunk è un chunk extra (opzionale) e tent...binary....data....chunk3..a è il contenuto del blocco.

Poi, quando la codifica è terminata e tutti i blocchi vengono inviati, estremità da:

0 
Expires: Wed, 21 Oct 2015 07:28:00 GMT 

Dove 0 significa che ci non più blocchi, seguita da zero o più rimorchio (campi di intestazione consentiti) definiti nell'intestazione (Trailer: Expires e Expires: Wed, 21 Oct 2015 07:28:00 GMT non sono tenuti) per fornire checksum o firme digitali, ecc

Qui è l'equivalente di risposta del server se il file è stato già generato (nessuna codifica live):

HTTP/1.1 200 OK 
Content-Type: video/mp4 
Content-Length: 142 
Expires: Wed, 21 Oct 2015 07:28:00 GMT 

...binary....data...chunk1..myvideo..binary....data....chunk2..content...binary....data....chunk3..and...binary......data......chunk4...etc... 

Per maggiori informazioni: Chunked transfer encoding — Wikipedia, Trailer - HTTP | MDN

+0

Non penso che il tag video supporti la codifica Chunked (almeno in Chrome). – themihai

+0

La codifica del trasporto Chunked è necessaria per il supporto HTTP/1.1. Inoltre ho provato, Chrome e Firefox riproducono bene i video usando la codifica di trasporto chunked. Safari (desktop e iOS) richiede il supporto [intervalli] (https://en.wikipedia.org/wiki/Byte_serving) per riprodurre video (non richiesto per l'audio). La codifica e gli intervalli di trasporto non sono incompatibili, ma gli intervalli di byte richiedono di conoscere in anticipo la dimensione del byte. Per questo, è possibile utilizzare i protocolli di live streaming (ma richiedono un trattamento speciale durante la codifica del video) come HLS o API come MSE. Vedere [hls.js] (https://github.com/video-dev/hls.js) – mems

+0

Chrome effettua una richiesta parziale indipendentemente dal fatto che il server la supporti o meno, quindi penso che la codifica di chunked da sola senza supporto parziale/intervalli sia non abbastanza per servire il video html5. Se il server non annuncia la lunghezza totale del contenuto (cioè fornisce un carattere jolly *), il lettore si ferma dopo aver completato la prima richiesta parziale, invece di richiedere la parte successiva, quindi suppongo che si tratti di un bug di Chrome. – themihai

Problemi correlati