MediaServicesArch

De la misma manera que el el post anterior exprimíamos Windows Azure MOBILE SERVICES, en este caso le toca el turno a Windows Azure MEDIA SERVICES.

Pues bien, estos recientes servicios (PaaS), aun en Preview, tienen como finalidad, facilitar, crear y entregar contenido multimedia a los diferentes dispositivos: HTML5, Silverlight, Flash, Windows (Windows 8, etc.), Windows Phone, Xbox, MacOS, iPhone/iPad y Android, aportando gracias a Windows Azure, flexibilidad, escalabilidad y fiabilidad en la nube y, todo ello basado en “Microsoft Media Platform”.

  • Me gustaría hacer referencia a este enlace, donde Scott Guthrie, comenta brevemente el uso de estos servicios en las olimpiadas de Londres 2012, donde se estos han soportado unas 2.300h de vídeos en directo y contenido VOD (Video On demand)HD.

Y como siempre, Microsoft pone a nuestra disposición unos mágnificos tutoriales, donde poder seguir nuestros primeros pasos. Nosotros iremos un poco más allá hasta completar un ejemplo completo y funcional además de aclarar dudas y seguir algunas recomendaciones.

 

MEDIA SERVICES soporta los siguientes escenarios:

  • Building end-to-end workflows: Todo la aplicación en el Cloud.
  • Building hybrid workflows:  Integración de aplicaciones y procesos.  Pudiendo codificar por ejemplo, contenido en “On-Premisse” subiendo el mismo a Media Services para su transcodificación y transformación en distintos formatos.
  • Providing cloud support for media players: Crear, gestionar y distribuir/entregar contenido media a diferentes dispositivos y plataformas: iOS, Android, and Windows Devices.

Una vez ya conocemos las aspectos generales, veamos a continuación lo que necesitamos para llevar a cabo nuestro ejemplo: “MyFirstMedia Services”

1. Activamos el servicio para usarlo con nuestra subscripción.

2. Creamos un servicio desde la consola de administración (Portal):

imageimage

3. Aseguramos la sincronización de “key” con nuestro storage:

image

4. Creamos, desde Visual Studio un proyecto de tipo Consola

5. Añadimos las siguientes referencias.

  • C:\Program Files\Microsoft SDKs\Windows Azure\.NET SDK\2012-06\bin\Microsoft.WindowsAzure.StorageClient.dll
  • C:\Program Files (x86)\Microsoft WCF Data Services\5.0\bin\.NETFramework\
    • Microsoft.Data.Edm.dll
    • Microsoft.Data.OData.dll
    • Microsoft.Data.Services.dll
    • Microsoft.Data.Services.Client.dll
    • System.Spatial.dll
  • C:\Program Files (x86)\Microsoft SDKs\Windows Azure Media Services\Services\v1.0\Microsoft.WindowsAzure.MediaServices.Client.dll

6.  Conectamos con Media Services obteniendo un Contexto.  En este punto podemos obtener el siguiente error: ”Error 400 Bad Request when using CloudMediaContext with Azure Media Services” si no se introducen correctamente estos parámetros:

   1: this._context = new CloudMediaContext(accountName, accountKey);

Donde:

  • accountName es el nombre del servicio, introducido al crear el mismo y según hemos señalado como “Account Name”.
  • accountKey es el “SECONDARY MEDIA SERVICE ACCESS KEY”.

7. Creamos y encriptamos nuestro activo y lo subimos al Storage de Windows Azure, obteniendo una referencia al container de dichos ficheros. Si el valor de encriptación es “None”, podremos ver el contenido de dichos ficheros una vez realizado el “Upload” utilizando por ejemplo, “Azure Storage Explorer”.

   1: private IAsset UpLoadFiles(string[] files)

   2: {

   3:     // (2) Creamos un asset y añadimos los activos/ficheros para hacer el Upload del asset.            

   4:     return this._context.Assets.Create(files, AssetCreationOptions.StorageEncrypted);

   5: }

En este punto podremos obtener el error: “Could not load file or assembly ‘Microsoft.WindowsAzure.StorageClient, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35’ or one of its dependencies. The located assembly’s manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)” que podemos resolver (gracias al cable de John) incluyendo en nuestro “.config” el siguiente código:

   1: <?xml version="1.0" encoding="utf-8" ?>

   2: <configuration>

   3:   <runtime>

   4:      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

   5:        <dependentAssembly>

   6:          <assemblyIdentity name="Microsoft.WindowsAzure.StorageClient"

   7:                            publicKeyToken="31bf3856ad364e35"

   8:                            culture="neutral" />

   9:          <bindingRedirect oldVersion="1.1.0.0"

  10:                           newVersion="1.7.0.0"/>

  11:        </dependentAssembly>

  12:        <dependentAssembly>

  13:          <assemblyIdentity name="Microsoft.WindowsAzure.StorageClient"

  14:                            publicKeyToken="31bf3856ad364e35"

  15:                            culture="neutral" />

  16:          <publisherPolicy apply="no" />

  17:        </dependentAssembly>

  18:  

  19:      </assemblyBinding>

  20:    </runtime>  

  21: </configuration>

Una vez todo ya podemos hacer el Upload de nuestro asset, ahora ya estos listo para lanzar el Job con el Encoding adecuado:

   1: public IJob CreateEncodingJob(IAsset asset, EnumMediaProcessor enumProcesor)

   2: {

   3:     // Declarar un nuevo Job.

   4:     IJob job = this._context.Jobs.Create("My encoding job");          

   5:  

   6:     string configuration = GetConfiguration(enumProcesor);

   7:     IMediaProcessor processor = this.GetMediaProcessor(enumProcesor);

   8:  

   9:  

  10:     // Crea un tarea con el detalle de codificacion(enconding) utilizando un string preestablecido.

  11:     // Strings preestablecidos para Windows Azure Media Encoder: http://msdn.microsoft.com/en-us/library/jj129582.aspx

  12:     ITask task = job.Tasks.AddNew("My encoding task",

  13:         processor,

  14:         configuration,

  15:         TaskCreationOptions.None);

  16:  

  17:     // Especificar el Asset a codificar

  18:     task.InputMediaAssets.Add(asset);

  19:  

  20:     

  21:     // Añade un asset de salida que contiene el resultado del job.

  22:     // Este asset no es encriptado "AssetCreationOption.None"

  23:     task.OutputMediaAssets.AddNew("Output asset",

  24:         true,

  25:         AssetCreationOptions.None);

  26:  

  27:     // Ejecutar el Job. 

  28:     job.Submit();

  29:  

  30:     return job;

  31: }

En este punto, tenemos que tener claras varias cosas:

  • El Tipo de “Media Processor” a utilizar dependerá  de lo que queramos hacer. Se trata de un string como puede verse en el siguiente snippet y, para conocer todos estos tipos preestablecidos accederemos a este enlace: “Task Preset Strings for Windows Azure Media Encoder”.
  • Cada uno de estos “Media Processor” trata un tipo de ficheros/asset determinado y de la misma manera genera una salida diferente. Por tanto si estamos lanzado un Job para:
    • una conversión de .MP4 a Smooth Streaming tendremos utilizar como “Media Processor”, “MP4 to Smooth Streams Task”, que genera un asset de salida: .ism, .isma, .ismc, .ismv, necesarios para los visores de smooth streaming.
    • una conversión   “Windows Azure Media Encoder” se genera una salida: .mp4, y “_metadata.xml”.
    • Etc. En este enlace, podemos ver todos los tipos de archivos soportados por Media Services.
  • Cada Job, además de trabajar con un “Media Processor” espera una configuración para el mismo, de manera que por ejemplo, para:
   1: // http://www.windowsazure.com/en-us/develop/net/how-to-guides/media-services/

   2: // Windows Azure Media Encoder:  Media Encoder. 

   3: // PlayReady Protection Task:   PlayReady Protection. 

   4: // MP4 to Smooth Streams Task:  Conversión de  .mp4 a formato smooth streaming. 

   5: // Smooth Streams to HLS Task:  Conversión de smooth streaming a formato Apple HTTP Live Streaming (HLS). 

   6: // Storage Decryption:          Desencripta los assets que has sindo encriptados usando Storage Encription. 

   7: private IMediaProcessor GetMediaProcessor(EnumMediaProcessor mediaProcessor)

   8: {

   9:     var theProcessor =

  10:         from p in this._context.MediaProcessors

  11:         where p.Name == mediaProcessor.GetStringValue()

  12:         select p;

  13:  

  14:     IMediaProcessor processor = theProcessor.First();

  15:  

  16:     if (processor == null)

  17:     {

  18:         throw new ArgumentException(string.Format(System.Globalization.CultureInfo.CurrentCulture,

  19:             "Unknown processor",

  20:             mediaProcessor));

  21:     }

  22:     return processor;

  23: }

Si el Job ha terminado correctamente nuestra aplicación cliente podría acceder al asset y descargarlo. No obstante se puede producir erres durante la la codificación, donde para ello, haremos un chequeo del Job para corroborar su estado:

   1: private static ReadOnlyCollection<ErrorDetail> CheckJobProgress(string jobId)

   2: {

   3:     bool jobCompleted = false;

   4:     ReadOnlyCollection<ErrorDetail> jobErrors = null;

   5:  

   6:     while (!jobCompleted && (null == jobErrors))

   7:     {

   8:         IJob theJob = _manager.GetJob(jobId);

   9:         switch (theJob.State)

  10:         {

  11:             case JobState.Finished:

  12:                 jobCompleted = true;

  13:                 break;

  14:             case JobState.Queued:

  15:             case JobState.Scheduled:

  16:             case JobState.Processing:

  17:                 Thread.Sleep(5000);

  18:                 Console.Write(".");

  19:                 break;

  20:             case JobState.Error:

  21:                 jobErrors = theJob.Tasks[0].ErrorDetails;

  22:                 break;

  23:             default:

  24:                 Console.WriteLine("Unknown job state: {0}", theJob.State.ToString());

  25:                 break;

  26:         }

  27:     }

  28:     return jobErrors;

  29: }

A pesar de tener en cuenta las recomendaciones anteriores, se pueden producir algunos errores durante el “Upload” o el “Endonding del Job”

  • “Cannot import a zero-lenght file”: Se trata de error al subir un fichero de tipo imagen e intentar realizar su enconding distinto a “Storage Decryption”.
  • “One or more errors occurred” : Generalmente este se produce si intentamos subir ficheros a través de una conexión débil como por ejemplo desde una 3G con poca cobertura. El detalle de estos puede verse a continuación:

InnerException: System.Data.Services.Client.DataServiceClientException

            HResult=-2146233079

            Message=<?xml version="1.0" encoding="utf-8" standalone="yes"?><error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><code></code><message xml:lang="en-US">Asset has no files uploaded.</message></error>

            Source=Microsoft.Data.Services.Client

            StatusCode=400

            StackTrace:

                 at System.Data.Services.Client.QueryResult.ExecuteQuery(DataServiceContext context)

                 at System.Data.Services.Client.DataServiceRequest.Execute[TElement](DataServiceContext context, QueryComponents queryComponents)

Donde Fiddler nos proporcional la siguiente información:

  • 500: <?xml version="1.0" encoding="utf-8"?><Error><Code>OperationTimedOut</Code><Message>Operation could not be completed within the specified time.

    RequestId:fc8be06f-1801-4724-a549-14a5d327d48f

    Time:2012-09-23T13:33:32.3638228Z</Message></Error>

  • 504:ReadResponse() failed: The server did not return a response for this request.
  • 502: The socket connection to mediasvc5s9w447krcczl.blob.core.windows.net failed. <br />ErrorCode: 10060. <br />A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond 168.63.0.14:80
  • DNS Lookup for "mediasvc5s9w447krcczl.blob.core.windows.net" failed. No such host is known.    

Otros recomendaciones que deberíamos conocer son:

  • En la preview actual, no es posible eliminar los “Assets” subidos de ninguna manera desde la consola.  Mediante código sería de la siguiente manera, teniendo en cuenta que antes de poder borrar un Asset, tenemos que revocar (“Revoke”) su Locator. No obstante y a pesar de todo, en mis pruebas no lo he conseguido.  Supongo que es debido a la versión “Preview”:
   1: foreach (var a in this._context.Assets)

   2: {

   3:     foreach (var l in a.Locators)

   4:         this._context.Locators.Revoke(l);               

   5:  

   6:         this._context.Assets.Delete(a); 

   7: }

  • Incluyo a continuación un par de snippets que nos facilitarán los progresos de carga y descarga para así no desesperarnos:

Progreso de carga (“Upload”) de ficheros/assets:

   1: ...

   2:        _manager.Assets.OnUploadProgress += Assets_OnUploadProgress;

   3: ..

   4: rivate static void Assets_OnUploadProgress(object sender, UploadProgressEventArgs e)

   5:  

   6:    try

   7:    {

   8:        Console.WriteLine("Assets_OnUploadProgress: {0:0.00} %, {1}/{2}, {3:0.00} MB/{4:0.00} MB",

   9:        e.Progress, e.CurrentFile, e.TotalFiles, e.BytesSent / 1024 ^ 2, e.TotalBytes / 1024 ^ 2);

  10:    }

  11:    catch (Exception ex)

  12:    {

  13:        Console.WriteLine("OnUploadProgress event error: {0}{1}{2}", ex.Message, Environment.NewLine, ex.StackTrace.ToString());

  14:    }

Progreso de descarga (“Download”):

   1: private static void DownloadAssetToLocal(IAsset outputAsset, string outputFolder)

   2: {

   3:     Console.WriteLine();

   4:     Console.WriteLine("Files are downloading...please wait.");

   5:     Console.WriteLine();

   6:  

   7:     foreach (IFileInfo outputFile in outputAsset.Files)

   8:     {

   9:         outputFile.OnDownloadProgress += outputFile_OnDownloadProgress;

  10:  

  11:         string localDownloadPath = Path.GetFullPath(outputFolder + @"\" + outputFile.Name);

  12:         Console.WriteLine("File is downloading to:  " + localDownloadPath);

  13:         outputFile.DownloadToFile(Path.GetFullPath(outputFolder + @"\" + outputFile.Name));

  14:         Console.WriteLine();

  15:     }

  16: }

  17:  

  18: static void outputFile_OnDownloadProgress(object sender, DownloadProgressEventArgs e)

  19: {

  20:     Console.WriteLine("    Bytes downloaded: " + e.BytesDownloaded);

  21:     Console.WriteLine("    Download progress %:  " + e.Progress);

  22: }

Espero haber conseguido con este recorrido a través de Media Services acercarnos un poco más al mundo Cloud con Windows Azure.


Saludos @Home

Juanlu, ElGuerre