Skip to content
Permalink
2 contributors

Users who have contributed to this file

@Juliako @johndeu
504 lines (432 sloc) 22.8 KB
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Management.Media;
using Microsoft.Azure.Management.Media.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Rest;
using Microsoft.Rest.Azure.Authentication;
using Microsoft.WindowsAzure.Storage.Blob;
namespace EncodeAndStreamFiles
{
class Program
{
private const string AdaptiveStreamingTransformName = "MyTransformWithAdaptiveStreamingPreset";
private const string OutputFolderName = @"Output";
public static async Task Main(string[] args)
{
ConfigWrapper config = new ConfigWrapper(new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build());
try
{
await RunAsync(config);
}
catch (Exception exception)
{
if (exception.Source.Contains("ActiveDirectory"))
{
Console.Error.WriteLine("TIP: Make sure that you have filled out the appsettings.json file before running this sample.");
}
Console.Error.WriteLine($"{exception.Message}");
ApiErrorException apiException = exception.GetBaseException() as ApiErrorException;
if (apiException != null)
{
Console.Error.WriteLine(
$"ERROR: API call failed with error code '{apiException.Body.Error.Code}' and message '{apiException.Body.Error.Message}'.");
}
}
Console.WriteLine("Press Enter to continue.");
Console.ReadLine();
}
/// <summary>
/// Run the sample async.
/// </summary>
/// <param name="config">The parm is of type ConfigWrapper. This class reads values from local configuration file.</param>
/// <returns></returns>
// <RunAsync>
private static async Task RunAsync(ConfigWrapper config)
{
IAzureMediaServicesClient client = await CreateMediaServicesClientAsync(config);
// 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}";
// 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);
// In this demo code, we will poll for Job status
// 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.
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);
await DownloadOutputAssetAsync(client, config.ResourceGroup, config.AccountName, outputAssetName, OutputFolderName);
StreamingLocator locator = await CreateStreamingLocatorAsync(client, config.ResourceGroup, config.AccountName, outputAsset.Name, locatorName);
IList<string> urls = await GetStreamingUrlsAsync(client, config.ResourceGroup, config.AccountName, locator.Name);
foreach (var url in urls)
{
Console.WriteLine(url);
}
}
Console.WriteLine("Done. Copy and paste one of the Streaming URLs into the Azure Media Player at 'http://aka.ms/azuremediaplayer'.");
}
// </RunAsync>
/// <summary>
/// Create the ServiceClientCredentials object based on the credentials
/// supplied in local configuration file.
/// </summary>
/// <param name="config">The parm is of type ConfigWrapper. This class reads values from local configuration file.</param>
/// <returns></returns>
// <GetCredentialsAsync>
private static async Task<ServiceClientCredentials> GetCredentialsAsync(ConfigWrapper config)
{
// Use ApplicationTokenProvider.LoginSilentWithCertificateAsync or UserTokenProvider.LoginSilentAsync to get a token using service principal with certificate
//// ClientAssertionCertificate
//// ApplicationTokenProvider.LoginSilentWithCertificateAsync
// Use ApplicationTokenProvider.LoginSilentAsync to get a token using a service principal with symetric key
ClientCredential clientCredential = new ClientCredential(config.AadClientId, config.AadSecret);
return await ApplicationTokenProvider.LoginSilentAsync(config.AadTenantId, clientCredential, ActiveDirectoryServiceSettings.Azure);
}
// </GetCredentialsAsync>
/// <summary>
/// Creates the AzureMediaServicesClient object based on the credentials
/// supplied in local configuration file.
/// </summary>
/// <param name="config">The parm is of type ConfigWrapper. This class reads values from local configuration file.</param>
/// <returns></returns>
// <CreateMediaServicesClient>
private static async Task<IAzureMediaServicesClient> CreateMediaServicesClientAsync(ConfigWrapper config)
{
var credentials = await GetCredentialsAsync(config);
return new AzureMediaServicesClient(config.ArmEndpoint, credentials)
{
SubscriptionId = config.SubscriptionId,
};
}
// </CreateMediaServicesClient>
/// <summary>
/// If the specified transform exists, get that transform.
/// If the it does not exist, creates a new transform with the specified output.
/// In this case, the output is set to encode a video using one of the built-in encoding presets.
/// </summary>
/// <param name="client">The Media Services client.</param>
/// <param name="resourceGroupName">The name of the resource group within the Azure subscription.</param>
/// <param name="accountName"> The Media Services account name.</param>
/// <param name="transformName">The name of the transform.</param>
/// <returns></returns>
// <EnsureTransformExists>
private static async Task<Transform> GetOrCreateTransformAsync(
IAzureMediaServicesClient client,
string resourceGroupName,
string accountName,
string transformName)
{
// Does a Transform already exist with the desired name? Assume that an existing Transform with the desired name
// also uses the same recipe or Preset for processing content.
Transform transform = await client.Transforms.GetAsync(resourceGroupName, accountName, transformName);
if (transform == null)
{
// You need to specify what you want it to produce as an output
TransformOutput[] output = new TransformOutput[]
{
new TransformOutput
{
// The preset for the Transform is set to one of Media Services built-in sample presets.
// You can customize the encoding settings by changing this to use "StandardEncoderPreset" class.
Preset = new BuiltInStandardEncoderPreset()
{
// This sample uses the built-in encoding preset for Adaptive Bitrate Streaming.
PresetName = EncoderNamedPreset.AdaptiveStreaming
}
}
};
// Create the Transform with the output defined above
transform = await client.Transforms.CreateOrUpdateAsync(resourceGroupName, accountName, transformName, output);
}
return transform;
}
// </EnsureTransformExists>
/// <summary>
/// Creates an ouput asset. The output from the encoding Job must be written to an Asset.
/// </summary>
/// <param name="client">The Media Services client.</param>
/// <param name="resourceGroupName">The name of the resource group within the Azure subscription.</param>
/// <param name="accountName"> The Media Services account name.</param>
/// <param name="assetName">The output asset name.</param>
/// <returns></returns>
// <CreateOutputAsset>
private static async Task<Asset> CreateOutputAssetAsync(IAzureMediaServicesClient client, string resourceGroupName, string accountName, string assetName)
{
// Check if an Asset already exists
Asset outputAsset = await client.Assets.GetAsync(resourceGroupName, accountName, assetName);
Asset asset = new Asset();
string outputAssetName = assetName;
if (outputAsset != null)
{
// Name collision! In order to get the sample to work, let's just go ahead and create a unique asset name
// Note that the returned Asset can have a different name than the one specified as an input parameter.
// You may want to update this part to throw an Exception instead, and handle name collisions differently.
string uniqueness = $"-{Guid.NewGuid().ToString("N")}";
outputAssetName += uniqueness;
Console.WriteLine("Warning – found an existing Asset with name = " + assetName);
Console.WriteLine("Creating an Asset with this name instead: " + outputAssetName);
}
return await client.Assets.CreateOrUpdateAsync(resourceGroupName, accountName, outputAssetName, asset);
}
// </CreateOutputAsset>
/// <summary>
/// Submits a request to Media Services to apply the specified Transform to a given input video.
/// </summary>
/// <param name="client">The Media Services client.</param>
/// <param name="resourceGroupName">The name of the resource group within the Azure subscription.</param>
/// <param name="accountName"> The Media Services account name.</param>
/// <param name="transformName">The name of the transform.</param>
/// <param name="outputAssetName">The (unique) name of the output asset that will store the result of the encoding job. </param>
/// <param name="jobName">The (unique) name of the job.</param>
/// <returns></returns>
// <SubmitJob>
private static async Task<Job> SubmitJobAsync(IAzureMediaServicesClient client,
string resourceGroup,
string accountName,
string transformName,
string outputAssetName,
string jobName)
{
// This example shows how to encode from any HTTPs source URL - a new feature of the v3 API.
// Change the URL to any accessible HTTPs URL or SAS URL from Azure.
JobInputHttp jobInput =
new JobInputHttp(files: new[] { "https://nimbuscdn-nimbuspm.streaming.mediaservices.windows.net/2b533311-b215-4409-80af-529c3e853622/Ignite-short.mp4" });
JobOutput[] jobOutputs =
{
new JobOutputAsset(outputAssetName),
};
// In this example, we are assuming that the job name is unique.
//
// If you already have a job with the desired name, use the Jobs.Get method
// to get the existing job. In Media Services v3, Get methods on entities returns null
// if the entity doesn't exist (a case-insensitive check on the name).
Job job = await client.Jobs.CreateAsync(
resourceGroup,
accountName,
transformName,
jobName,
new Job
{
Input = jobInput,
Outputs = jobOutputs,
});
return job;
}
// </SubmitJob>
/// <summary>
/// Polls Media Services for the status of the Job.
/// </summary>
/// <param name="client">The Media Services client.</param>
/// <param name="resourceGroupName">The name of the resource group within the Azure subscription.</param>
/// <param name="accountName"> The Media Services account name.</param>
/// <param name="transformName">The name of the transform.</param>
/// <param name="jobName">The name of the job you submitted.</param>
/// <returns></returns>
// <WaitForJobToFinish>
private static async Task<Job> WaitForJobToFinishAsync(IAzureMediaServicesClient client,
string resourceGroupName,
string accountName,
string transformName,
string jobName)
{
const int SleepIntervalMs = 60 * 1000;
Job job = null;
do
{
job = await client.Jobs.GetAsync(resourceGroupName, accountName, transformName, jobName);
Console.WriteLine($"Job is '{job.State}'.");
for (int i = 0; i < job.Outputs.Count; i++)
{
JobOutput output = job.Outputs[i];
Console.Write($"\tJobOutput[{i}] is '{output.State}'.");
if (output.State == JobState.Processing)
{
Console.Write($" Progress: '{output.Progress}'.");
}
Console.WriteLine();
}
if (job.State != JobState.Finished && job.State != JobState.Error && job.State != JobState.Canceled)
{
await Task.Delay(SleepIntervalMs);
}
}
while (job.State != JobState.Finished && job.State != JobState.Error && job.State != JobState.Canceled);
return job;
}
// </WaitForJobToFinish>
/// <summary>
/// Creates a StreamingLocator for the specified asset and with the specified streaming policy name.
/// Once the StreamingLocator is created the output asset is available to clients for playback.
/// </summary>
/// <param name="client">The Media Services client.</param>
/// <param name="resourceGroupName">The name of the resource group within the Azure subscription.</param>
/// <param name="accountName"> The Media Services account name.</param>
/// <param name="assetName">The name of the output asset.</param>
/// <param name="locatorName">The StreamingLocator name (unique in this case).</param>
/// <returns></returns>
// <CreateStreamingLocator>
private static async Task<StreamingLocator> CreateStreamingLocatorAsync(
IAzureMediaServicesClient client,
string resourceGroup,
string accountName,
string assetName,
string locatorName)
{
StreamingLocator locator = await client.StreamingLocators.CreateAsync(
resourceGroup,
accountName,
locatorName,
new StreamingLocator
{
AssetName = assetName,
StreamingPolicyName = PredefinedStreamingPolicy.ClearStreamingOnly
});
return locator;
}
// </CreateStreamingLocator>
/// <summary>
/// Checks if the "default" streaming endpoint is in the running state,
/// if not, starts it.
/// Then, builds the streaming URLs.
/// </summary>
/// <param name="client">The Media Services client.</param>
/// <param name="resourceGroupName">The name of the resource group within the Azure subscription.</param>
/// <param name="accountName"> The Media Services account name.</param>
/// <param name="locatorName">The name of the StreamingLocator that was created.</param>
/// <returns></returns>
// <GetStreamingURLs>
private static async Task<IList<string>> GetStreamingUrlsAsync(
IAzureMediaServicesClient client,
string resourceGroupName,
string accountName,
String locatorName)
{
const string DefaultStreamingEndpointName = "default";
IList<string> streamingUrls = new List<string>();
StreamingEndpoint streamingEndpoint = await client.StreamingEndpoints.GetAsync(resourceGroupName, accountName, DefaultStreamingEndpointName);
if (streamingEndpoint != null)
{
if (streamingEndpoint.ResourceState != StreamingEndpointResourceState.Running)
{
await client.StreamingEndpoints.StartAsync(resourceGroupName, accountName, DefaultStreamingEndpointName);
}
}
ListPathsResponse paths = await client.StreamingLocators.ListPathsAsync(resourceGroupName, accountName, locatorName);
foreach (StreamingPath path in paths.StreamingPaths)
{
UriBuilder uriBuilder = new UriBuilder();
uriBuilder.Scheme = "https";
uriBuilder.Host = streamingEndpoint.HostName;
uriBuilder.Path = path.Paths[0];
streamingUrls.Add(uriBuilder.ToString());
}
return streamingUrls;
}
// </GetStreamingURLs>
/// <summary>
/// Downloads the results from the specified output asset, so you can see what you got.
/// </summary>
/// <param name="client">The Media Services client.</param>
/// <param name="resourceGroupName">The name of the resource group within the Azure subscription.</param>
/// <param name="accountName"> The Media Services account name.</param>
/// <param name="assetName">The output asset.</param>
/// <param name="outputFolderName">The name of the folder into which to download the results.</param>
// <DownloadResults>
private static async Task DownloadOutputAssetAsync(
IAzureMediaServicesClient client,
string resourceGroup,
string accountName,
string assetName,
string outputFolderName)
{
if (!Directory.Exists(outputFolderName))
{
Directory.CreateDirectory(outputFolderName);
}
AssetContainerSas assetContainerSas = await client.Assets.ListContainerSasAsync(
resourceGroup,
accountName,
assetName,
permissions: AssetContainerPermission.Read,
expiryTime: DateTime.UtcNow.AddHours(1).ToUniversalTime());
Uri containerSasUrl = new Uri(assetContainerSas.AssetContainerSasUrls.FirstOrDefault());
CloudBlobContainer container = new CloudBlobContainer(containerSasUrl);
string directory = Path.Combine(outputFolderName, assetName);
Directory.CreateDirectory(directory);
Console.WriteLine($"Downloading output results to '{directory}'...");
BlobContinuationToken continuationToken = null;
IList<Task> downloadTasks = new List<Task>();
do
{
// A non-negative integer value that indicates the maximum number of results to be returned at a time,
// up to the per-operation limit of 5000. If this value is null, the maximum possible number of results
// will be returned, up to 5000.
int? ListBlobsSegmentMaxResult = null;
BlobResultSegment segment = await container.ListBlobsSegmentedAsync(null, true, BlobListingDetails.None, ListBlobsSegmentMaxResult, continuationToken, null, null);
foreach (IListBlobItem blobItem in segment.Results)
{
CloudBlockBlob blob = blobItem as CloudBlockBlob;
if (blob != null)
{
string path = Path.Combine(directory, blob.Name);
downloadTasks.Add(blob.DownloadToFileAsync(path, FileMode.Create));
}
}
continuationToken = segment.ContinuationToken;
}
while (continuationToken != null);
await Task.WhenAll(downloadTasks);
Console.WriteLine("Download complete.");
}
// </DownloadResults>
/// <summary>
/// Deletes the jobs and assets that were created.
/// Generally, you should clean up everything except objects
/// that you are planning to reuse (typically, you will reuse Transforms, and you will persist StreamingLocators).
/// </summary>
/// <param name="client"></param>
/// <param name="resourceGroupName"></param>
/// <param name="accountName"></param>
/// <param name="transformName"></param>
// <CleanUp>
private static async Task CleanUpAsync(
IAzureMediaServicesClient client,
string resourceGroupName,
string accountName,
string transformName)
{
var jobs = await client.Jobs.ListAsync(resourceGroupName, accountName, transformName);
foreach (var job in jobs)
{
await client.Jobs.DeleteAsync(resourceGroupName, accountName, transformName, job.Name);
}
var assets = await client.Assets.ListAsync(resourceGroupName, accountName);
foreach (var asset in assets)
{
await client.Assets.DeleteAsync(resourceGroupName, accountName, asset.Name);
}
}
// </CleanUp>
}
}
You can’t perform that action at this time.