SyntaxHighlighter

lunes, 17 de diciembre de 2012

Migración de materiales de WebCT a Moodle usando SCORM

En el evento MoodleMoot 2008 presentamos desde el CEVUG el proceso que utilizamos para migrar los materiales de nuestro antiguo WebCT a Moodle.
Aunque posiblemente ya queda un poco lejos aquel proceso, comparto la presentación que llevamos por si a alguien le resulta de utilidad.


martes, 30 de octubre de 2012

Clase introductoria de eportafolios con Moodle y Mahara

Proyecto de Innovación Docente coordinado por Vanesa Gámiz: "El e-portafolio como estrategia didáctica de aprendizaje y evaluación. Implementación con Mahara integrado con Moodle"

Fragmentos de la clase de introducción impartida a los alumnos que participan en el PID.


Ir a la página del proyecto

domingo, 29 de julio de 2012

Analizando logs de Flash Media Server con Perl

El log de Flash Media Server es muy completo, pero al mismo tiempo difícil de analizar con herramientas estándar de análisis de logs. La solución que utilicé la última vez que vinieron pidiendo estadísticas urgentes fue importar el log en LibreOffice Calc e intentar sacar los datos lo más rápido posible para salir del paso.

Como hace tiempo que no toco Perl (al final siempre acabo utilizando PHP porque me es más familiar), he decidido hacer un pequeño script para obtener algunos datos estadísticos de las aplicaciones que se ejecutan en Flash Media Server.

El script recibe el nombre del fichero log y la aplicación FMS sobre la que queremos información, hace un recuento de las ips que se conectan entre los eventos publish y unpublish e imprime un resumen. Me centro en aplicaciones tipo "live" en las que un cliente publica y el resto conectan para seguir la emisión de ese cliente.

Aquí el código:
#!/usr/bin/perl

(scalar @ARGV == 2) || die ("Wrong number of arguments.\nUse: $0 filename app_name \n");

$filename = $ARGV[0];
$appname = $ARGV[1];


$APP_NAME_FIELD = 12;
$DATE_FIELD = 2;
$TIME_FIELD = 3;
$CLIENT_IP_FIELD = 16;


open FILE, $filename or die "File not found!!\n";

$publishing = 0;
while (<FILE>){
 @line = split;
 
 if (  (($line[0]) eq "publish") and (($line[$APP_NAME_FIELD]) eq $appname)   ){
  $publishing = 1;
  print "Application '$appname' started at $line[$TIME_FIELD] ($line[$DATE_FIELD])\n"; 
 }
 
 if (  (($line[0]) eq "unpublish") and (($line[$APP_NAME_FIELD]) eq $appname)   ){
  $publishing = 0;
  
  print "Application '$appname' stopped at $line[$TIME_FIELD] ($line[$DATE_FIELD])\n"; 
  
  # Print ips and number of connections of each one.
  print scalar keys %ip_array ," different clients received the broadcast\n";
  print "IP \t #Connections\n";
  foreach $ip (keys %ip_array){
   print "$ip \t $ip_array{$ip}\n";
  }
  undef %ip_array;
  print "********************************************************************\n";
 }
 
 if ($publishing){
  if (  (($line[0]) eq "connect") and (($line[$APP_NAME_FIELD]) eq $appname)   ){
   if (exists ( $ip_array{$line[$CLIENT_IP_FIELD]} ) ){
    $ip_array{$line[$CLIENT_IP_FIELD]}++;
   }else{
    $ip_array{$line[$CLIENT_IP_FIELD]}=1;
   }
  }  
 }

}

El resultado es el siguiente:

new-host-2:fms_log emilio$ ./fms_stats.pl access.2012032100.log live 
Application 'live' started at 11:13:22 (2012-03-21)
Application 'live' stopped at 12:11:26 (2012-03-21)
15 clients received the broadcast
IP   #Connections
90.170.72.143   1
172.20.18.215   1
111.111.23.53   2
111.111.17.203   2
111.111.23.152   1
172.20.8.133   1
172.20.18.83   2
83.33.247.36   1
172.20.17.167   1
111.111.31.78   1
111.111.23.173   1
111.111.23.55   1
111.111.204.147   1
83.32.145.161   1
111.111.22.139   5
********************************************************************
Application 'live' started at 17:08:00 (2012-03-21)
Application 'live' stopped at 17:08:08 (2012-03-21)
0 clients received the broadcast
IP   #Connections
********************************************************************
Application 'live' started at 17:09:26 (2012-03-21)
Application 'live' stopped at 17:14:16 (2012-03-21)
1 clients received the broadcast
IP   #Connections
111.111.104.51   1
********************************************************************
Application 'live' started at 17:14:50 (2012-03-21)
Application 'live' stopped at 18:48:12 (2012-03-21)
4 clients received the broadcast
IP   #Connections
90.170.72.143   2
111.111.36.4   3
217.216.52.82   1
111.111.104.51   5
********************************************************************



lunes, 7 de mayo de 2012

Configuración rápida de Zabbix + SNMP + HP Insight en un HP Proliant

En el último post estuve cambiando un disco de un HP Proliant. Tengo que decir que enterarte de que un disco falla gracias al led de estado que hay en su carcasa no es lo más adecuado (mea culpa!!). En los servidores Proliant tenemos varios sistemas que nos mantienen informados de cualquier incidencia hardware, entre otros: Las herramientas de configuración-diagnóstico en línea de órdenes (hpacucli, hpasmcli, etc.), la web de monitorización "HP System Manager Homepage" (si la hemos instalado), o el sistema de notificación vía SNMP.

La interfaz de monitorización normalmente se activa al instalar el HP PSP en el puerto 2831. Nos da acceso a gran cantidad de información de estado del servidor con una interfaz gráfica muy cómoda.


En la figura tenemos información del sistema, de las controladoras de discos, del estado de los adaptadores de red, de la interfaz ILO, etc. Es una herramienta muy potente ya que, además, dispone de un log donde se registran todos los cambios en los parámetros monitorizados. Pero falta algo, que nos avise cuando algo falle.

Como vemos en la parte superior derecha de la ventana, en la sección "Data Source", todo el mecanismo de monitorización se hace a través de SNMP, entonces, ¿por qué no ir al origen y consultar directamente estos sensores?

SNMP

La verdad es que tan sólo he leído un poco sobre SNMP, lo justo para poder consultar algunos valores desde Zabbix (aunque también es posible con NAGIOS).

Debemos tener en cuenta que SNMP es un servicio que viene en cualquier distribución de Linux. Con este servicio estándar podemos consultar una serie de monitores básicos de nuestro servidor definidos en una colección MIB (Management Information Base), que no es más que un conjunto de valores organizados jerárquicamente que describen el estado de muchos parámetros del servidor.
No quiero entrar en el tema mucho porque es bastante complejo y no le he dedicado mucho tiempo, así que vamos al grano.

Lo primero que he hecho ha sido comprobar que el servicio está funcionando con, por ejemplo service snmpd status o chkconfig --list | grep snmp con el que además podemos ver si los agentes snmp de HP están funcionando. Si se ha instalado el servicio SNMP y el Proliant Support Pack todo debería estar funcionando. Comprobamos la salida que nos da snmpwalk, que imprime todos los valores en una rama del árbol de indicadores, en este caso system.

[root@localhost ~]# snmpwalk -v 2c -c public localhost system
SNMPv2-MIB::sysDescr.0 = STRING: Linux localhost 2.6.18-274.7.1.el5 #1 SMP Thu Oct 20 16:21:01 EDT 2011 x86_64
SNMPv2-MIB::sysObjectID.0 = OID: NET-SNMP-MIB::netSnmpAgentOIDs.10
DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (3655739) 10:09:17.39
SNMPv2-MIB::sysContact.0 = STRING: Root  (configure /etc/snmp/snmp.local.conf)
SNMPv2-MIB::sysName.0 = STRING: localhost
SNMPv2-MIB::sysLocation.0 = STRING: Unknown (edit /etc/snmp/snmpd.conf)
SNMPv2-MIB::sysORLastChange.0 = Timeticks: (0) 0:00:00.00
SNMPv2-MIB::sysORID.1 = OID: SNMPv2-MIB::snmpMIB
SNMPv2-MIB::sysORID.2 = OID: TCP-MIB::tcpMIB
SNMPv2-MIB::sysORID.3 = OID: IP-MIB::ip
SNMPv2-MIB::sysORID.4 = OID: UDP-MIB::udpMIB
SNMPv2-MIB::sysORID.5 = OID: SNMP-VIEW-BASED-ACM-MIB::vacmBasicGroup
SNMPv2-MIB::sysORID.6 = OID: SNMP-FRAMEWORK-MIB::snmpFrameworkMIBCompliance
SNMPv2-MIB::sysORID.7 = OID: SNMP-MPD-MIB::snmpMPDCompliance
SNMPv2-MIB::sysORID.8 = OID: SNMP-USER-BASED-SM-MIB::usmMIBCompliance
SNMPv2-MIB::sysORDescr.1 = STRING: The MIB module for SNMPv2 entities
SNMPv2-MIB::sysORDescr.2 = STRING: The MIB module for managing TCP implementations
SNMPv2-MIB::sysORDescr.3 = STRING: The MIB module for managing IP and ICMP implementations
SNMPv2-MIB::sysORDescr.4 = STRING: The MIB module for managing UDP implementations
SNMPv2-MIB::sysORDescr.5 = STRING: View-based Access Control Model for SNMP.
SNMPv2-MIB::sysORDescr.6 = STRING: The SNMP Management Architecture MIB.
SNMPv2-MIB::sysORDescr.7 = STRING: The MIB for Message Processing and Dispatching.
SNMPv2-MIB::sysORDescr.8 = STRING: The management information definitions for the SNMP User-based Security Model.
SNMPv2-MIB::sysORUpTime.1 = Timeticks: (0) 0:00:00.00
SNMPv2-MIB::sysORUpTime.2 = Timeticks: (0) 0:00:00.00
SNMPv2-MIB::sysORUpTime.3 = Timeticks: (0) 0:00:00.00
SNMPv2-MIB::sysORUpTime.4 = Timeticks: (0) 0:00:00.00
SNMPv2-MIB::sysORUpTime.5 = Timeticks: (0) 0:00:00.00
SNMPv2-MIB::sysORUpTime.6 = Timeticks: (0) 0:00:00.00
SNMPv2-MIB::sysORUpTime.7 = Timeticks: (0) 0:00:00.00
SNMPv2-MIB::sysORUpTime.8 = Timeticks: (0) 0:00:00.00

Si tenemos respuesta de la orden snmpwalk todo está listo para comenzar con la configuración.

La información que podemos extraer con las consultas a SNMP pueden ser bastante sensible y, por defecto en la configuración de SNMP, se bloquean ciertas ramas. En el fichero /etc/snmp/snmp.conf se pueden definir las ramas que se mostrarán y a qué grupos. Yo lo que he hecho es añadir a la lista systemview la rama MIB correspondiente a los monitores de HP Proliant con la siguiente directiva:


# Make at least  snmpwalk -v 1 localhost -c public system fast again.
#       name           incl/excl     subtree         mask(optional)
view    systemview    included   .1.3.6.1.2.1.1
view    systemview    included   .1.3.6.1.2.1.25.1.1
# Emilio: Linea de abajo añadida para poder consultar monitores de hp insight
view    systemview    included   .1.3.6.1.4.1.232

La rama .1.3.6.1.4.1.232 contiene la información asociada a la rama enterprises. Podéis ver un listado con todos los OIDs en el archivo /opt/hp/hp-snmp-agents/mibs/cmaobjects.mibdef. Reiniciamos snmpd y consultamos la rama enterprises con snmpwalk. La orden debería mostrar un listado bastante largo con todos los valores del árbol MIB.

Con esto ya podríamos consultar los monitores de forma periódica con un script en bash por ejemplo que envíe un correo en caso de encontrar un valor no deseado. Pero podemos ir un paso más allá usando ZABBIX.

SNMP + ZABBIX

Zabbix tiene la capacidad de hacer consultas a SNMP, es más, uno de los usuarios de Zabbix ya se ha molestado en hacer una plantilla que consulta algunos de los monitores más importantes de los servidores Proliant. Tan sólo hay que asociar la plantilla a nuestro servidor dentro de Zabbix, abrir el puerto 161 Udp del servidor a monitorizar y reiniciar zabbix_server.

Sensores Insight Manager
Y aquí tenemos los sensores que añade la plantilla. Algunos aparecerán como "no soportados" según la configuración de nuestro equipo. Por ejemplo, en este equipo sólo tenemos una controladora de discos, por lo que diskController2 aparece como no soportado. Ya disponemos de información tan importante como el estado de las controladoras de disco, de las fuentes de alimentación, de los ventiladores, de la memoria, temperaturas...

La plantilla define estos monitores como críticos, por lo que cualquier cambio en alguno de ellos será considerado como tal en Zabbix y desencadenará la acción que nosotros hemos configurado para eventos críticos: email, sms, etc.

Una buena página para acceder al significado de cada monitor con un enorme listado de MIBs, enlazo directamente los monitores de HP Health:
http://www.mibdepot.com/cgi-bin/getmib3.cgi?win=mib_a&n=CPQHLTH-MIB&r=compaq&f=Compaq-Health.mib&t=tree&v=v1&i=0#compaq



viernes, 4 de mayo de 2012

Cambiando un Disco Duro en un HP Proliant

Shit happens!! Si, solo es cuestión de tiempo. Tarde o temprano todo aparato electrónico acaba fallando, incluso si es de buena calidad. Ayer, cuando me disponía a cambiar la cinta de copia de seguridad de uno de nuestros servidores me di cuenta de que uno de los discos duros tenía iluminado el led de fallo.

Recuerdo la primera vez que nos falló un disco en el trabajo. Fue en un servidor clónico, muy potente, poco después de comprarlo. Entonces todas nuestras aplicaciones estaban alojadas en este servidor. Aprendimos mucho aquella vez: es mejor invertir en un servidor que te dé garantías y en un buen servicio técnico que en uno tipo "hacendado" por muy potente que sea, lo importante que es poder confiar en tu sistema de copias de seguridad, que debes prever situaciones de emergencia, que andar con pies de plomo y tener todo medianamente bien atado es imprescindible si quieres ahorrarte muchos dolores de cabeza porque LA GENTE SOLO SE ACUERDA DE " EL INFORMÁTICO" CUANDO ALGO FALLA. Por eso sustituimos esa máquina por nuestro primer HP Proliant, ahora tenemos 15.

Lavadora o servidor??

No quiero hacer de comercial de HP, seguro que Dell, Sun o cualquier marca de renombre tienen buen hardware, proveen de herramientas software adecuadas y disponen de un servicio técnico de calidad.

Bueno, voy al grano. Se nos ha roto uno de los discos que, junto con otros 3, formaba parte de un conjunto RAID 1+0. Como con esta configuración RAID podemos "perder" un disco, todo el sistema sigue funcionando. Los discos para estos sistemas vienen incrustados en una especie de chasis que facilita su manejo y además da información del estado de los mismos por medio de una serie de leds.

Disco defectuoso
Podemos confirmar esta información usando una de las herramientas del paquete Proliant Support Pack hpacucli. La sintaxis es la siguiente:


[root@localhost hpacucli]# hpacucli ctrl all show config

Smart Array 641 in Slot 3     (sn: XXXXX)

   array A (Parallel SCSI, Unused Space: 0 MB)

      logicaldrive 1 (273.5 GB, RAID 1+0, Interim Recovery Mode)

      physicaldrive 1:2   (port 1:id 2 , Parallel SCSI, 146 GB, Failed)
      physicaldrive 1:3   (port 1:id 3 , Parallel SCSI, 146.8 GB, OK)
      physicaldrive 1:4   (port 1:id 4 , Parallel SCSI, 146.8 GB, OK)
      physicaldrive 1:5   (port 1:id 5 , Parallel SCSI, 146.8 GB, OK)

Como vemos en la salida de hpacucli el disco en el slot 2 está defectuoso.

Si contamos con un contrato de mantenimiento con HP el proceso de pedido se facilita mucho. Solo tienes que dar el número de serie del servidor y el modelo del disco. Podemos verlo usando también las herramientas de HP PSP. En pocas horas recibes el disco e incluso te pueden enviar un técnico para que lo reemplace sin coste adicional. Usando hpasmcli podemos ver toda la información del servidor.

HP management CLI for Linux (v1.0)
Copyright 2004 Hewlett-Packard Development Group, L.P.

--------------------------------------------------------------------------
NOTE: Some hpasmcli commands may not be supported on all Proliant servers.
      Type 'help' to get a list of all top level commands.
--------------------------------------------------------------------------
hpasmcli> show server
System        : ProLiant ML350 G4p
Serial No.    : CZJ6XXXXX      
ROM version   : D19 02/15/2006
iLo present   : Yes
Embedded NICs : 1
 NIC1 MAC: XX:XX:XX:XX:XX:XX

Processor: 0
 Name         : Intel Xeon
 Stepping     : 10
 Speed        : 3000 MHz
 Bus          : 800 MHz
 Socket       : 1
 Level2 Cache : 2048 KBytes
 Status       : Ok

Processor: 1
 Name         : Intel Xeon
 Stepping     : 10
 Speed        : 3000 MHz
 Bus          : 800 MHz
 Socket       : 2
 Level2 Cache : 2048 KBytes
 Status       : Ok

Processor total  : 2

Memory installed : 4096 MBytes
ECC supported    : Yes

Arriba tenemos el número de serie del servidor. Y para ver la información del disco podemos utilizar de nuevo hpacucli:

[root@localhost hpacucli]# hpacucli ctrl slot=3 pd all show detail

Smart Array 641 in Slot 3

   array A (Failed)

      physicaldrive 1:2
         SCSI Bus: 1
         SCSI ID: 2
         Status: Failed
         Drive Type: Data Drive
         Interface Type: Parallel SCSI
         Transfer Mode: Sync Narrow
         Size: 146 GB
         Transfer Speed: 10 MB/Sec
         Rotational Speed: 10000
         Firmware Revision: HPB1
         Serial Number: XXXXXX
         Model: COMPAQ  BD14689BB9      
      physicaldrive 1:3
         SCSI Bus: 1
         SCSI ID: 3
         Status: OK
         Drive Type: Data Drive
         Interface Type: Parallel SCSI
         Transfer Mode: Ultra Narrow
         Size: 146.8 GB
         Transfer Speed: 20 MB/Sec
         Rotational Speed: 10000
         Firmware Revision: HPB1
         Serial Number: XXXXXX
         Model: COMPAQ  BD14689BB9      
      physicaldrive 1:4
         SCSI Bus: 1
         SCSI ID: 4
         Status: OK
         Drive Type: Data Drive
         Interface Type: Parallel SCSI
         Transfer Mode: Ultra Wide
         Size: 146.8 GB
         Transfer Speed: 40 MB/Sec
         Rotational Speed: 10000
         Firmware Revision: HPB4
         Serial Number: XXXXXX
         Model: COMPAQ  BD1468A4C5      
      physicaldrive 1:5
         SCSI Bus: 1
         SCSI ID: 5
         Status: OK
         Drive Type: Data Drive
         Interface Type: Parallel SCSI
         Transfer Mode: Ultra Wide
         Size: 146.8 GB
         Transfer Speed: 40 MB/Sec
         Rotational Speed: 15000
         Firmware Revision: HPB1
         Serial Number: XXXXXX
         Model: COMPAQ  BF1468AFEB

Si alguna vez habéis visto el precio de una buena controladora de discos os habréis dado cuenta que hay un gran salto entre las las controladoras de "andar por casa" y las de gama empresarial. La respuesta es muy sencilla "hacen magia": Hot Swap de discos, reconstrucción de conjuntos RAID de varios tipos de forma rápida y transparente... Tan fácil como extraer el disco defectuoso e introducir el nuevo, la controladora se encarga de todo.

Cambiando el disco

Si consultamos otra vez el estado de la controladora de discos podemos ver que se ha detectado correctamente el cambio y que el proceso de reconstrucción del RAID ha comenzado, cosa que ya notábamos en los indicadores led de los discos duros asociados al conjunto RAID.

[root@localhost hpacucli]# hpacucli ctrl all show config

Smart Array 641 in Slot 3     (sn: XXXX)

   array A (Parallel SCSI, Unused Space: 0 MB)

      logicaldrive 1 (273.5 GB, RAID 1+0, Recovering 1.6% complete)

      physicaldrive 1:2   (port 1:id 2 , Parallel SCSI, 146.8 GB, Rebuilding)
      physicaldrive 1:3   (port 1:id 3 , Parallel SCSI, 146.8 GB, OK)
      physicaldrive 1:4   (port 1:id 4 , Parallel SCSI, 146.8 GB, OK)
      physicaldrive 1:5   (port 1:id 5 , Parallel SCSI, 146.8 GB, OK)

Según la rapidez del disco y la controladora la reconstrucción tardará más o menos. En nuestro servidor con discos de 10K rpm ha tardado alrededor de 40 minutos, en caliente, ejecutándose en segundo plano mientras el servidor seguía funcionando con normalidad.

Resumen de órdenes de hpacucli
Resumen de órdenes de hpasmcli

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