/// <summary> /// Run the sample async. /// </summary> /// <param name="config">The param is of type ConfigWrapper. This class reads values from local configuration file.</param> /// <returns></returns> private static async Task RunAsync(ConfigWrapper config) { IAzureMediaServicesClient client; try { client = await Authentication.CreateMediaServicesClientAsync(config, UseInteractiveAuth); } catch (Exception e) { Console.Error.WriteLine("TIP: Make sure that you have filled out the appsettings.json or .env file before running this sample."); Console.Error.WriteLine($"{e.Message}"); return; } // Set the polling interval for long running operations to 2 seconds. // The default value is 30 seconds for the .NET client SDK client.LongRunningOperationRetryTimeout = 2; // Creating a unique suffix so that we don't have name collisions if you run the sample // multiple times without cleaning up. string uniqueness = Guid.NewGuid().ToString("N"); string jobName = $"job-{uniqueness}"; string locatorName = $"locator-{uniqueness}"; string outputAssetName = $"output-{uniqueness}"; bool stopEndpoint = false; // In this sample, we use Event Grid to listen to the notifications through an Azure Event Hub. // If you do not provide an Event Hub config in the settings, the sample will fall back to polling the job for status. // For production ready code, it is always recommended to use Event Grid instead of polling on the Job status. EventProcessorClient processorClient = null; BlobContainerClient storageClient = null; MediaServicesEventProcessor mediaEventProcessor = null; try { // Ensure that you have the desired encoding Transform. This is really a one time setup operation. Transform transform = await GetOrCreateTransformAsync(client, config.ResourceGroup, config.AccountName, AdaptiveStreamingTransformName); // Output from the encoding Job must be written to an Asset, so let's create one Asset outputAsset = await CreateOutputAssetAsync(client, config.ResourceGroup, config.AccountName, outputAssetName); Job job = await SubmitJobAsync(client, config.ResourceGroup, config.AccountName, AdaptiveStreamingTransformName, outputAsset.Name, jobName); try { // First we will try to process Job events through Event Hub in real-time. If this fails for any reason, // we will fall-back on polling Job status instead. // Please refer README for Event Hub and storage settings. // A storage account is required to process the Event Hub events from the Event Grid subscription in this sample. // Create a new host to process events from an Event Hub. Console.WriteLine("Creating a new client to process events from an Event Hub..."); var credential = new DefaultAzureCredential(); var storageConnectionString = string.Format("DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}", config.StorageAccountName, config.StorageAccountKey); var blobContainerName = config.StorageContainerName; var eventHubsConnectionString = config.EventHubConnectionString; var eventHubName = config.EventHubName; var consumerGroup = config.EventHubConsumerGroup; storageClient = new BlobContainerClient( storageConnectionString, blobContainerName); processorClient = new EventProcessorClient( storageClient, consumerGroup, eventHubsConnectionString, eventHubName); // Create an AutoResetEvent to wait for the job to finish and pass it to EventProcessor so that it can be set when a final state event is received. AutoResetEvent jobWaitingEvent = new(false); // Create a Task list, adding a job waiting task and a timer task. Other tasks can be added too. IList <Task> tasks = new List <Task>(); // Add a task to wait for the job to finish. The AutoResetEvent will be set when a final state is received by EventProcessor. Task jobTask = Task.Run(() => jobWaitingEvent.WaitOne()); tasks.Add(jobTask); // 30 minutes timeout. var cancellationSource = new CancellationTokenSource(); var timeout = Task.Delay(30 * 60 * 1000, cancellationSource.Token); tasks.Add(timeout); mediaEventProcessor = new MediaServicesEventProcessor(jobName, jobWaitingEvent, null); processorClient.ProcessEventAsync += mediaEventProcessor.ProcessEventsAsync; processorClient.ProcessErrorAsync += mediaEventProcessor.ProcessErrorAsync; await processorClient.StartProcessingAsync(cancellationSource.Token); // Wait for tasks. if (await Task.WhenAny(tasks) == jobTask) { // Job finished. Cancel the timer. cancellationSource.Cancel(); // Get the latest status of the job. job = await client.Jobs.GetAsync(config.ResourceGroup, config.AccountName, AdaptiveStreamingTransformName, jobName); } else { // Timeout happened, Something might go wrong with job events. Fall-back on polling instead. jobWaitingEvent.Set(); throw new Exception("Timeout happened."); } } catch (Exception) { Console.WriteLine("Warning: Failed to connect to Event Hub, please refer README for Event Hub and storage settings."); // Polling is not a recommended best practice for production applications because of the latency it introduces. // Overuse of this API may trigger throttling. Developers should instead use Event Grid. Console.WriteLine("Polling job status..."); job = await WaitForJobToFinishAsync(client, config.ResourceGroup, config.AccountName, AdaptiveStreamingTransformName, jobName); } if (job.State == JobState.Finished) { Console.WriteLine("Job finished."); if (!Directory.Exists(OutputFolderName)) { Directory.CreateDirectory(OutputFolderName); } // Generate a new random token signing key to use RNGCryptoServiceProvider rng = new(); rng.GetBytes(TokenSigningKey); //Create the content key policy that configures how the content key is delivered to end clients // via the Key Delivery component of Azure Media Services. ContentKeyPolicy policy = await GetOrCreateContentKeyPolicyAsync(client, config.ResourceGroup, config.AccountName, ContentKeyPolicyName); StreamingLocator locator = await CreateStreamingLocatorAsync(client, config.ResourceGroup, config.AccountName, outputAsset.Name, locatorName, ContentKeyPolicyName); // We are using the ContentKeyIdentifierClaim in the ContentKeyPolicy which means that the token presented // to the Key Delivery Component must have the identifier of the content key in it. Since we didn't specify // a content key when creating the StreamingLocator, the system created a random one for us. In order to // generate our test token we must get the ContentKeyId to put in the ContentKeyIdentifierClaim claim. string keyIdentifier = locator.ContentKeys.First().Id.ToString(); string token = GetTokenAsync(Issuer, Audience, keyIdentifier, TokenSigningKey); StreamingEndpoint streamingEndpoint = await client.StreamingEndpoints.GetAsync(config.ResourceGroup, config.AccountName, MyStreamingEndpointName); if (streamingEndpoint.ResourceState != StreamingEndpointResourceState.Running) { Console.WriteLine("Streaming Endpoint was Stopped, restarting now.."); await client.StreamingEndpoints.StartAsync(config.ResourceGroup, config.AccountName, MyStreamingEndpointName); // Since we started the endpoint, we should stop it in cleanup. stopEndpoint = true; } string dashPath = await GetDASHStreamingUrlAsync(client, config.ResourceGroup, config.AccountName, locator.Name, streamingEndpoint); Console.WriteLine("Copy and paste the following URL in your browser to play back the file in the Azure Media Player."); Console.WriteLine("Note, the player is set to use the AES token and the Bearer token is specified. "); Console.WriteLine(); Console.WriteLine($"https://ampdemo.azureedge.net/?url={dashPath}&aes=true&aestoken={Authentication.TokenType}%3D{token}"); Console.WriteLine(); } Console.WriteLine("When finished press enter to cleanup."); Console.Out.Flush(); Console.ReadLine(); } catch (ErrorResponseException e) { Console.WriteLine("Hit ErrorResponseException"); Console.WriteLine($"\tCode: {e.Body.Error.Code}"); Console.WriteLine($"\tMessage: {e.Body.Error.Message}"); Console.WriteLine(); Console.WriteLine("Exiting, cleanup may be necessary..."); Console.ReadLine(); } finally { Console.WriteLine("Cleaning up..."); await CleanUpAsync(client, config.ResourceGroup, config.AccountName, AdaptiveStreamingTransformName, outputAssetName, jobName, ContentKeyPolicyName, stopEndpoint, MyStreamingEndpointName); if (processorClient != null) { Console.WriteLine("Job final state received, Stopping the event processor..."); await processorClient.StopProcessingAsync(); Console.WriteLine(); // It is encouraged that you unregister your handlers when you have // finished using the Event Processor to ensure proper cleanup. This // is especially important when using lambda expressions or handlers // in any form that may contain closure scopes or hold other references. processorClient.ProcessEventAsync -= mediaEventProcessor.ProcessEventsAsync; processorClient.ProcessErrorAsync -= mediaEventProcessor.ProcessErrorAsync; } } }