If you have developed VOD or Live workflows with Azure Media Services, you might have faced the following error when creating multiple Asset Locators: “Server does not support setting more than 5 shared access policy identifiers on a single container”.
<?xml version="1.0" encoding="utf-8"?>
<m:message xml:lang="en-US">Server does not support setting more than 5 shared access policy identifiers on a single container.</m:message>
To understand the reason behind this error (and how to avoid it), first let me clarify the differences among Azure Storage Stored Access Policies, Media Services Access Policies and Media Services Locators:
- Stored Access Policies. This is an Azure Storage Services REST API feature that provides an additional level of control over shared access signatures (SAS) on the server-side for containers, queues, or tables. This feature has the limitation that you can include up to 5 Stored Access Policies for each container, queue or table.
- Access Policies. This is an Azure Media Services REST API entity that it is just used as a “template” for creating Locators. There is no mapping between a Media Services Access Policy and a Storage Services Stored Access Policy and, therefore, there is no explicit limitation on the number of Access Policies you can create.
- Locators. This is an Azure Media Services REST API entity that provides an entry point to access the files contained in an Asset container. An Access Policy is used to define the permissions and duration that a client has access to a given Asset. When a Locator is created, the Media Services REST API creates a Stored Access Policy in the container associated with the Asset. Therefore, the same Stored Access Policy limitation also applies for Locators: you can create up to 5 Locators for a given Asset.
As you can see, the error is thrown by an Azure Storage Services limitation on the number of Stored Access Policies for a container, and the same limitation is inherited by the number of Media Services Locators for an Asset.
There are different options to avoid getting this error (depending on your scenario):
- Delete the asset locators after you are done using them. For example, if you need to upload a new asset file, you have to create a SAS locator with Write permissions. Once the operation is complete, you no longer need the locator, so it is safe to delete it. Take into account that this approach does not apply to some scenarios: if you want to publish an asset for adaptive streaming (On-Demand Origin locator) or progressive download (SAS locator), the locator must persist; otherwise, deleting the locator will “unpublish” the asset.
- Reuse the locators that are available in the asset. Instead of creating a new locator every time, check if the asset already contains one that matches the type and access policy permissions you need. If you find one, make sure it is not expired (or near expiration) before using it; otherwise, create a new locator.
- Leverage the Azure Media Services Content Protection feature. If you are trying to get granular control over your content by creating a different Locator for each client, there is a better way now: you can dynamically encrypt your content with AES or PlayReady, set a token authorization policy for the content key or license, and make the token expire after a short period of time (long enough for the player to retrieve the content key or license and start the playback). This way, you will be using a single long-lived Locator for all your clients. For more details, you can check this blog post: Announcing public availability of Azure Media Services Content Protection Services.
In this post, I will focus on Option #2 and show you how to implement a helper extension method to let you reuse your Locators and also update the duration if it happens to be expired (or near expiration). Below, you can find a proposed implementation that takes care of this.
Note: This code uses the Windows Azure Media Services .NET SDK Extensions NuGet package.
public static class LocatorCollectionExtensions
public static readonly TimeSpan DefaultExpirationTimeThreshold = TimeSpan.FromMinutes(5);
public static async Task<ILocator> GetOrCreateAsync(this LocatorBaseCollection locators, LocatorType locatorType, IAsset asset, AccessPermissions permissions, TimeSpan duration, DateTime? startTime = null, TimeSpan? expirationThreshold = null)
MediaContextBase context = locators.MediaContext;
ILocator assetLocator = context
.Where(l => (l.AssetId == asset.Id) && (l.Type == locatorType))
.OrderByDescending(l => l.ExpirationDateTime)
.Where(l => (l.AccessPolicy.Permissions & permissions) == permissions)
if (assetLocator == null)
// If there is no locator in the asset matching the type and permissions, then a new locator is created.
assetLocator = await context.Locators.CreateAsync(locatorType, asset, permissions, duration, startTime).ConfigureAwait(false);
else if (assetLocator.ExpirationDateTime <= DateTime.UtcNow.Add(expirationThreshold ?? DefaultExpirationTimeThreshold))
// If there is a locator in the asset matching the type and permissions but it is expired (or near expiration), then the locator is updated.
await assetLocator.UpdateAsync(startTime, DateTime.UtcNow.Add(duration)).ConfigureAwait(false);
public static ILocator GetOrCreate(this LocatorBaseCollection locators, LocatorType locatorType, IAsset asset, AccessPermissions permissions, TimeSpan duration, DateTime? startTime = null, TimeSpan? expirationThreshold = null)
using (Task<ILocator> task = locators.GetOrCreateAsync(locatorType, asset, permissions, duration, startTime, expirationThreshold))
Every time you need a Locator for an Asset, you can use the GetOrCreate method as follows. Of course, if you call the GetOrCreate method multiple times using different parameter combinations (locator type and access policy permissions), you might end up facing the “5 shared access policy” limitation. That’s why it is also important to delete the locators that are not needed as explained in Option #1.
var myContext = new CloudMediaContext("%accountName%", "%accountKey%");
var myAsset = myContext.Assets.Where(a => a.Id == "%assetId%").First();
var myLocator = myContext.Locators.GetOrCreate(LocatorType.Sas, myAsset, AccessPermissions.Read, TimeSpan.FromDays(30));