Tutorial: Upload, encode, and stream videos using APIs

This tutorial shows you how to upload, encode and stream video files with Azure Media Services. You would want to stream your content in Apple's HLS, MPEG DASH, or CMAF formats so it can be played on a wide variety of browsers and devices. Your video needs to be encoded and packaged appropriately before you can stream it.

While the tutorial walks you through the steps to upload a video, you can also encode content that you make accessible to your Media Services account via a HTTPS URL.

Play the video

This tutorial shows you how to:

  • Create a Media Services account
  • Access the Media Services API
  • Configure the sample app
  • Examine the code in detail
  • Run the app
  • Test the streaming URL
  • Clean up resources

If you don't have an Azure subscription, create a free account before you begin.

Prerequisites

If you do not have Visual Studio installed, you can get Visual Studio Community 2017.

Download the sample

Clone a GitHub repository that contains the streaming .NET sample to your machine using the following command:

git clone https://github.com/Azure-Samples/media-services-v3-dotnet-tutorials.git

Open Azure Cloud Shell

Azure Cloud Shell is a free, interactive shell that you can use to run the steps in this article. Common Azure tools are preinstalled and configured in Cloud Shell for you to use with your account. Just select the Copy button to copy the code, paste it in Cloud Shell, and then press Enter to run it. There are a few ways to open Cloud Shell:

Select Try It in the upper-right corner of a code block. Cloud Shell in this article
Open Cloud Shell in your browser. https://shell.azure.com/bash
Select the Cloud Shell button on the menu in the upper-right corner of the Azure portal. Cloud Shell in the portal

Create a Media Services account

You first need to create a Media Services account. This section shows what you need for the acount creation using CLI 2.0.

Create a resource group

Create a resource group using the following command. An Azure resource group is a logical container into which resources like Azure Media Services accounts and the associated Storage accounts are deployed and managed.

az group create --name amsResourceGroup --location westus2

Create a storage account

When creating a Media Services account, you need to supply the name of an Azure Storage account resource. The specified storage account is attached to your Media Services account.

You must have one Primary storage account and you can have any number of Secondary storage accounts associated with your Media Services account. Media Services supports General-purpose v2 (GPv2) or General-purpose v1 (GPv1) accounts. Blob only accounts are not allowed as Primary. If you want to learn more about storage accounts, see Azure Storage account options.

For more information about how storage accounts are used in Media Services, see Storage accounts.

The following command creates a Storage account that is going to be associated with the Media Services account. In the script below, you can substitute storageaccountforams with your value. The account name must have length less than 24.

az storage account create --name storageaccountforams \  
--kind StorageV2 \
--sku Standard_RAGRS \
--resource-group amsResourceGroup

Create a Media Services account

The following Azure CLI command creates a new Media Services account. You can replace the following values: amsaccount storageaccountforams (must match the value you gave for your storage account), and amsResourceGroup (must match the value you gave for the resource group).

az ams account create --name amsaccount --resource-group amsResourceGroup --storage-account storageaccountforams

Access the Media Services API

To connect to Azure Media Services APIs, you use the Azure AD service principal authentication. The following command creates an Azure AD application and attaches a service principal to the account. You should use the returned values to configure your app.

If you are developing in Visual Studio Code or Visual Studio, you would normally add these values to your App.config. If you are using Postman, you might want to create environment variables and set them to the values you got after executing the following script.

Before running the script, you can replace the amsaccount and amsResourceGroup with the names you chose when creating these resources. amsaccount is the name of the Azure Media Services account where to attach the service principal.
The command that follows uses the xml option that returns an xml that you can paste in your app.config. If you omit the xml option, the response will be in json.

az ams account sp create --account-name amsaccount --resource-group amsResourceGroup --xml

This command will produce a response similar to this:

<add key="Region" value="West US 2" />
<add key="ResourceGroup" value="amsResourceGroup" />
<add key="AadEndpoint" value="https://login.microsoftonline.com" />
<add key="AccountName" value="amsaccount" />
<add key="SubscriptionId" value="111111111-0000-2222-3333-55555555555" />
<add key="ArmAadAudience" value="https://management.core.windows.net/" />
<add key="AadTenantId" value="2222222222-0000-2222-3333-6666666666666" />
<add key="AadSecret" value="33333333-0000-2222-3333-55555555555" />
<add key="AadClientId" value="44444444-0000-2222-3333-55555555555" />
<add key="ArmEndpoint" value="https://management.azure.com/" />

Examine the code

This section examines functions defined in the Program.cs file of the UploadEncodeAndStreamFiles project.

The sample performs the following actions:

  1. Creates a new Transform (first, checks if the specified Transform exists).
  2. Creates an output Asset that is used as the encoding Job's output.
  3. Create an input Asset and uploads the specified local video file into it. The Asset is used as the Job's input.
  4. Submits the encoding Job using the input and output that was created.
  5. Checks the Job's status.
  6. Creates a StreamingLocator.
  7. Builds streaming URLs.

Start using Media Services APIs with .NET SDK

To start using Media Services APIs with .NET, you need to create an AzureMediaServicesClient object. To create the object, you need to supply credentials needed for the client to connect to Azure using Azure AD. You first need to get a token and then create a ClientCredential object from the returned token. In the code you cloned at the beginning of the article, the ArmClientCredential object is used to get the token.

private static IAzureMediaServicesClient CreateMediaServicesClient(ConfigWrapper config)
{
    ArmClientCredentials credentials = new ArmClientCredentials(config);

    return new AzureMediaServicesClient(config.ArmEndpoint, credentials)
    {
        SubscriptionId = config.SubscriptionId,
    };
}

Create an input asset and upload a local file into it

The CreateInputAsset function creates a new input Asset and uploads the specified local video file into it. This Asset is used as the input to your encoding Job. In Media Services v3, the input to a Job can either be an Asset, or it can be content that you make available to your Media Services account via HTTPS URLs. If you want to learn how to encode from a HTTPS URL, see this article.

In Media Services v3, you use Azure Storage APIs to upload files. The following .NET snippet shows how.

The following function performs these actions:

  • Creates an Asset
  • Gets a writable SAS URL to the Asset’s container in storage
  • Uploads the file into the container in storage using the SAS URL
private static Asset CreateInputAsset(IAzureMediaServicesClient client, 
    string resourceGroupName, 
    string accountName, 
    string assetName, 
    string fileToUpload)
{
    // In this example, we are assuming that the asset name is unique.
    //
    // If you already have an asset with the desired name, use the Assets.Get method
    // to get the existing asset. In Media Services v3, Get methods on entities returns null 
    // if the entity doesn't exist (a case-insensitive check on the name).

    // Call Media Services API to create an Asset.
    // This method creates a container in storage for the Asset.
    // The files (blobs) associated with the asset will be stored in this container.
    Asset asset = client.Assets.CreateOrUpdate(resourceGroupName, accountName, assetName, new Asset());

    // Use Media Services API to get back a response that contains
    // SAS URL for the Asset container into which to upload blobs.
    // That is where you would specify read-write permissions 
    // and the exparation time for the SAS URL.
    var response = client.Assets.ListContainerSas(
          resourceGroupName,
          accountName,
          assetName,
          permissions: AssetContainerPermission.ReadWrite,
          expiryTime: DateTime.UtcNow.AddHours(4).ToUniversalTime()
      );

    var sasUri = new Uri(response.AssetContainerSasUrls.First());

    // Use Storage API to get a reference to the Asset container
    // that was created by calling Asset's CreateOrUpdate method.  
    CloudBlobContainer container = new CloudBlobContainer(sasUri);
    var blob = container.GetBlockBlobReference(Path.GetFileName(fileToUpload));

    // Use Strorage API to upload the file into the container in storage.
    blob.UploadFromFile(fileToUpload);

    return asset;
}

Create an output asset to store the result of a job

The output Asset stores the result of your encoding job. The project defines the DownloadResults function that downloads the results from this output asset into the "output" folder, so you can see what you got.

private static Asset CreateOutputAsset(IAzureMediaServicesClient client, string resourceGroupName, string accountName, string assetName)
{
    // Check if an Asset already exists
    Asset outputAsset = client.Assets.Get(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();
        outputAssetName += uniqueness;
    }

    return client.Assets.CreateOrUpdate(resourceGroupName, accountName, outputAssetName, asset);
}

Create a Transform and a Job that encodes the uploaded file

When encoding or processing content in Media Services, it is a common pattern to set up the encoding settings as a recipe. You would then submit a Job to apply that recipe to a video. By submitting new Jobs for each new video, you are applying that recipe to all the videos in your library. A recipe in Media Services is called as a Transform. For more information, see Transforms and jobs. The sample described in this tutorial defines a recipe that encodes the video in order to stream it to a variety of iOS and Android devices.

Transform

When creating a new Transform instance, you need to specify what you want it to produce as an output. The required parameter is a TransformOutput object, as shown in the code below. Each TransformOutput contains a Preset. Preset describes the step-by-step instructions of video and/or audio processing operations that are to be used to generate the desired TransformOutput. The sample described in this article uses a built-in Preset called AdaptiveStreaming. The Preset encodes the input video into an auto-generated bitrate ladder (bitrate-resolution pairs) based on the input resolution and bitrate, and produces ISO MP4 files with H.264 video and AAC audio corresponding to each bitrate-resolution pair. For information about this Preset, see auto-generating bitrate ladder.

You can use a built-in EncoderNamedPreset or use custom presets. For more information, see How to customize encoder presets.

When creating a Transform, you should first check if one already exists using the Get method, as shown in the code that follows. In Media Services v3, Get methods on entities return null if the entity doesn’t exist (a case-insensitive check on the name).

private static Transform EnsureTransformExists(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 = client.Transforms.Get(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 = client.Transforms.CreateOrUpdate(resourceGroupName, accountName, transformName, output);
    }

    return transform;
}

Job

As mentioned above, the Transform object is the recipe and a Job is the actual request to Media Services to apply that Transform to a given input video or audio content. The Job specifies information like the location of the input video, and the location for the output.

In this example, the input video has been uploaded from your local machine. If you want to learn how to encode from a HTTPS URL, see this article.

private static Job SubmitJob(IAzureMediaServicesClient client, 
    string resourceGroupName, 
    string accountName, 
    string transformName, 
    string jobName, 
    JobInput jobInput, 
    string outputAssetName)
{
    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 = client.Jobs.Create(
        resourceGroupName,
        accountName,
        transformName,
        jobName,
        new Job
        {
            Input = jobInput,
            Outputs = jobOutputs,
        });

    return job;
}

Wait for the Job to complete

The job takes some time to complete and when it does you want to be notified. The code sample below shows how to poll the service for the status of the Job. Polling is not a recommended best practice for production applications because of potential latency. Polling can be throttled if overused on an account. Developers should instead use Event Grid.

Event Grid is designed for high availability, consistent performance, and dynamic scale. With Event Grid, your apps can listen for and react to events from virtually all Azure services, as well as custom sources. Simple, HTTP-based reactive event handling helps you build efficient solutions through intelligent filtering and routing of events. See Route events to a custom web endpoint.

The Job usually goes through the following states: Scheduled, Queued, Processing, Finished (the final state). If the job has encountered an error, you get the Error state. If the job is in the process of being canceled, you get Canceling and Canceled when it is done.

private static Job WaitForJobToFinish(IAzureMediaServicesClient client,
    string resourceGroupName,
    string accountName,
    string transformName,
    string jobName)
{
    int SleepInterval = 60 * 1000;

    Job job = null;

    while (true)
    {
        job = client.Jobs.Get(resourceGroupName, accountName, transformName, jobName);

        if (job.State == JobState.Finished || job.State == JobState.Error || job.State == JobState.Canceled)
        {
            break;
        }

        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();
        }
        System.Threading.Thread.Sleep(SleepInterval);
    }

    return job;
}

Get a StreamingLocator

After the encoding is complete, the next step is to make the video in the output Asset available to clients for playback. You can accomplish this in two steps: first, create a StreamingLocator, and second, build the streaming URLs that clients can use.

The process of creating a StreamingLocator is called publishing. By default, the StreamingLocator is valid immediately after you make the API calls, and lasts until it is deleted, unless you configure the optional start and end times.

When creating a StreamingLocator, you will need to specify the desired StreamingPolicyName. In this example, you will be streaming in-the-clear (or non-encrypted content) so the predefined clear streaming policy (PredefinedStreamingPolicy.ClearStreamingOnly) is used.

Important

When using a custom StreamingPolicy, you should design a limited set of such policies for your Media Service account, and re-use them for your StreamingLocators whenever the same encryption options and protocols are needed. Your Media Service account has a quota for the number of StreamingPolicy entries. You should not be creating a new StreamingPolicy for each StreamingLocator.

The following code assumes that you are calling the function with a unique locatorName.

private static StreamingLocator CreateStreamingLocator(IAzureMediaServicesClient client,
                                                        string resourceGroup,
                                                        string accountName,
                                                        string assetName,
                                                        string locatorName)
{
    StreamingLocator locator =
        client.StreamingLocators.Create(resourceGroup,
        accountName,
        locatorName,
        new StreamingLocator()
        {
            AssetName = assetName,
            StreamingPolicyName = PredefinedStreamingPolicy.ClearStreamingOnly,
        });

    return locator;
}

While the sample in this topic discusses streaming, you can use the same call to create a StreamingLocator for delivering video via progressive download.

Get streaming URLs

Now that the StreamingLocator has been created, you can get the streaming URLs, as shown in GetStreamingURLs. To build a URL, you need to concatenate the StreamingEndpoint host name and the StreamingLocator path. In this sample, the default StreamingEndpoint is used. When you first create a Media Service account, this default StreamingEndpoint will be in a stopped state, so you need to call Start.

Note

In this method, you need the locatorName that was used when creating the StreamingLocator for the output Asset.

static IList<string> GetStreamingURLs(
    IAzureMediaServicesClient client,
    string resourceGroupName,
    string accountName,
    String locatorName)
{
    IList<string> streamingURLs = new List<string>();

    string streamingUrlPrefx = "";

    StreamingEndpoint streamingEndpoint = client.StreamingEndpoints.Get(resourceGroupName, accountName, "default");

    if (streamingEndpoint != null)
    {
        streamingUrlPrefx = streamingEndpoint.HostName;

        if (streamingEndpoint.ResourceState != StreamingEndpointResourceState.Running)
            client.StreamingEndpoints.Start(resourceGroupName, accountName, "default");
    }

    foreach (var path in client.StreamingLocators.ListPaths(resourceGroupName, accountName, locatorName).StreamingPaths)
    {
        streamingURLs.Add("http://" + streamingUrlPrefx + path.Paths[0].ToString());
    }

    return streamingURLs;
}

Clean up resources in your Media Services account

Generally, you should clean up everything except objects that you are planning to reuse (typically, you will reuse Transforms, and you will persist StreamingLocators, etc.). If you want for your account to be clean after experimenting, you should delete the resources that you do not plan to reuse. For example, the following code deletes Jobs.

static void CleanUp(IAzureMediaServicesClient client,
    string resourceGroupName,
    string accountName,
    string transformName)
{
    foreach (var job in client.Jobs.List(resourceGroupName, accountName, transformName))
    {
        client.Jobs.Delete(resourceGroupName, accountName, transformName, job.Name);
    }

    foreach (var asset in client.Assets.List(resourceGroupName, accountName))
    {
        client.Assets.Delete(resourceGroupName, accountName, asset.Name);
    }
}

Run the sample app

  1. Press Ctrl+F5 to run the EncodeAndStreamFiles application.
  2. Copy one of the streaming URLs from the console.

This example displays URLs that can be used to play back the video using different protocols:

Output

Test the streaming URL

To test the stream, this article uses Azure Media Player.

Note

If a player is hosted on an https site, make sure to update the URL to "https".

  1. Open a web browser and navigate to https://aka.ms/azuremediaplayer/.
  2. In the URL: box, paste one of the streaming URL values you got when you ran the application.
  3. Press Update Player.

Azure Media Player can be used for testing but should not be used in a production environment.

Clean up resources

If you no longer need any of the resources in your resource group, including the Media Services and storage accounts you created for this tutorial, delete the resource group you created earlier. You can use the CloudShell tool.

In the CloudShell, execute the following command:

az group delete --name amsResourceGroup

Multithreading

The Azure Media Services v3 SDKs are not thread-safe. When developing a multi-threaded application, you should generate and use a new AzureMediaServicesClient object per thread.

Next steps

Now that you know how to upload, encode, and stream your video, see the following article: