SyntaxHighlighter

jueves, 23 de febrero de 2012

Canal de TV con Adobe Flash Media Server

Hace poco empecé a hacer mis primeros pinitos con Server-Side ActionScript con Adobe Flash Media Server. Uno de los proyectos que teníamos pendientes era el de crear un "canal de TV" que estuviera retrasmitiendo programas previamete grabados.


Dado que ya estábamos usando Adobe FMS para hacer retransmisiones en directo y dar soporte a streaming de vídeos bajo demanda (VOD) de esos mismos vídeos el paso natural es disponer de un servicio que emita alguno de estos vídeos de manera programada.

FMS dispone de varias aplicaciones "preconfiguradas" que cubren los dos servicios anteriores (live y vod), pero la verdadera potencia de este software reside en la posiblidad de desarrollar nuevos servicios usando Server-Side ActonScript. La forma de trabajar en este entorno es totalmente nueva ya que toma mucha de las características de la programación Flash para usarlas "al otro lado del espejo": gestión de conexiones de clientes, gestión de flujos de datos, etc.También conserva, como es normal, muchas de sus incomodidades.

Un recurso que me ha ayudado bastante a comprender como funciona Flash Media Server y dar los primeros pasos en el desarrollo es la magnífica página llena de tutoriales en vídeo http://fmsguru.com/.

Entorno de trabajo
Como siempre, lo primero es ponerse cómodos. El código Server-Side ActionScript lo vamos a "picar" con un editor ligero. Buscando un poco he encontrado SEPY, aunque podría ser cualquier otro, este tiene resaltado de sintaxis ActionScript.

Para ejecutar nuestro código server-side necesitamos Flash Media Server. Hay una versión de desarrollo, que viene limitada en número de conexiones simultáneas a pero que tiene toda la funcionalidad necesaria para programar aplicaciones Flash Media Development Server. Existen versiones para Windows y Linux :D . La instalación es sencilla, la saltaremos. Lo único que conviene configurar es el modo debug, para poder acceder a algunos datos de la aplicación en la consola de administración.



Sobre la consola de administración de Flash Media Server haremos todo el debug de nuestra aplicación.
Y para el desarrollo del cliente necesitamos Adobe Flash o Flash Builder (Flex), según nuestras preferencias. Para este post elegiré Flash.


Planteamiento de la aplicación
Como siempre, antes de empezar a escribir código y a pelearnos con los distintos elementos deberíamos tener claro qué queremos hacer:

Por un lado debemos planificar a qué se compromete la parte servidor:
  • Leer un fichero XML con la programación que se emitirá. En este fichero deberá haber datos del evento en sí (nombre, descripción, etc.) así como de la planificación de emisión (fecha y hora) y referencias al archivo que contiene el vídeo.
  • Decidir cual es el próximo evento a emitir.
  • Comenzar a emitir el próximo evento a la hora adecuada.
  • Actualizar la información que se comparte con el cliente.
Y por otro lado el cliente:
  • Conectar con el servidor.
  • Leer la información de estado de la emisión así como los datos de los eventos.
  • Adaptar la interfaz al estado del servidor: Mostrar la emisión en caso de que esté activa o dar información de la próxima emisión si no lo está.

Script del Servidor

/*
* Descripción del Shared Object:
* so.reproduciendose
*  * "false" si no se está reproduciendo nada.
*  * matriz con la información del evento resproduciéndose en otro caso: so.data.reproduciendose["fichero"] para acceder a los datos
* 
* so.proximo
*  * matriz con la información del evento resproduciéndose en otro caso: so.data.proximo["fichero"] para acceder a los datos
* 
* so.arrayEventos
*  * Matriz con toda la información del XML
*   so.data.arrayEventos[0]["fichero"] = 2011012600
*   so.data.arrayEventos[0]["fecha"] = 18:00
*   so.data.arrayEventos[0]["descripcion"] = Descripcion del programa
*   ...
*/

myStream = Stream.get("Stream_VOD");
var arrayEventos = new Array();
var so = SharedObject.get("so", false);
so.setProperty("reproduciendose",false);

myStream.onStatus = function (info) {
 if (info.code == "NetStream.Play.Stop"){
  so.setProperty("reproduciendose", false);
 }
 
}

application.allowDebug = true;
application.onAppStart = function() {
 trace ("Leyendo el fichero XML...");

 arrayEventos = leeXML("programa_v2.xml");  
 espera = setInterval(Realizador, 2000);
}

Realizador = function () {
 trace ("Ejecutando el Realizador...")
 var proximoEvento = proximoPrograma(arrayEventos);
 
 so.setProperty("proximo", arrayEventos[proximoEvento.idPrograma]);
 so.setProperty("arrayEventos", arrayEventos);
 clearInterval(espera);
 espera = setInterval(Reproduce, proximoEvento.espera, arrayEventos[proximoEvento.idPrograma]);
}

Reproduce = function (programa) { 
 so.setProperty("reproduciendose", programa);
 trace ("Reproduciendo el video: " +programa["fichero"]);
 myStream.setBufferTime(1);
 myStream.play(programa["fichero"]);
 
 Realizador(); 
}


/**
* Devuelve un objeto a con dos atributos: 
*  idPrograma: Indice de la matriz ArrayEventos en el que se encuentra el elemento con la información del próximo programa
*   espera: Tiempo en ms hasta el comienzo el programa con índice "programa".
*/

function proximoPrograma(arrayEventos){
}

/*
* Estructura del XML es:
*
*
* 2011012600
* 18:00
* Descripcion del programa
* http://cevug.ugr.es
* Nombre del Evento
* No
*
*
* 
* Matriz Generada es:
* 
* mat[0]["fichero"] = 2011012600
* mat[0]["fecha"] = 18:00
* mat[0]["descripcion"] = Descripcion del programa
* ...
* 
*/

function leeXML(fichero)
{
}
 
La estructura del programa tiene tres bloques: la Inicialización, donde se lee el XML; el Realizador, que decide cual es el próximo programa a emitir y la Reproducción que se encarga de "pulsar play" y de llamar de nuevo al realizador para que programe el próximo programa.

Como se puede ver en el código la secuencialidad del programa hay que imponerla a base de retardos ya que, por ejemplo, Cuando se lee el fichero XML, la ejecución del script no "se para", por lo que si llamamos al Realizador inmediatamente es muy probable que el XML no se haya terminado de cargar y, por lo tanto, obtengamos datos erróneos.

También tenemos varios objetos compartidos con el servidor. Para poder rastrear el contenido de estos objetos compartidos debemos activar el "modo debug", lo hago en la línea 30. Estos objetos se usan para que clientes y servidor se comuniquen. En nuestro caso, el objeto compartido so.reproduciendose determinará el modo en el que debe estar el cliente: mostrando el vídeo o mostrando información sobre próximas emisiones.

Script del Cliente
Para empezar con Flash hacemos un diseño preliminar de todos los elementos del reproductor: colocamos el objeto de vídeo, cajas de texto para los mensajes, fondos, etc.


Hay que dar nombre a todos los objetos para después poder referirnos a ellos desde el código AS:
/**********************************************************/
/*             Inicialización de la Interfaz              */
/**********************************************************/
TVoff();

/**********************************************************/
/*                        Conexión                        */
/**********************************************************/
var nc:NetConnection = new NetConnection();
var ns:NetStream;

nc.onStatus = function (info) {
 if (info.code.indexOf("Success") != -1) {
  goGetTheStream();
 }
}

ns.onStatus = function (info) {
 trace ("Status del stream: "+ info.code);
}

function goGetTheStream() {
 ns = new NetStream(nc);
 ns.setBufferTime(3);
 vid.attachVideo(ns);
 ns.play("Stream_VOD");
}

ns.onStatus = function (info) {
 trace(info.code);
}
nc.connect("rtmp://fms_server/application/Stream");
/**********************************************************/
/*                      Shared Object                     */
/**********************************************************/
/*
* Descripción del Shared Object:
* so.reproduciendose
*  * "false" si no se está reproduciendo nada.
*  * matriz con la información del evento resproduciéndose en otro caso: so.data.reproduciendose["fichero"] para acceder a los datos
* 
* so.proximo
*  * matriz con la información del evento resproduciéndose en otro caso: so.data.proximo["fichero"] para acceder a los datos
* 
* so.arrayEventos
*  * Matriz con toda la información del XML
*   so.data.arrayEventos[0]["fichero"] = 2011012600
*   so.data.arrayEventos[0]["fecha"] = 18:00
*   so.data.arrayEventos[0]["descripcion"] = Descripcion del programa
*   ...
*/

var so:SharedObject;
so = SharedObject.getRemote("so", nc.uri, false);
so.connect(nc);

so.onSync = function (info){ 
 
 /**/
 // Muestra algunos datos del SO cuando se actualiza. 
 trace ("***Sincronizando. SO registró un evento: " + info[0].code); 
 // Imprime algunas propiedades del SharedObject
 trace ("*Imprimiendo proximo");
 for (var prop in so.data.proximo){
  trace (prop + " = " + so.data.proximo[prop]);
 } 
 trace ("*Imprimiendo arrayEventos"); 
 for (var programa in so.data.arrayEventos){    
  for (var prop in so.data.arrayEventos[programa]){
   trace (prop +  " = " + so.data.arrayEventos[programa][prop]);
  }  
 } 
 
 // Controla el modo del reproductor en función de la existencia de un stream reproduciéndose.
 if (so.data.reproduciendose==false){  
  proxEventoNombre.text = so.data.proximo["nombre"];
  proxEventoHora.text = "Próximo programa a las " + so.data.proximo["fecha"].toString();
  proxEventoDescrip.text = so.data.proximo["descripcion"]; 
  TVoff();
 }else{  
  infoActual.text = so.data.reproduciendose["nombre"] + ": " + so.data.reproduciendose["descripcion"];
  TVon();
 }
}

/**********************************************************/
/*                Funciones de Interfaz                   */
/**********************************************************/

/*
* Configura la interfaz en modo de emisión inactiva: Oculta controles de pantalla, muestra información.
*/
function TVoff(){ 
 trace ("Ocultando player");
 proxEventoNombre._visible = true;
 proxEventoHora._visible = true;
 proxEventoDescrip._visible = true;
 infoActual._visible = false;
 vid.enabled = false;
 btn_fullScreen._visible = false;
 bannerCEVUG._visible = true;
 fondo._visible = true;
}

/*
* Configura la interfaz en modo de emisión activa: Oculta información, muestra player y controles de pantalla.
*/
function TVon(){
 trace ("Mostrando player");
 vid.enabled = true;
 proxEventoNombre._visible = false;
 proxEventoHora._visible = false;
 proxEventoDescrip._visible = false;
 infoActual._visible = true;
 btn_fullScreen._visible = true;
 bannerCEVUG._visible = false;
 fondo._visible = false; 
}

btn_fullScreen.onRelease = function() {
  Stage.displayState = Stage.displayState == "normal" ? "fullScreen" : "normal";
};


Un código de novato :D en esto de Flash, pero que funciona correctamente.

Enlace