cze 03 2008

Video streaming w PHP

Kategoria: PHPPiotr Surma @ 20:30

Kiedyś zastanawiałem się na jakiej zasadzie działają serwisy a’la YouTube. Wrzucam film, a on po chwili wyświetlany jest we Flashowym odtwarzaczu. W tym artykule postaram się wytłumaczyć na czym to polega i przedstawić praktyczne tworzenie takiego serwisu. Teoretycznie wygląda to tak, że wrzucam plik przez formularz na stronie, skrypt na serwerze go odbiera i zapisuje. Następnie sprawdzamy rozszerzenie i typ MIME pliku, czy rzeczywiście jest to materiał wideo. Na koniec musimy przekonwertować na serwerze owy film do formatu FLV (Flash Video). Tylko taki format obsługuje Flash. Do tego będzie potrzebny specjalny enkoder – ffmpeg. Wywołujemy go z poziomu PHP jako zewnętrzny program, ale o tym za chwilę. Ffmpeg musi być preinstalowany na serwerze, a z tego co się orientuję, żadna polska firma hostingowa nie udostępnia takiej funkcjonalności. Zdarzyło mi się sprawdzić w praktyce dwie zagraniczne firmy:

  • www.cirtexhosting.com – serwery w USA, dość wolny transfer, wieczne problemy z CRONem (tydzień działa, tydzień nie działa), nie ma możliwości wprowadzenia własnych ustawień dla PHP5 (zarówno w .htaccess jak i przez własny php.ini) w przeciwieństwie do PHP4;
  • www.apthost.com – serwery w Kanadzie, tu również dwie wersje PHP, ale problemów z własnymi ustawieniami nie miałem. Natomiast największą wadą jaką zauważyłem jest brak niektórych kodeków, np. do obsługi (konwersji) plików 3GP;

Niemniej jednak od czasu moich “testów” mogło coś się zmienić.

Jak zdobyć ffmpeg?

Oczywiście aby pisać tego typu serwis, trzeba go testować na bieżąco i sprawdzać czy wszystko działa tak jak należy. Nie może się to obyć bez ffmpeg-a na naszym lokalnym komputerze. Ze strony projektu można pobrać źródła, natomiast skompilowane binarki dla Windows można ściągnąć chociażby z tej strony. Pobrane archiwum rozpakowujemy do C:\Windows\ (lub do innego folderu ze zmiennej środowiskowej PATH). Dzięki temu zabiegowi będzie można wywoływać program z samej nazwy, bez podawania ścieżki.

Na początek formularz

Zbudujmy prosty formularz, za pomocą którego możliwe będzie wgranie pliku na serwer. Należy pamiętać o kilku obowiązkowych rzeczach:

  • znacznik FORM musi mieć atrybut enctype=”miltipart/form-data” mówiący o tym, że będziemy przesyłać pliki;
  • formularz musimy wysłać metodą POST;
  1. <form action="plik.php" enctype="multipart/form-data" method="post">
  2.  <input name="MAX_FILE_SIZE" type="hidden" value="1048576" />
  3.  <input name="plik" type="file" />
  4.  <input type="submit" value="Prześlij plik" />
  5. </form>

Ukryte pole MAX_FILE_SIZE mówi o maksymalnym rozmiarze przesyłanego pliku. Nie jest to pole obowiązkowe, ale bardzo przydatne. Jako wartość przyjmuje rozmiar wyrażony w bajtach. Pamiętaj o ustawieniach dotyczących rozmiaru przesyłanego pliku w php.ini (post_max_size i upload_max_filesize). Jeśli wprowadzisz dużą wartość w pole MAX_FILE_SIZE, a ustawienia PHP będą bardziej ograniczały plik, zostanie on odrzucony po stronie serwera.

Odbiór pliku

Przesłany plik zapisany jest w katalogu tymczasowym. Aby go zachować musimy go przenieść do dowolnego wybranego katalogu na serwerze, który musi mieć prawa do zapisu.

  1. if(isset($_FILES['plik'])) {
  2.    if(is_uploaded_file($_FILES['plik']['tmp_name'])) { //czy plik rzeczywiście uploadowany
  3.       move_uploaded_file($_FILES['plik']['tmp_name'], 'pliki/'.$_FILES['plik']['name'])); //zapisz plik
  4.    }
  5. }

W międzyczasie powinniśmy sprawdzić rozszerzenie i typ MIME pliku oraz dla pewności rozmiar.

  1. if(isset($_FILES['plik'])) {
  2.    if(is_uploaded_file($_FILES['plik']['tmp_name'])) {
  3.       if($_FILES['plik']['size']<=1048576) { //sprawdzenie rozmiaru
  4.          $rozszerzenia=array('mpg','mpeg','avi','rmvb');
  5.          $mime=array('video/avi','video/mpeg','video/x-ms-wmv','');
  6.          $roz=strtolower(substr(strrchr($_FILES['plik']['name'],'.'),1)); //rozszerzenie wgranego pliku
  7.          if(in_array($roz, $rozszerzenia)&&in_array($_FILES['plik']['type'], $mime))
  8.             move_uploaded_file($_FILES['plik']['tmp_name'], 'pliki/'.$_FILES['plik']['name']));
  9.       }
  10.    }
  11. }

Dla zachowania czytelności przedstawiłem w przykładzie tylko kilka rozszerzeń i typów MIME. W ostatecznym kodzie nie powinno zabraknąć następujących rozszerzeń: avi, mpg, mpeg, mpe, asf, asx, movie, wmv, 3gp, mp4, mov i rmvb oraz następujących typów MIME:

  • video/avi
  • video/mpeg
  • video/mpeg4
  • video/quicktime
  • video/mp4
  • video/x-msvideo
  • video/x-ms-asf
  • video/x-sgi-movie
  • video/x-ms-wmv
  • video/3gpp
  • video/3gp
  • application/force-download
  • application/octet-stream
  • application/mpeg4-iod
  • application/mpeg4-muxcodetable
  • video/vnd.rn-realvideo

Zauważ, że w przykładzie w tablicy $mime znalazła się jedna pusta wartość. Jest ona tam celowo. Zdarza się niekiedy, że tablica superglobalna $_FILES['video']['type'] przechowuje pustą wartość, mimo poprawności pliku.

Konwersja

Przyszedł czas na konwersję pliku. Aby tego dokonać, musimy wywołać program ffmpeg z poziomu PHP. Samo wywołanie ffmpeg z wiersza poleceń może wyglądać tak:

  1. ffmpeg -i plik_wejsciowy -r 25 -ar 22050 -ab 24k -f flv plik_wyjsciowy

Natomiast aby wywołać program z poziomu PHP możemy posłużyć się funkcją exec(), system() lub shell_exec(). My skorzystamy z exec():

  1. exec('ffmpeg -i pliki/'.$_FILES['plik']['name'].'-r 25 -ar 22050 -ab 24k -f flv pliki/'.$_FILES['plik']['name'].'.flv');

Poszczególne przełączniki w wywołaniu oznaczają:

  • -i lokalizacja pliku wejściowego;
  • -r framerate pliku wyjściowego;
  • -ar częstotliwość próbkowania dźwięku;
  • -ab bitrate dźwięku;
  • -f informacja o formacie pliku wyjściowego (flv) i lokalizacji tego pliku;

Oczywiście ffmpeg ma wiele więcej innych przełączników. Pełną ich listę znajdziemy na stronie dokumentacji.

Należy pamiętać, że katalog pliku wyjściowego musi mieć prawa zapisu. W powyższym przykładzie zostało to pominięte, ale trzeba także sprawdzić nazwę pliku pod kątem niebezpiecznych znaków, które mogą stanowić komendę służącą do wykonania innego polecenia powłoki.

Dodatkowo ffmpeg umożliwia wyciągnięcie jednego kadru z filmu i zapisanie go jako obrazu. W ten sposób uzyskamy miniaturkę (podgląd) filmu:

  1. exec('ffmpeg -i pliki/'.$_FILES['plik']['name'].'-s 160×120 -vframes 1 -f mjpeg pliki/'.$_FILES['plik']['name'].'.jpg');

Poszczególne przełączniki w wywołaniu oznaczają:

  • -i lokalizacja pliku wejściowegoz filmem;
  • -s rozdzielczość miniaturki;
  • -vframes numer klatki do wyciągnięcia;
  • -f informacja o formacie pliku wyjściowego (mjpeg) i lokalizacji tego pliku;

Oczywiście po konwersji możemy usunąć stary plik wideo, by nie zajmował cennego miejsca na serwerze:

  1. unlink('pliki/'.$_FILES['plik']['name']);

Należy pamiętać, że PHP czeka na zakończenie działania wywołanego programu. W przypadku dużych plików konwersja może potrwać dość długo i potrzebować sporo pamięci. Niskie wartości max_execution_time i memory_limit w php.ini mogą zabić skrypt przed zakończeniem jego działania. Dlatego jeśli na naszym serwerze wartości te są niskie oraz nasza firma hostingowa nie pozwala ich zmienić, trzeba wprowadzić proporcjonalny ogranicznik rozmiaru pliku.

Odtwarzanie

Przyszedł czas na ostatnią operację, czyli wyświetlenie filmu na stronie www. Flashowy odtwarzacz możemy oczywiście zrobić samemu za pomocą programu Adobe Flash (lub zamienników). Możemy też skorzystać z gotowca. Udało mi się przetestować dwa tego typu odtwarzacze (darmowe do zastosowań niekomercyjnych): flowplayer i JW FLV Media Player. Na ich oficjalnych stronach możemy pobrać zarówno źródła, jak również skompilowane pliki SWF.

Na przykładzie flowplayer’a przedstawię jak umieścić film na stronie. W pobranej paczce znajduje się odtwarzacz w kilku skórkach, wybieramy jedną, na przykład FlowPlayerClassic.swf. Przykładowy kod wyświetlający film może wyglądać tak:

  1. <object type="application/x-shockwave-flash" width="352" height="288" data="FlowPlayerClassic.swf" id="FlowPlayer">
  2.  <param name="allowScriptAccess" value="sameDomain" />
  3.  <param name="movie" value="FlowPlayerClassic.swf" />
  4.  <param name="quality" value="high" />
  5.  <param name="scale" value="noScale" />
  6.  <param name="wmode" value="transparent" />
  7.  <param name="flashvars" value="config={videoFile:\'pliki/'.$_FILES['plik']['name'].’.flv\',loop:false,showFullScreenButton:false}" />
  8. </object>

Uwaga! Kod ten obowiązuje do wersji odtwarzacza z czasów pisania tej notki. Informacje o implementacji najnowszej wersji znajdują się na stronie autora FlowPlayera.

Obydwa odtwarzacze mają wiele przełączników pozwalających dostosować odtwarzacz do naszych upodobań:

  1. <param name="flashvars" value="config={videoFile:\'pliki/'.$_FILES['plik']['name'].’.flv\',loop:false,showFullScreenButton:false}" />

Pierwszy z nich, videoFile, (tu: flowplayer) jest obowiązkowy i informuje jaki plik ma zostać odtworzony. Kolejne są opcjonalne, w powyższym skrypcie wyłączyłem powtarzanie odtwarzania filmu i dostępność przycisku umożliwiającego użycie “pełnego ekranu”.

Pełna lista przełączników dla flowplayer’a dostępna jest tutaj, a dla JW FLV Media Player’a tutaj.

20 Odpowiedzi do “Video streaming w PHP”

  1. Jacek says:

    Mam pytanie jak wyciągnąc plik video ze strony na dysk jeżeli jest odtwarzany przez JW Player ?

  2. Piotr Surma says:

    W przypadku JW FLV Player’a zainteresuje nas linia:

    <param name=”movie” value=”flvplayer.swf?file=pliki/mojpliczek.flv” />

    Jak widać zmienna “file” zawiera lokalizację pliku z filmem.

  3. krzych says:

    A gdzie w tym kodzie wstawić adres pliku z filmem?
    Chodzi o flowplayer. Próbuję od 2 dni i mi nie wychodzi.

  4. krzych says:

    Wszystko działa, tylko film się nie wyświetla. W którym miejscu kodu należy wpisać adres do pliku z filmem we flowplayerze?
    Z góry dziękuję za pomoc.

  5. Piotr Surma says:

    Informację o filmie należy przesłać w formie zmiennej Flasha:
    <param name=”flashvars” value=”config={videoFile:\’pliki/’.$_FILES['plik']['name'].’.flv\’” />

    gdzie:
    pliki/’.$_FILES['plik']['name'].’.flv
    to nasz film.

  6. krzych says:

    Czy mozna gotowy plik *.flv wysłac na serwer i odtwarzać go za pomocą tego kodu? Jak wpisać w nim taki plik:
    http://www.sp1.raciborz.com.pl/v/11listopada.flv
    Dzięki za odpowiedź.

  7. krzych says:

    Czy mógłbyś wpisać tą linię jeszcze raz z dowolną lokalizacją jakiegoś pliku, np.: “domek.flv”?
    Dzięki za odpowiedź

  8. Piotr Surma says:

    Jeśli plik z filmem jest umieszczony w tym samym katalogu co strona, która będzie go wyświetlać, wystarczy taka składnia:
    <param name=”flashvars” value=”config={videoFile:’11listopada.flv’” />

    “Czy mozna gotowy plik *.flv wysłac na serwer i odtwarzać go za pomocą tego kodu?”

    Tak, można :)

  9. krzych says:

    Dziękuję za odpowiedź, jednak jeszcze coś robię źle. Umieściłem na serwerze plik “FlowPlayerClassic.swf” i w powyższym kodzie 2 razy wpisałem do niego adres, tj data=… i value=… Czy tak ma być, bo film nadal mi się nie wyświetla?

  10. Piotr Surma says:

    Wróżką nie jestem. Bez kodu nie powiem dlaczego nie działa.

  11. krzych says:

    Nie moge wstawić tego kodu (nie wyświetla się) jak to zrobić?

  12. Piotr Surma says:

    Okazuje się, że autor FlowPlayera wydał nową wersję swojego odtwarzacza, która nie jest kompatybilna z wcześniejszymi ustawieniami. Informacje o implementacji najnowszej wersji playera znajdują się tutaj: http://flowplayer.org/documentation/installation.html

  13. krzych says:

    Niestety dla mnie to czarna magia i to jeszcze po angielsku. Czy mógłbyś mi podać kod, do tej nowej wersji. Jeżeli nie to i tak dziękuję za chęć pomocy i nie chcę już zawracać głowy, zawsze mogę pozostać przy łatwiejszych plikach *.wmv.
    Jeszcze raz dziękuję i pozdrawiam. krzych.

  14. krzych says:

    Dzięki Kutar za pomoc. Już sobie poradziłem z JWPlayer’em.
    Po 3 dniach analizy różnych kodów dałem taki:http://www.unit1.pl/pb-932, i działa.
    Dzięki i pozdrawiam

  15. Piotr Surma says:

    Co do FlowPlayera kod będzie taki:

    <script src=”flowplayer-3.0.0.min.js”></script>
    <a href=”tutaj_plik_flv” style=”display:block;width:400px;height:300px” id=”player”>
    </a>
    <script language=”JavaScript”>
    flowplayer(“player”, “flowplayer-3.0.0.swf”);
    </script>

    Nie należy zapomnieć o wgraniu pliku flowplayer-3.0.0.min.js z pobranego archiwum (poza samym plikiem odtwarzacza).

  16. krzych says:

    Czy tam gdzie jest nazwa pliku “flowplayer-3.0.0.min.js” i “flowplayer-3.0.0.swf” należy podać cały adres do tych plików? Bo tak zrobiłem i film nie wyświetla się.

  17. Piotr Surma says:

    Musi to być pełna, właściwa ścieżka.

  18. krzych says:

    Wielkie dzięki za pomoc.Już mi działa mediaplayer. Teraz eksperymentuję również z flowplayerem. Na razie wszystko działa oprócz wyświetlania filmu ale myślę, że też do dego dojdę. Pozdrawiam.

Pozostaw Odpowiedź