La crescente diffusione delle connessioni a banda larga ha portato una grande diffusione dei contenuti video sul Web: con il progredire delle tecnologie di compressione, e delle velocità di connessione, i contenuti diventano sempre di maggior qualità e soprattutto si diffondono anche video di durata di diversi minuti.
Proprio con i video di durata di alcuni minuti si è posta la necessità di poter accedere a diverse parti del video in ogni momento, anziché dover attendere il caricamento dell'intero contenuto (download progressivo); la possibilità di muoversi in qualsiasi posizione del video, anche prima del suo completo download, è definita streaming, e utilizzando ActionScript e PHP è possibile ottenerla, senza l'impiego di prodotti come Flash Media Server che richiedono hosting particolari.
La principale limitazione del PHP Streaming, rispetto a soluzioni come Flash Media Server, è l'impossibilità di trasmettere video in diretta, inoltre non è possibile scalare la banda in base alla connessione dell'utente. Per maggiori informazioni sul PHP Streaming è possibile consultare il video dedicato a tale tecnologia su ICTv.
Come funziona
Il procedimento ha come fulcro PHP e si basa sul concetto che lo script server-side, ad ogni richiesta del Flash Player, di fatto legga il file FLV, ne selezioni solo la parte successiva al punto selezionato dall'utente, e restituisca un nuovo file al Flash Player. Più schematicamente:
- L'utente, tramite la barra di controllo del player Flash, sceglie un punto del filmato;
- Il file PHP apre il file FLV, legge i settori che iniziano dal punto passato dal player fino alla fine del video;
- Il file PHP restituisce al Flash Player un nuovo file, che inizia dal punto selezionato in precedenza;
- Il player si trova ad eseguire un nuovo file, pertanto non importa quanto del video precedente fosse stato scaricato.
Possiamo facilmente intuire come Flash abbia un compito di "invio e ricezione" di informazioni, più che di elaborazione vera e propria, anche se vengono sfruttate le potenzialità del Flash Player per la riproduzione e soprattutto il formato FLV, ottimo sia per l'alto rapporto tra qualità e peso del file, e soprattutto perché permette l'utilizzo di MetaData che sono, di fatto, quelli che consentono a PHP di tradurre con precisione il punto selezionato dall'utente nell'interfaccia con il corrispondente settore del file da leggere.
Per questo motivo, tra i requisiti per poter sfruttare il PHP Streaming è annoverato il software FLVMDI (FLV Meta Data Injector), un software gratuito che popola il file FLV con una ampia quantità di informazioni su durata e fotogrammi chiave. Tali Meta Data vengono poi usati dal player per la comunicazione con PHP.
Requisiti per il PHP Streaming
Per creare il semplice player, che vedremo in questo articolo, oltre al già citato FLVMDI, è necessario avere:
- uno spazio Web con supporto PHP;
- uno script PHP che si occupi di elaborare il file FLV (nell'esempio di questo è articolo è stato usato Xmoov-PHP di Eric Lorenzo Benjamin Jr).
Se abbiamo un nostro file, e non siamo sicuri che contenga tutti i MetaData necessari per lo streaming, possiamo "trattarlo" con FLVMDI.
Uso di FLVMDI
Il software è semplice da utilizzare; si compone di pochi file ed è possibile usarlo tramite linea di comando (usando il file flvmdi.exe) o tramite interfaccia grafica (usando flvmdigui.exe). La seconda opzione è più comoda: sarà sufficiente selezionare un file (o eventualmente un'intera directory), scegliere il nome del file di output e quindi premere il tasto Run FLVMDI posto in basso a destra nell'interfaccia.
Dopo pochi istanti avremo un file che visivamente sarà uguale al precedente, ma conterrà i MetaData che ci permetteranno di eseguire lo streaming via PHP.
Va sempre tenuto presente che i MetaData generati da FLVMDI sono comunque basati sui keyframe (fotogrammi chiave) del video FLV, ne consegue che file con pochi fotogrammi chiave potrebbero non offrire un seeking particolarmente preciso.
Creazione del player
Qualora utilizzassimo lo script xmoov-php sarà necessario impostare alcuni parametri nel file PHP, solo dopo potremo concentrarci sulla parte ActionScript.
Lo script che realizzeremo in questo articolo sarà in ActionScript 3, è comunque possibile ottenere un player con PHP Streaming anche in ActionScript 2 (è disponibile un ottimo esempio su FlashComGuru).
La struttura della nostra applicazione sarà composta in questo modo:
- it: cartella principale del package delle clasis
- html: sottodirectory del package delle classi
- MainApplication.as: classe principale
- StreamingPlayer.as: classe del player
- html: sottodirectory del package delle classi
- Player.fla: file principale
- xmoov.php: file per lo streaming
Per prima cosa creiamo un nuovo progetto ActionScript 3 e associamo come Document Class it.html.MainApplication; quest'ultima sarà la classe che utilizzeremo per disporre il player sullo stage. La classe a cui lavoreremo nel proseguio dell'articolo è invece StreamingPlayer.as, che conterrà i comandi per il player vero e proprio.
La classe StreamingPlayer
Per prima cosa dichiariamo il package e la classe con il relativo costruttore:
package it.html{
import flash.display.MovieClip;
public class StreamingPlayer extends MovieClip{
public function StreamingPlayer():void{
}
}
}
Poi andiamo a dichiarare alcune variabili: i commenti nel codice dovrebbero essere sufficienti a capire il loro scopo; alcune variabili sono principalmente di configurazione (ad esempio il path del file PHP) e vengono già impostate, altre invece vengono solo inizializzate e verranno poi sfruttate durante l'esecuzione del filmato. Importiamo anche le prime classi di cui avremo bisogno per il nostro player.
package it.html{
import flash.display.MovieClip;
// importazione delle classi per la riproduzione di video
import flash.media.Video
import flash.net.NetConnection
import flash.net.NetStream
// importazione della classe per la gestione degli eventi del video
import flash.events.NetStatusEvent
// importazione del setInterval
import flash.utils.setInterval
public class StreamingPlayer extends MovieClip{
// array relativi a tempo e keyframe, conterranno i MetaData del file
private var tempi:Array
private var posizioni:Array
// oggetto che verrà utilizzato come listener dal NetStream
private var customClient:Object = new Object();
// oggetti Video, NetConnection e Netstream che verranno sfruttati per eseguire il filmato
private var oggVideo:Video = new Video()
private var nc:NetConnection
private var ns:NetStream
// ampiezza di banda : low, mid or high
private var bandwidth = "high";
// path del video da caricare
private var _vidName:String = "Video.flv";
// url del file PHP;
private var _phpURL:String = "http://localhost/Html/xmoov.php"
// bytes Caricati
private var amountLoaded:Number;
// durata del video
private var durata:Number
// larghezza del controller
private var loaderwidth:Number ;
// variabili usate per convertire la posizione della barra in tempo del video (e viceversa)
private var timeOffset:Number = 0;
private var pixelOffset:Number = 0;
// variabile per impostare il tempo di buffer per il filmato
private var bt:Number
Dopo queste operazioni abbiamo a disposizione gli elementi principali per la riproduzione del video, in particolare i percorsi dello script PHP e del video, oltre agli array che gestiranno i MetaData e alcune variabili che utilizzeremo per calcolare il rapporto tra la posizione selezionata nel controller e la posizione in secondi del video.
Possiamo quindi impostare il nostro costruttore, che principalmente si occuperà di creare gli oggetti impostati in precedenza e associarli tra loro, come nel caso dell'oggetto customClient
che verrà associato all'oggetto ns(NetStream)
. All'interno del costruttore ci occuperemo anche di aggiungere allo stage l'oggetto Video.
public function StreamingPlayer(){
// associamo la funzione per il controllo dei MetaData
customClient.onMetaData = gestioneMetaData;
// diamo un nome istanza all'oggetto video
oggVideo.name = "video"
// aggiungiamo l'oggetto allo stage
addChild(oggVideo);
// creiamo l'oggetto netConnection
nc = new NetConnection()
nc.connect (null);
// creiamo l'oggetto netStream e assegniamo ad esso il client coi relativi listener
ns = new NetStream (nc);
ns.client = customClient
// agganciamo il netStream all'oggetto Video
oggVideo.attachNetStream(ns);
// richiamiamo la funzione di avvio del video
playVideo()
}
La funzione playVideo è piuttosto semplice:
private function playVideo():void{
// in base alla banda impostata, stabiliamo il tempo di buffering
switch (bandwidth){
case "low" :
bt = 30;
break;
case "mid" :
bt = 5;
break;
case "high" :
bt = 2;
break;
}
// associamo il tempo di buffering all'oggetto NetStream
ns.bufferTime = bt;
// controlliamo lo stato del video
ns.addEventListener(NetStatusEvent.NET_STATUS, stato);
// avviamo il video, passando dallo script PHP
ns.play (_phpURL + "?file=" + _vidName + "&position=0" + "&bw=" + bandwidth);
}
A questo punto il filmato viene già eseguito, ma non abbiamo a disposizione nessun controllo che ci permette di apprezzare l'utilizzo del PHP Streaming, andremo quindi a creare una barra che potremo usare per spostarci all'interno del filmato, il classico controllo presente sulla maggior parte dei player video.
Apriamo il nostro file Player.fla, creiamo un nuovo movieclip e impostiamone l'esportazione per ActionScript.
In questo modo abbiamo creato un MovieClip che conterrà il nostro video e al cui interno possiamo inserire anche altri elementi: nel nostro caso creiamo al suo interno un movieclip con nome istanza controller che conterrà al suo interno un ulteriore clip di nome cursore e uno di nome barra. Il cursore sarà quello che l'utente userà per muoversi all'interno del filmato, mentre barra verrà usato per indicare lo stato di caricamento del filmato.
Con questa aggiunta possiamo inserire due nuove righe al costruttore StreamingVideo:
// posizioniamo i controlli sotto al video
controller.y = oggVideo.y + oggVideo.height
// ricaviamo la larghezza della barra
loaderwidth = controller.barra.width
Gestione dello streaming e dei progressi del filmato
Siamo giunti alla parte un po' più complicata: innanzitutto controllando lo stato del filmato (grazie alla funzione stato associata all'evento NET_STATUS_EVENT); una volta che il buffer sarà completo (e quindi il video partirà) avvieremo un setInterval
che chiamerà la funzione progressiVideo ogni 200 millisecondi: tale funzione si occuperà di aggiornare la larghezza della barra di caricamento e la posizione del cursore in base alla posizione attuale del video. Da notare che la durata completa del video viene ricavata dalla funzione gestioneMetaData che sarà associata all'oggetto NetStream.
Ecco le funzioni:
private function stato(info:NetStatusEvent):void{
if(info.info.code == "NetStream.Buffer.Full"){
intervallo = setInterval (progressiVideo, 200);
}
}
private function progressiVideo ():void{
amountLoaded = ns.bytesLoaded / ns.bytesTotal;
controller.barra.width = amountLoaded * loaderwidth;
controller.cursore.x = ns.time / durata * loaderwidth;
}
private function gestioneMetaData(infoObject:Object):void{
// ricavo la durata del video
durata = infoObject.duration;
// memorizzo le informazioni su tempo e posizioni dei keyframe
tempi = infoObject.keyframes.times;
posizioni = infoObject.keyframes.filepositions;
}
Vediamo allora come collegare la barra che abbiamo creato con il seeking del file (che verrà svolto di fatto dal file PHP). Ciò di cui abbiamo bisogno è una funzione che sia in grado di stabilire la corrispondenza tra il punto in cui è il cursore (quindi un valore in pixel) e la corrispondente posizione nel video (quindi un valore in secondi); tale funzione è semplice da realizzare, in quanto abbiamo bisogno di conoscere la durata del video e la larghezza della barra entro cui può essere mosso il cursore, valori che già abbiamo e che usiamo nella funzione progressiVideo.
Per la durata del video abbiamo la variabile durata, mentre la larghezza della barra è memorizzata nella variabile loaderwidth.
Abbiamo poi bisogno anche della funzione inversa, dal tempo in secondi ci resituisca il pixel: poiché il punto trovato nel file FLV potrebbe non essere precisamente quello cercato col cursore; il video può essere spostato solo su un fotogramma chiave (ricordiamo gli array tempi e posizioni), e un fotogramma chiave del video potrebbe essere leggermente più avanti o più indietro del punto verso cui abbiamo eseguito il seeking, di conseguenza potremmo dover spostare il cursore una volta che il video ripartirà dopo il seeking.
Ecco allora le funzioni pixelToTime e timeToPixel:
private function pixelToTime(px:Number):Number{
return Math.floor (px / (loaderwidth -controller.cursore.width) * durata);
}
private function timeToPixel(tmm:Number):Number{
return Math.floor (tmm / durata * (loaderwidth - controller.cursore.width));
}
La prima funzione richiede un parametro numerico (la posizione in pixel dove è stato spostato il cursore) e restituisce il risultato dell'operazione valore in pixel / (larghezza barra - larghezza cursore) * durata filmato.
Anche la funzione timeToPixel riceve un valore numerico (il numero di secondi di cui cercare il pixel corrispondente) e restituisce il valore dell'operazione tempo in secondi / durata del filmato * (larghezza barra - larghezza cursore).
Associare gli eventi al cursore di scorrimento del filmato
Abbiamo impostato le funzioni per la conversione secondi/pixel, ora dobbiamo rendere trascinabile il cursore di scorrimento del filmato, inoltre quando il cursore verrà rilasciato dovremmo eseguire il seeking convertendo la posizione da pixel a secondi, richiamare quindi lo script PHP adibito allo streaming, ricevere il nuovo file dallo script e avviarne la riproduzione spostando eventualmente il cursore per avere la perfetta corrispondenza con il keyframe trovato nel video.
Associamo innanzitutto l'evento MOUSE_DOWN
al cursore; sarà necessario aggiungere alla serie di import ad inizio classe anche il package degli eventi del mouse (flash.events.MouseEvent); l'evento MOUSE_DOWN
possiamo invece inserirlo al termine della funzione playVideo
:
import flash.events.MouseEvent
//..
public function playVideo():void{
//..
// impostiamo il trascinamento del cursore
controller.cursore.addEventListener(MouseEvent.MOUSE_DOWN, trascina_cursore)
}
La funzione trascina_cursore si occuperà di avviare il trascinamento (limitandolo all'area inclusa nella barra di scorrimento), mettere in pausa il video, fermare l'intervallo che aggiorna lo stato del video (dato che stiamo per spostarci in un altro punto non ha senso eseguire la funzione progressiVideo finché il nuovo video non sarà in esecuzione) e infine associa al rilascio del cursore l'avvio del seeking del video.
Per impostare i limiti del trascinamento dovremo usare l'oggetto Rectangle del package flash.geom, mentre per fermare l'intervallo di nome intervallo sfrutteremo il comando clearInterval del package flash.utils, di conseguenza andiamo ad importare anche questi due package nella nostra classe:
import flash.geom.Rectangle
import flash.utils.clearInterval
Ecco infine il codice della funzione trascina_cursore.
private function trascina_cursore(evt:MouseEvent):void{
// mettiamo in pausa il video
ns.pause ();
// eliminamo l'intervallo che richiama la funzione progressiVideo
clearInterval (intervallo);
// avviamo il trascinamento del cursore limitando alla barra
evt.target.startDrag (false, new Rectangle(0, 0, loaderwidth, 0));
// associamo al rilascio del cursore la funzione per il rilascio del cursore
stage.addEventListener(MouseEvent.MOUSE_UP,rilascia_cursore)
}
La funzione rilascia_cursore ferma il trascinamento, rimuove il listener dell'evento MOUSE_UP
e soprattutto richiama la funzione esegui_seeking, che si occupa di richiamare il file PHP e gestirne la risposta.
function rilascia_cursore(evt:MouseEvent):void{
esegui_seeking();
controller.cursore.stopDrag ();
stage.removeEventListener(MouseEvent.MOUSE_UP, rilascia_cursore)
}
Funzione esegui_seeking
Eccoci finalmente giunti a quello che è di fatto il "cuore" del nostro script:
private function esegui_seeking (){
// convertiamo la posizione del cursore in secondi
var cerca:Number = pixelToTime (controller.cursore.x);
// se i secondi sono minori o uguali a zero, riavviamo il video
if (cerca <= 0){
riavvia ();
return;
}
// cerchiamo all'interno dell'array tempi il valore più vicino a quello di "cerca"
for (var i:Number = 0; i < tempi.length; i++){
var j = i + 1;
// se la posizione trovata è la più vicina al valore
if ((tempi[i] <= cerca) && (tempi[j] >= cerca)){
// associamo le differenze di tempo e pixel
timeOffset = tempi[i];
pixelOffset = timeToPixel (timeOffset);
// spostiamo il cursore e la barra al valore corrispondente
controller.cursore.x = controller.barra.x = pixelOffset;
// diamo alla barra larghezza pari a quella del cursore
controller.barra.width = controller.cursore.width;
// riportiamo a zero il valore di amountLoaded
amountLoaded = 0;
// ricarichiamo il video tramite il file PHP, passandogli la nuova posizione di partenza
ns.play (_phpURL + "?file=" + _vidName + "&position=" + posizioni[i] + "&bw=" + bandwidth);
break;
}
}
}
Notiamo che timeOffset e pixelOffset sono praticamente i valori che utilizziamo come nuovo "punto zero" per il nostro video, in pratica ci permettono di spostare la barra e il cursore nella nuova posizione e non allo zero (ricordiamo che con ns.play è come se caricassimo un nuovo video che inizia nel punto specificato tramite il cursore, infatti se provassimo a tracciare il tempo del video caricato otterremmo come risultato 0).
Proprio per evitare che, dopo lo streaming, la barra si allarghi oltre lo spazio disponibile, dobbiamo modificare leggermente la funzione progressiVideo:
private function progressiVideo ():void{
amountLoaded = ns.bytesLoaded / ns.bytesTotal;
controller.barra.width = amountLoaded * (loaderwidth - pixelOffset);
controller.cursore.x = ns.time / durata * loaderwidth;
}
Prima dell'uso dello streaming non era necessario sottrarre il valore di pixelOffset dato che la barra partiva dal punto zero e non veniva più spostata, cosa che invece ora avviene al rilascio del cursore.
All'interno della funzione esegui_seeking abbiamo inserito un richiamo a riavvia, da utilizzare solo nel caso in cui il cursore fosse stato riportato a zero: il codice è semplicissimo, si limita a riportare timeOffset
e pixelOffset
a zero e ricaricare il video dalla posizione iniziale:
private function riavvia ():void{
pixelOffset = timeOffset = 0;
ns.play (_phpURL + "?file=" + _vidName + "&position=0" + "&bw=" + bandwidth);
}