Implementare lo streaming del failover con Servizi multimediali di Azure

Questa procedura dettagliata illustra come copiare il contenuto (BLOB) da un asset all'altro per gestire la ridondanza dello streaming on demand. Questo scenario è utile se si desidera configurare la rete di distribuzione dei contenuti di Azure affinché esegua il failover tra due data center, in caso di interruzione in uno di essi. In questa procedura dettagliata viene usato l'SDK dei Servizi multimediali di Azure, l'API REST di Servizi multimediali di Microsoft Azure e l'SDK di Archiviazione di Azure per dimostrare queste attività:

  1. Configurare un account di Servizi multimediali nel "Data center A".
  2. Caricare in un asset di origine un file in formato intermedio.
  3. Codificare l'asset in file MP4 a bit multipli.
  4. Creare un localizzatore per la firma di accesso condiviso di sola lettura. Questo consente all'asset di origine di avere l'accesso in lettura al contenitore nell'account di archiviazione associato all'asset di origine.
  5. Ottenere il nome del contenitore dell'asset di origine dal localizzatore firma di accesso condiviso di sola lettura creato nel passaggio precedente. Questa operazione è necessaria per copiare i BLOB tra gli account di archiviazione, operazione descritta più avanti in questo argomento.
  6. Creare un localizzatore di origine per l'asset creato mediante l'attività di codifica.

A quel punto, per gestire il failover:

  1. Configurare un account di Servizi multimediali nel "Data center B".
  2. Creare un asset di destinazione vuoto nell'account di Servizi multimediali di destinazione.
  3. Creare un localizzatore per la firma di accesso condiviso di scrittura. Questo consente all'asset vuoto di destinazione di avere l'accesso in scrittura al contenitore nell'account di archiviazione di destinazione associato all'asset di destinazione.
  4. Usare SDK di Archiviazione di Azure per copiare i BLOB (file di asset) tra l'account di archiviazione di origine nel "Data center A" e l'account di archiviazione di destinazione nel "Data center B". Questi account di archiviazione sono associati agli asset di interesse.
  5. Associare all'asset di destinazione i BLOB (file di asset) copiati nel contenitore BLOB di destinazione.
  6. Creare un localizzatore di origine per l'asset nel "Data center B" e specificarne l'ID generato per l'asset nel "Data center A".

In questo modo si ottengono URL di streaming in cui i percorsi relativi sono uguali (differiscono solo gli URL di base).

Per gestire eventuali guasti, è quindi possibile creare una rete per la distribuzione di contenuti basata su tali localizzatori di origine.

Si applicano le considerazioni seguenti:

  • La versione corrente di Media Services SDK non supporta la generazione a livello di codice delle informazioni IAssetFile per l'associazione di un asset ai file di asset. Usare invece l'API REST CreateFileInfos dell'API REST di Servizi multimediali per farlo.
  • Gli asset con crittografia di archiviazione (AssetCreationOptions.StorageEncrypted) non sono supportati per la replica, in quanto la chiave di crittografia è diversa nei due account di Servizi multimediali.
  • Se si vuole sfruttare i vantaggi della creazione dinamica dei pacchetti, verificare che l'endpoint di streaming da cui trasmettere i contenuti si trovi nello stato In esecuzione.
Nota

Considerare la possibilità di usare il replicatore di Servizi multimediali come alternativa all'implementazione manuale di uno scenario di streaming con failover. Tale strumento consente di replicare gli asset tra due account di Servizi multimediali.

Prerequisiti

  • Due account di Servizi multimediali in una sottoscrizione di Azure nuova o esistente. Vedere l'articolo relativo alla creazione di un account di Servizi multimediali.
  • Sistemi operativi: Windows 7, Windows 2008 R2 o Windows 8.
  • .NET Framework 4.5 o .NET Framework 4.
  • Visual Studio 2010 SP1 o versioni successive (Professional, Premium, Ultimate o Express).

Configurare il progetto

In questa sezione si crea e si configura un progetto di applicazione console in C#.

  1. Usare Visual Studio per creare una nuova soluzione contenente il progetto di applicazione console in C#. Immettere HandleRedundancyForOnDemandStreaming come nome e quindi fare clic su OK.
  2. Creare la cartella SupportFiles allo stesso livello del file di progetto HandleRedundancyForOnDemandStreaming.csproj. Nella cartella SupportFiles creare le cartelle OutputFiles e MP4Files. Copiare un file con estensione .mp4 nella cartella MP4Files. (In questo esempio, viene usato il file BigBuckBunny.mp4.)
  3. Usare NuGet per aggiungere riferimenti alle DLL correlate a Servizi multimediali. Nel menu principale di Visual Studio scegliere STRUMENTI > Library Package Manager (Gestione pacchetti libreria) > Console di Gestione pacchetti. Nella finestra della console digitare Install-Package windowsazure.mediaservices e premere Invio.
  4. Aggiungere gli altri riferimenti necessari per il progetto: System.Configuration, System.Runtime.Serialization e System.Web.
  5. Sostituire le istruzioni using aggiunte per impostazione predefinita al file Programs.cs con le seguenti:

     using System;
     using System.Configuration;
     using System.Globalization;
     using System.IO;
     using System.Net;
     using System.Runtime.Serialization.Json;
     using System.Text;
     using System.Threading;
     using System.Threading.Tasks;
     using System.Web;
     using System.Xml;
     using System.Linq;
     using Microsoft.WindowsAzure;
     using Microsoft.WindowsAzure.MediaServices.Client;
     using Microsoft.WindowsAzure.Storage;
     using Microsoft.WindowsAzure.Storage.Blob;
     using Microsoft.WindowsAzure.Storage.Auth;
    
  6. Aggiungere la sezione appSettings al file .config e aggiornare i valori in base ai valori di chiave e nome dell'account di Servizi multimediali e di archiviazione.

     <appSettings>
       <add key="MediaServicesAccountNameSource" value="Media-Services-Account-Name-Source"/>
       <add key="MediaServicesAccountKeySource" value="Media-Services-Account-Key-Source"/>
       <add key="MediaServicesStorageAccountNameSource" value="Media-Services-Storage-Account-Name-Source"/>
       <add key="MediaServicesStorageAccountKeySource" value="Media-Services-Storage-Account-Key-Source"/>
       <add key="MediaServicesAccountNameTarget" value="Media-Services-Account-Name-Target" />
       <add key="MediaServicesAccountKeyTarget" value=" Media-Services-Account-Key-Target" />
       <add key="MediaServicesStorageAccountNameTarget" value=" Media-Services-Storage-Account-Name-Target" />
       <add key="MediaServicesStorageAccountKeyTarget" value=" Media-Services-Storage-Account-Key-Target" />
     </appSettings>
    

Aggiungere un codice in grado di gestire la ridondanza per lo streaming on demand

In questa sezione, si crea la possibilità di gestire la ridondanza.

  1. Aggiungere alla classe Program i campi a livello di classe seguenti.

     // Read values from the App.config file.
     private static readonly string MediaServicesAccountNameSource = ConfigurationManager.AppSettings["MediaServicesAccountNameSource"];
     private static readonly string MediaServicesAccountKeySource = ConfigurationManager.AppSettings["MediaServicesAccountKeySource"];
     private static readonly string StorageNameSource = ConfigurationManager.AppSettings["MediaServicesStorageAccountNameSource"];
     private static readonly string StorageKeySource = ConfigurationManager.AppSettings["MediaServicesStorageAccountKeySource"];
    
     private static readonly string MediaServicesAccountNameTarget = ConfigurationManager.AppSettings["MediaServicesAccountNameTarget"];
     private static readonly string MediaServicesAccountKeyTarget = ConfigurationManager.AppSettings["MediaServicesAccountKeyTarget"];
     private static readonly string StorageNameTarget = ConfigurationManager.AppSettings["MediaServicesStorageAccountNameTarget"];
     private static readonly string StorageKeyTarget = ConfigurationManager.AppSettings["MediaServicesStorageAccountKeyTarget"];
    
     // Base support files path.  Update this field to point to the base path  
     // for the local support files folder that you create. 
     private static readonly string SupportFiles = Path.GetFullPath(@"../..\SupportFiles");
    
     // Paths to support files (within the above base path). 
     private static readonly string SingleInputMp4Path = Path.GetFullPath(SupportFiles + @"\MP4Files\BigBuckBunny.mp4");
     private static readonly string OutputFilesFolder = Path.GetFullPath(SupportFiles + @"\OutputFiles");
    
     // Class-level field used to keep a reference to the service context.
     static private CloudMediaContext _contextSource = null;
     static private CloudMediaContext _contextTarget = null;
     static private MediaServicesCredentials _cachedCredentialsSource = null;
     static private MediaServicesCredentials _cachedCredentialsTarget = null;
    
  2. Sostituire la definizione predefinita del metodo Main con quella indicata di seguito. Di seguito sono specificate le definizioni dei metodi chiamate da Main.

     static void Main(string[] args)
     {
         _cachedCredentialsSource = new MediaServicesCredentials(
                         MediaServicesAccountNameSource,
                         MediaServicesAccountKeySource);
    
         _cachedCredentialsTarget = new MediaServicesCredentials(
                         MediaServicesAccountNameTarget,
                         MediaServicesAccountKeyTarget);
    
         // Get server context.    
         _contextSource = new CloudMediaContext(_cachedCredentialsSource);
         _contextTarget = new CloudMediaContext(_cachedCredentialsTarget);
    
         IAsset assetSingleFile = CreateAssetAndUploadSingleFile(_contextSource,
                                     AssetCreationOptions.None,
                                     SingleInputMp4Path);
    
         IJob job = CreateEncodingJob(_contextSource, assetSingleFile);
    
         if (job.State != JobState.Error)
         {
             IAsset sourceOutputAsset = job.OutputMediaAssets[0];
             // Get the locator for Smooth Streaming
             var sourceOriginLocator = GetStreamingOriginLocator(_contextSource, sourceOutputAsset);
    
             Console.WriteLine("Locator Id: {0}", sourceOriginLocator.Id);
    
             // 1.Create a read-only SAS locator for the source asset to have read access to the container in the source Storage account (associated with the source Media Services account)
             var readSasLocator = GetSasReadLocator(_contextSource, sourceOutputAsset);
    
             // 2.Get the container name of the source asset from the read-only SAS locator created in the previous step
             string containerName = (new Uri(readSasLocator.Path)).Segments[1];
    
             // 3.Create a target empty asset in the target Media Services account
             var targetAsset = CreateTargetEmptyAsset(_contextTarget, containerName);
    
             // 4.Create a write SAS locator for the target empty asset to have write access to the container in the target Storage account (associated with the target Media Services account)
             ILocator writeSasLocator = CreateSasWriteLocator(_contextTarget, targetAsset);
    
             // Get asset container name.
             string targetContainerName = (new Uri(writeSasLocator.Path)).Segments[1];
    
             // 5.Copy the blobs in the source container (source asset) to the target container (target empty asset)
             CopyBlobsFromDifferentStorage(containerName, targetContainerName, StorageNameSource, StorageKeySource, StorageNameTarget, StorageKeyTarget);
    
             // 6.Use the CreateFileInfos Media Services REST API to automatically generate all the IAssetFile’s for the target asset. 
             //      This API call is not supported in the current Media Services SDK for .NET. 
             CreateFileInfosForAssetWithRest(_contextTarget, targetAsset, MediaServicesAccountNameTarget, MediaServicesAccountKeyTarget);
    
             // Check if the AssetFiles are now  associated with the asset.
             Console.WriteLine("Asset files assocated with the {0} asset:", targetAsset.Name);
             foreach (var af in targetAsset.AssetFiles)
             {
                 Console.WriteLine(af.Name);
             }
    
             // 7.Copy the Origin locator of the source asset to the target asset by using the same Id
             var replicatedLocatorPath = CreateOriginLocatorWithRest(_contextTarget,
                         MediaServicesAccountNameTarget, MediaServicesAccountKeyTarget,
                         sourceOriginLocator.Id, targetAsset.Id);
    
             // Create a full URL to the manifest file. Use this for playback
             // in streaming media clients. 
             string originalUrlForClientStreaming = sourceOriginLocator.Path + GetPrimaryFile(sourceOutputAsset).Name + "/manifest";
    
             Console.WriteLine("Original Locator Path: {0}\n", originalUrlForClientStreaming);
    
             string replicatedUrlForClientStreaming = replicatedLocatorPath + GetPrimaryFile(sourceOutputAsset).Name + "/manifest";
    
             Console.WriteLine("Replicated Locator Path: {0}", replicatedUrlForClientStreaming);
    
             readSasLocator.Delete();
             writeSasLocator.Delete();
     }
    
  3. Le definizioni seguenti dei metodi sono chiamati dal metodo Main.

    Nota

    È previsto un limite di 1.000.000 di criteri per i diversi criteri di Servizi multimediali (ad esempio per i criteri Locator o ContentKeyAuthorizationPolicy). Se si usano sempre gli stessi giorni e le stesse autorizzazioni di accesso, è necessario usare lo stesso ID criterio. Ad esempio, usare lo stesso ID per i criteri dei localizzatori che devono rimanere sul posto per molto tempo (criteri di non-caricamento). Per altre informazioni, vedere questo argomento.

     public static IAsset CreateAssetAndUploadSingleFile(CloudMediaContext context,
                                                     AssetCreationOptions assetCreationOptions,
                                                     string singleFilePath)
     {
         var assetName = "UploadSingleFile_" + DateTime.UtcNow.ToString();
    
         var asset = context.Assets.Create(assetName, assetCreationOptions);
    
         Console.WriteLine("Asset name: " + asset.Name);
    
         var fileName = Path.GetFileName(singleFilePath);
    
         var assetFile = asset.AssetFiles.Create(fileName);
    
         Console.WriteLine("Created assetFile {0}", assetFile.Name);
    
         Console.WriteLine("Upload {0}", assetFile.Name);
    
         assetFile.Upload(singleFilePath);
         Console.WriteLine("Done uploading of {0}", assetFile.Name);
    
         return asset;
     }
    
     public static IJob CreateEncodingJob(CloudMediaContext context, IAsset asset)
     {
         // Declare a new job.
         IJob job = context.Jobs.Create("My encoding job");
    
         // Get a media processor reference, and pass to it the name of the 
         // processor to use for the specific task.
         IMediaProcessor processor = GetLatestMediaProcessorByName(context,
                                                 "Media Encoder Standard");
    
         // Create a task with the encoding details, using a string preset.
         // In this case "Adaptive Streaming" preset is used.
         ITask task = job.Tasks.AddNew("My encoding task",
             processor,
             "Adaptive Streaming",
             TaskOptions.ProtectedConfiguration);
    
         // Specify the input asset to be encoded.
         task.InputAssets.Add(asset);
    
         // Add an output asset to contain the results of the job. 
         // This output is specified as AssetCreationOptions.None, which 
         // means the output asset is in the clear (unencrypted). 
         var outputAssetName = "OutputAsset_" + Guid.NewGuid();
         task.OutputAssets.AddNew(outputAssetName,
             AssetCreationOptions.None);
    
         // Use the following event handler to check job progress.  
         job.StateChanged += new
                 EventHandler<JobStateChangedEventArgs>(StateChanged);
    
         // Launch the job.
         job.Submit();
    
         // Optionally log job details. This displays basic job details
         // to the console and saves them to a JobDetails-{JobId}.txt file 
         // in your output folder.
         LogJobDetails(context, job.Id);
    
         // Check job execution and wait for job to finish. 
         Task progressJobTask = job.GetExecutionProgressTask(CancellationToken.None);
         progressJobTask.Wait();
    
         // Get an updated job reference.
         job = GetJob(context, job.Id);
    
         // Since we the output asset contains a set of Smooth Streaming files,
         // set the .ism file to be the primary file
         if (job.State != JobState.Error)
             SetPrimaryFile(job.OutputMediaAssets[0]);
    
         return job;
     }
    
     public static ILocator GetStreamingOriginLocator(CloudMediaContext context, IAsset assetToStream)
     {
         // Get a reference to the streaming manifest file from the  
         // collection of files in the asset. 
         IAssetFile manifestFile = GetPrimaryFile(assetToStream);
    
         // Create a 30-day readonly access policy. 
         // You cannot create a streaming locator using an AccessPolicy that includes write or delete permissions.            
    
         IAccessPolicy policy = context.AccessPolicies.Create("Streaming policy",
             TimeSpan.FromDays(30),
             AccessPermissions.Read);
    
         // Create a locator to the streaming content on an origin. 
         ILocator originLocator = context.Locators.CreateLocator(LocatorType.OnDemandOrigin,
             assetToStream,
             policy,
             DateTime.UtcNow.AddMinutes(-5));
    
         // Return the locator. 
         return originLocator;
     }
    
     public static ILocator GetSasReadLocator(CloudMediaContext context, IAsset asset)
     {
         IAccessPolicy accessPolicy = context.AccessPolicies.Create("File Download Policy",
             TimeSpan.FromDays(30), AccessPermissions.Read);
    
         ILocator sasLocator = context.Locators.CreateLocator(LocatorType.Sas,
             asset, accessPolicy);
    
         return sasLocator;
     }
    
     public static ILocator CreateSasWriteLocator(CloudMediaContext context, IAsset asset)
     {
    
         IAccessPolicy writePolicy = context.AccessPolicies.Create("Write Policy",
             TimeSpan.FromDays(30), AccessPermissions.Write);
    
         ILocator sasLocator = context.Locators.CreateLocator(LocatorType.Sas,
             asset, writePolicy);
    
         return sasLocator;
     }
    
     public static IAsset CreateTargetEmptyAsset(CloudMediaContext context, string containerName)
     {
         // Create a new asset.
         IAsset assetToBeProcessed = context.Assets.Create(containerName,
             AssetCreationOptions.None);
    
         return assetToBeProcessed;
     }
    
     public static void CreateFileInfosForAssetWithRest(CloudMediaContext context, IAsset asset, string mediaServicesAccountNameTarget,
         string mediaServicesAccountKeyTarget)
     {
         string apiServer = "";
         string scope = "";
         string acsBaseAddress = "";
    
         string acsToken = GetAcsBearerToken(mediaServicesAccountNameTarget,
                                 mediaServicesAccountKeyTarget, scope, acsBaseAddress);
    
         if (!string.IsNullOrEmpty(acsToken))
         {
             CreateFileInfos(apiServer, acsToken, asset.Id);
         }
     }
    
     public static string CreateOriginLocatorWithRest(CloudMediaContext context, string mediaServicesAccountNameTarget,
         string mediaServicesAccountKeyTarget, string locatorIdToReplicate, string targetAssetId)
     {
         //Make sure we are not asking for a duplicate:
         var locator = context.Locators.Where(l => l.Id == locatorIdToReplicate).FirstOrDefault();
         if (locator != null)
             return "";
    
         string locatorNewPath = "";
         string apiServer = "";
         string scope = "";
         string acsBaseAddress = "";
    
         string acsToken = GetAcsBearerToken(mediaServicesAccountNameTarget,
                                 mediaServicesAccountKeyTarget, scope, acsBaseAddress);
    
         if (!string.IsNullOrEmpty(acsToken))
         {
             var asset = context.Assets.Where(a => a.Id == targetAssetId).FirstOrDefault();
    
             // You cannot create a streaming locator using an AccessPolicy that includes write or delete permissions.            
             var accessPolicy = context.AccessPolicies.Create("RestTest", TimeSpan.FromDays(100),
                                                                 AccessPermissions.Read);
             if (asset != null)
             {
                 string redirectedServiceUri = null;
    
                 var xmlResponse = CreateLocator(apiServer, out redirectedServiceUri, acsToken,
                                                             asset.Id, accessPolicy.Id,
                                                             (int)LocatorType.OnDemandOrigin,
                                                             DateTime.UtcNow.AddMinutes(-10), locatorIdToReplicate);
    
                 Console.WriteLine("Redirected to: " + redirectedServiceUri);
                 if (xmlResponse != null)
                 {
                     Console.WriteLine(String.Format("Locator Id: {0}",
                                                     xmlResponse.GetElementsByTagName("Id")[0].InnerText));
                     Console.WriteLine(String.Format("Locator Path: {0}",
                             xmlResponse.GetElementsByTagName("Path")[0].InnerText));
    
                     locatorNewPath = xmlResponse.GetElementsByTagName("Path")[0].InnerText;
                 }
             }
         }
    
         return locatorNewPath;
     }
    
     public static void SetPrimaryFile(IAsset asset)
     {
    
         var ismAssetFiles = asset.AssetFiles.ToList().
                     Where(f => f.Name.EndsWith(".ism", StringComparison.OrdinalIgnoreCase))
                     .ToArray();
    
         if (ismAssetFiles.Count() != 1)
             throw new ArgumentException("The asset should have only one, .ism file");
    
         ismAssetFiles.First().IsPrimary = true;
         ismAssetFiles.First().Update();
     }
    
     public static IAssetFile GetPrimaryFile(IAsset asset)
     {
         var theManifest =
                 from f in asset.AssetFiles
                 where f.Name.EndsWith(".ism")
                 select f;
    
         // Cast the reference to a true IAssetFile type. 
         IAssetFile manifestFile = theManifest.First();
    
         return manifestFile;
     }
    
     public static IAsset RefreshAsset(CloudMediaContext context, IAsset asset)
     {
         asset = context.Assets.Where(a => a.Id == asset.Id).FirstOrDefault();
         return asset;
     }
    
     public static void CopyBlobsFromDifferentStorage(string sourceContainerName, string targetContainerName,
                                         string srcAccountName, string srcAccountKey,
                                         string destAccountName, string destAccountKey)
     {
         var srcAccount = new CloudStorageAccount(new StorageCredentials(srcAccountName, srcAccountKey), true);
         var destAccount = new CloudStorageAccount(new StorageCredentials(destAccountName, destAccountKey), true);
    
         var cloudBlobClient = srcAccount.CreateCloudBlobClient();
         var targetBlobClient = destAccount.CreateCloudBlobClient();
    
         var sourceContainer = cloudBlobClient.GetContainerReference(sourceContainerName);
         var targetContainer = targetBlobClient.GetContainerReference(targetContainerName);
         targetContainer.CreateIfNotExists();
    
         string blobToken = sourceContainer.GetSharedAccessSignature(new SharedAccessBlobPolicy()
         {
             // Specify the expiration time for the signature.
             SharedAccessExpiryTime = DateTime.Now.AddDays(1),
             // Specify the permissions granted by the signature.
             Permissions = SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read
         });
    
         foreach (var sourceBlob in sourceContainer.ListBlobs())
         {
             string fileName = (sourceBlob as ICloudBlob).Name;
             var sourceCloudBlob = sourceContainer.GetBlockBlobReference(fileName);
             sourceCloudBlob.FetchAttributes();
    
             if (sourceCloudBlob.Properties.Length > 0)
             {
                 // In Azure Media Services, the files are stored as block blobs. 
                 // Page blobs are not supported by Azure Media Services.  
                 var destinationBlob = targetContainer.GetBlockBlobReference(fileName);
                 destinationBlob.StartCopyFromBlob(new Uri(sourceBlob.Uri.AbsoluteUri + blobToken));
    
                 while (true)
                 {
                     // The StartCopyFromBlob is an async operation, 
                     // so we want to check if the copy operation is completed before proceeding. 
                     // To do that, we call FetchAttributes on the blob and check the CopyStatus. 
                     destinationBlob.FetchAttributes();
                     if (destinationBlob.CopyState.Status != CopyStatus.Pending)
                     {
                         break;
                     }
                     //It's still not completed. So wait for some time.
                     System.Threading.Thread.Sleep(1000);
                 }
             }
    
             Console.WriteLine(fileName);
         }
    
         Console.WriteLine("Done copying.");
     }
    
     private static IMediaProcessor GetLatestMediaProcessorByName(CloudMediaContext context, string mediaProcessorName)
     {
    
         var processor = context.MediaProcessors.Where(p => p.Name == mediaProcessorName).
             ToList().OrderBy(p => new Version(p.Version)).LastOrDefault();
    
         if (processor == null)
             throw new ArgumentException(string.Format("Unknown media processor", mediaProcessorName));
    
         return processor;
     }
    
     // This method is a handler for events that track job progress.   
     private static void StateChanged(object sender, JobStateChangedEventArgs e)
     {
         Console.WriteLine("Job state changed event:");
         Console.WriteLine("  Previous state: " + e.PreviousState);
         Console.WriteLine("  Current state: " + e.CurrentState);
    
         switch (e.CurrentState)
         {
             case JobState.Finished:
                 Console.WriteLine();
                 Console.WriteLine("********************");
                 Console.WriteLine("Job is finished.");
                 Console.WriteLine("Please wait while local tasks or downloads complete...");
                 Console.WriteLine("********************");
                 Console.WriteLine();
                 Console.WriteLine();
                 break;
             case JobState.Canceling:
             case JobState.Queued:
             case JobState.Scheduled:
             case JobState.Processing:
                 Console.WriteLine("Please wait...\n");
                 break;
             case JobState.Canceled:
             case JobState.Error:
                 // Cast sender as a job.
                 IJob job = (IJob)sender;
                 // Display or log error details as needed.
                 LogJobStop(null, job.Id);
                 break;
             default:
                 break;
         }
     }
    
     private static void LogJobStop(CloudMediaContext context, string jobId)
     {
         StringBuilder builder = new StringBuilder();
         IJob job = GetJob(context, jobId);
    
         builder.AppendLine("\nThe job stopped due to cancellation or an error.");
         builder.AppendLine("***************************");
         builder.AppendLine("Job ID: " + job.Id);
         builder.AppendLine("Job Name: " + job.Name);
         builder.AppendLine("Job State: " + job.State.ToString());
         builder.AppendLine("Job started (server UTC time): " + job.StartTime.ToString());
         // Log job errors if they exist.  
         if (job.State == JobState.Error)
         {
             builder.Append("Error Details: \n");
             foreach (ITask task in job.Tasks)
             {
                 foreach (ErrorDetail detail in task.ErrorDetails)
                 {
                     builder.AppendLine("  Task Id: " + task.Id);
                     builder.AppendLine("    Error Code: " + detail.Code);
                     builder.AppendLine("    Error Message: " + detail.Message + "\n");
                 }
             }
         }
         builder.AppendLine("***************************\n");
         // Write the output to a local file and to the console. The template 
         // for an error output file is:  JobStop-{JobId}.txt
         string outputFile = OutputFilesFolder + @"\JobStop-" + JobIdAsFileName(job.Id) + ".txt";
         WriteToFile(outputFile, builder.ToString());
         Console.Write(builder.ToString());
     }
    
     private static void LogJobDetails(CloudMediaContext context, string jobId)
     {
         StringBuilder builder = new StringBuilder();
         IJob job = GetJob(context, jobId);
    
         builder.AppendLine("\nJob ID: " + job.Id);
         builder.AppendLine("Job Name: " + job.Name);
         builder.AppendLine("Job submitted (client UTC time): " + DateTime.UtcNow.ToString());
    
         // Write the output to a local file and to the console. The template 
         // for an error output file is:  JobDetails-{JobId}.txt
         string outputFile = OutputFilesFolder + @"\JobDetails-" + JobIdAsFileName(job.Id) + ".txt";
         WriteToFile(outputFile, builder.ToString());
         Console.Write(builder.ToString());
     }
    
     // Replace ":" with "_" in Job id values so they can 
     // be used as log file names.  
     private static string JobIdAsFileName(string jobID)
     {
         return jobID.Replace(":", "_");
     }
    
     // Write method output to the output files folder.
     private static void WriteToFile(string outFilePath, string fileContent)
     {
         StreamWriter sr = File.CreateText(outFilePath);
         sr.WriteLine(fileContent);
         sr.Close();
     }
    
     private static IJob GetJob(CloudMediaContext context, string jobId)
     {
         // Use a Linq select query to get an updated 
         // reference by Id. 
         var jobInstance =
             from j in context.Jobs
             where j.Id == jobId
             select j;
    
         // Return the job reference as an Ijob. 
         IJob job = jobInstance.FirstOrDefault();
    
         return job;
     }
    
     private static IAsset GetAsset(CloudMediaContext context, string assetId)
     {
         // Use a LINQ Select query to get an asset.
         var assetInstance =
             from a in context.Assets
             where a.Id == assetId
             select a;
    
         // Reference the asset as an IAsset.
         IAsset asset = assetInstance.FirstOrDefault();
    
         return asset;
     }
    
     public static void DeleteAllAssets(CloudMediaContext context)
     {
         // Loop through and delete all assets.
         foreach (IAsset asset in context.Assets)
         {
             DeleteLocatorsForAsset(context, asset);
    
             asset.Delete();
         }
     }
    
     public static void DeleteLocatorsForAsset(CloudMediaContext context, IAsset asset)
     {
         string assetId = asset.Id;
         var locators = from a in context.Locators
                         where a.AssetId == assetId
                         select a;
         foreach (var locator in locators)
         {
             Console.WriteLine("Deleting locator {0} for asset {1}", locator.Path, assetId);
    
             locator.Delete();
         }
     }
    
     public static void DeleteAccessPolicy(CloudMediaContext context, string existingPolicyId)
     {
         // To delete a specific access policy, get a reference to the policy.  
         // based on the policy Id passed to the method.
         var policyInstance =
                 from p in context.AccessPolicies
                 where p.Id == existingPolicyId
                 select p;
    
         IAccessPolicy policy = policyInstance.FirstOrDefault();
    
         policy.Delete();
    
     }
    
     //////////////////////////////////////////////////////
     /// The following methods use REST calls.
     //////////////////////////////////////////////////////
    
     public static string GetAcsBearerToken(string clientId, string clientSecret, string scope, string accessControlServiceUri)
     {
         if (string.IsNullOrEmpty(clientId))
             throw new ArgumentNullException("clientId");
    
         if (string.IsNullOrEmpty(clientSecret))
             throw new ArgumentNullException("clientSecret");
    
         if (string.IsNullOrEmpty(scope))
         {
             scope = "urn:WindowsAzureMediaServices";
         }
         else if (!scope.ToLower().StartsWith("urn:"))
         {
             scope = "urn:" + scope;
         }
    
         if (string.IsNullOrEmpty(accessControlServiceUri))
         {
             accessControlServiceUri = "https://wamsprodglobal001acs.accesscontrol.windows.net/v2/OAuth2-13";
         }
         else if (!accessControlServiceUri.ToLower().EndsWith("/v2/oauth2-13"))
         {
             accessControlServiceUri = accessControlServiceUri.TrimEnd('/') + "/v2/OAuth2-13";
         }
    
         HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(accessControlServiceUri);
         request.Method = "POST";
         request.ContentType = "application/x-www-form-urlencoded";
         request.KeepAlive = true;
         string acsBearerToken = null;
    
         var requestBytes = Encoding.ASCII.GetBytes("grant_type=client_credentials&client_id=" +
             clientId + "&client_secret=" + HttpUtility.UrlEncode(clientSecret) +
             "&scope=" + HttpUtility.UrlEncode(scope));
         request.ContentLength = requestBytes.Length;
    
         var requestStream = request.GetRequestStream();
         requestStream.Write(requestBytes, 0, requestBytes.Length);
         requestStream.Close();
    
         var response = (HttpWebResponse)request.GetResponse();
    
         if (response.StatusCode == HttpStatusCode.OK)
         {
             using (Stream responseStream = response.GetResponseStream())
             {
                 using (StreamReader stream = new StreamReader(responseStream))
                 {
                     string responseString = stream.ReadToEnd();
                     var reader = JsonReaderWriterFactory.CreateJsonReader(Encoding.UTF8.GetBytes(responseString),
                         new XmlDictionaryReaderQuotas());
    
                     while (reader.Read())
                     {
                         if ((reader.Name == "access_token") && (reader.NodeType == XmlNodeType.Element))
                         {
                             if (reader.Read())
                             {
                                 acsBearerToken = reader.Value;
                                 break;
                             }
                         }
                     }
                 }
             }
         }
    
         return acsBearerToken;
     }
    
     public static XmlDocument CreateLocator(string mediaServicesApiServerUri,
                                             out string redirectedMediaServicesApiServerUri,
                                             string acsBearerToken, string assetId,
                                             string accessPolicyId, int locatorType,
                                             DateTime startTime, string locatorIdToReplicate = null,
                                             bool autoRedirect = true)
     {
         if (string.IsNullOrEmpty(mediaServicesApiServerUri))
         {
             mediaServicesApiServerUri = "https://media.windows.net/api/";
         }
         if (!mediaServicesApiServerUri.EndsWith("/"))
             mediaServicesApiServerUri = mediaServicesApiServerUri + "/";
    
         if (string.IsNullOrEmpty(acsBearerToken)) throw new ArgumentNullException("acsBearerToken");
         if (string.IsNullOrEmpty(assetId)) throw new ArgumentNullException("assetId");
         if (string.IsNullOrEmpty(accessPolicyId)) throw new ArgumentNullException("accessPolicyId");
    
         redirectedMediaServicesApiServerUri = null;
         XmlDocument xmlResponse = null;
    
         StringBuilder sb = new StringBuilder();
         sb.Append("{ \"AssetId\" : \"" + assetId + "\"");
         sb.Append(", \"AccessPolicyId\" : \"" + accessPolicyId + "\"");
         sb.Append(", \"Type\" : \"" + locatorType + "\"");
         if (startTime != DateTime.MinValue)
             sb.Append(", \"StartTime\" : \"" + startTime.ToString("G", CultureInfo.CreateSpecificCulture("en-us")) + "\"");
         if (!string.IsNullOrEmpty(locatorIdToReplicate))
             sb.Append(", \"Id\" : \"" + locatorIdToReplicate + "\"");
         sb.Append("}");
    
         string requestbody = sb.ToString();
    
         try
         {
             var request = GenerateRequest("POST", mediaServicesApiServerUri, "Locators",
                 null, acsBearerToken, requestbody);
             var response = (HttpWebResponse)request.GetResponse();
    
             switch (response.StatusCode)
             {
                 case HttpStatusCode.MovedPermanently:
                     //Recurse once with the mediaServicesApiServerUri redirect Location:
                     if (autoRedirect)
                     {
                         redirectedMediaServicesApiServerUri = response.Headers["Location"];
                         string secondRedirection = null;
                         xmlResponse = CreateLocator(redirectedMediaServicesApiServerUri,
                                                     out secondRedirection, acsBearerToken,
                                                     assetId, accessPolicyId, locatorType,
                                                     startTime, locatorIdToReplicate, false);
                     }
                     else
                     {
                         Console.WriteLine("Redirection to {0} failed.",
                             mediaServicesApiServerUri);
                         return null;
                     }
                     break;
                 case HttpStatusCode.Created:
                     using (Stream responseStream = response.GetResponseStream())
                     {
                         using (StreamReader stream = new StreamReader(responseStream))
                         {
                             string responseString = stream.ReadToEnd();
                             var reader = JsonReaderWriterFactory.
                                 CreateJsonReader(Encoding.UTF8.GetBytes(responseString),
                                     new XmlDictionaryReaderQuotas());
    
                             xmlResponse = new XmlDocument();
                             reader.Read();
                             xmlResponse.LoadXml(reader.ReadInnerXml());
                         }
                     }
                     break;
    
                 default:
                     Console.WriteLine(response.StatusDescription);
                     break;
             }
         }
         catch (WebException ex)
         {
             Console.WriteLine(ex.Message);
         }
    
         return xmlResponse;
     }
    
     public static void CreateFileInfos(string mediaServicesApiServerUri,
                                 string acsBearerToken,
                                 string assetId
                                 )
     {
         if (String.IsNullOrEmpty(mediaServicesApiServerUri))
         {
             mediaServicesApiServerUri = "https://media.windows.net/api/";
         }
         if (!mediaServicesApiServerUri.EndsWith("/"))
             mediaServicesApiServerUri = mediaServicesApiServerUri + "/";
    
         if (String.IsNullOrEmpty(acsBearerToken)) throw new ArgumentNullException("acsBearerToken");
         if (String.IsNullOrEmpty(assetId)) throw new ArgumentNullException("assetId");
    
        string id = assetId.Replace(":", "%");

        UriBuilder builder = new UriBuilder(mediaServicesApiServerUri);
        builder.Path = Path.Combine(builder.Path, "CreateFileInfos");
        builder.Query = String.Format(CultureInfo.InvariantCulture, "assetid='{0}'", assetId);

        try
        {
            var request = GenerateRequest("GET", mediaServicesApiServerUri, "CreateFileInfos",
                String.Format(CultureInfo.InvariantCulture, "assetid='{0}'", assetId), acsBearerToken, null);

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                if (response.StatusCode == HttpStatusCode.MovedPermanently)
                {
                    string redirectedMediaServicesApiUrl = response.Headers["Location"];

                    CreateFileInfos(redirectedMediaServicesApiUrl, acsBearerToken, assetId);
                }
                else if ((response.StatusCode != HttpStatusCode.OK) &&
                    (response.StatusCode != HttpStatusCode.Accepted) &&
                    (response.StatusCode != HttpStatusCode.Created) &&
                    (response.StatusCode != HttpStatusCode.NoContent))
                {
                    // TODO: Throw a more specific exception.
                    throw new Exception("Invalid response received ");
                }
            }
        }
        catch (WebException ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    private static HttpWebRequest GenerateRequest(string verb,
                                                    string mediaServicesApiServerUri,
                                                    string resourcePath, string query,
                                                    string acsBearerToken, string requestbody)
    {
        var uriBuilder = new UriBuilder(mediaServicesApiServerUri);
        uriBuilder.Path += resourcePath;
        if (query != null)
        {
            uriBuilder.Query = query;
        }
        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uriBuilder.Uri);
        request.AllowAutoRedirect = false; //We manage our own redirects.
        request.Method = verb;

        if (resourcePath == "$metadata")
            request.MediaType = "application/xml";
        else
        {
            request.ContentType = "application/json;odata=verbose";
            request.Accept = "application/json;odata=verbose";
        }

        request.Headers.Add("DataServiceVersion", "3.0");
        request.Headers.Add("MaxDataServiceVersion", "3.0");
        request.Headers.Add("x-ms-version", "2.1");
        request.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + acsBearerToken);

        if (requestbody != null)
        {
            var requestBytes = Encoding.ASCII.GetBytes(requestbody);
            request.ContentLength = requestBytes.Length;

            var requestStream = request.GetRequestStream();
            requestStream.Write(requestBytes, 0, requestBytes.Length);
            requestStream.Close();
        }
        else
        {
            request.ContentLength = 0;
        }
        return request;
    }

Passaggi successivi

È ora possibile usare uno strumento di gestione del traffico per instradare le richieste tra i due data center e quindi il failover in caso di guasti.

Percorsi di apprendimento di Media Services

I percorsi di apprendimento di Servizi multimediali di Azure sono disponibili qui:

Fornire commenti e suggerimenti

Usare il forum di suggerimenti degli utenti per fornire commenti e suggerimenti su come migliorare Servizi multimediali di Azure. È anche possibile passare direttamente a una delle categorie seguenti: