All posts by Mariano Converti

New Microsoft Azure Media Services SDK’s for Java and PHP release with FairPlay Streaming support

Over the last few days, the Azure SDK team published new releases of the Azure SDK for Java and Azure SDK for PHP packages that contain updates and new features for Microsoft Azure Media Services about Content Protection. In particular, both SDK’s now support Apple FairPlay Streaming (FPS) DRM dynamic encryption configuration and include improvements for Widevine DRM dynamic encryption configuration.

To take advantage of these features in your Java Maven projects, you need to use the latest Azure Media Service Java SDK (v0.9.4) by adding the following azure-media dependency in your pom.xml file.

<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-media</artifactId>
<version>0.9.4</version>
</dependency>

 

For PHP Composer projects, you need to use the latest Azure Media Service PHP SDK (v0.4.4) by adding the following microsoft/windowsazure dependency in the composer.json file – make sure to call require_once(‘vendor/autoload.php’); in your PHP files.

{
  "name": "my/sample",
    "license": "Apache-2.0",
    "require": {
        "microsoft/windowsazure": "^0.4"
    }
}

 

FairPlay Streaming DRM support

With this new release, you can now use the Azure Media Services REST API operations and entities to configure DRM dynamic encryption with Apple FairPlay Streaming (FPS) in both Java and PHP. Below you can find a sample VOD workflow that shows how to enable FairPlay Streaming:

For more details about Apple FairPlay Streaming (FPS) support in Azure Media Services, you can read the official general availability announcement by @mingfeiy: https://azure.microsoft.com/blog/apple-fairplay-streaming-for-azure-media-services-generally-available/.

 

Widevine DRM updates

When you create a Widevine Asset Delivery Policy, you have to specify the license acquisition URL for the common encryption Content Key assigned to the Asset. This value can be obtained by calling the GetKeyDeliveryUrl REST API operation on the Content Key instance. The URL returned by this call, however, contains the Content Key ID as a query string parameter: ?KID=<Guid>. This means that if you use this value “as is” in the configuration, the resulting Widevine Asset Delivery Policy will only be valid for that single Content Key instance.

With the new release, you can now reuse the same Widevine Asset Delivery Policy among multiple Assets. To do this, you just need to remove the query string from the Content Key license acquisition URL and then set it in the Asset Delivery Policy configuration using the new “Widevine Base License Acquisition Url” option. Below you can find some sample code snippets showing how to do this in both Java and PHP.

PHP sample code snippet

// $restProxy: Azure Media Services client context
// $contentKey: a common encrytion content key

$widevineUrl = $restProxy->getKeyDeliveryUrl($contentKey, ContentKeyDeliveryType::WIDEVINE);

// Remove query string
if (strpos($widevineUrl, '?') !== false) {
$widevineUrl = substr($widevineUrl, 0, strrpos($widevineUrl, "?"));
}

// Generate the AssetDeliveryPolicy configuration
$config = [AssetDeliveryPolicyConfigurationKey::WIDEVINE_BASE_LICENSE_ACQUISITION_URL => $widevineUrl];
$configuration = AssetDeliveryPolicyConfigurationKey::stringifyAssetDeliveryPolicyConfiguartionKey($config);

// Create a reusable AssetDeliveryPolicy for Widevine
$adpolicy = new AssetDeliveryPolicy();
$adpolicy->setName('Reusable Widevine Delivery Policy');
$adpolicy->setAssetDeliveryConfiguration($configuration);
$adpolicy->setAssetDeliveryProtocol(AssetDeliveryProtocol::DASH);
$adpolicy->setAssetDeliveryPolicyType(AssetDeliveryPolicyType::DYNAMIC_COMMON_ENCRYPTION);
$adpolicy = $restProxy->createAssetDeliveryPolicy($adpolicy);

Java sample code snippet

// mediaService: Azure Media Services client context
// contentKey: a common encrytion content key

String widevineUrl = mediaService.create(ContentKey.getKeyDeliveryUrl(contentKey.getId(), ContentKeyDeliveryType.Widevine));

// Remove query string
if (widevineUrl.contains("?")) {
widevineUrl = widevineUrl.substring(0, widevineUrl.indexOf("?"));
}


// Generate the AssetDeliveryPolicy configuration
Map<AssetDeliveryPolicyConfigurationKey, String> configuration = new HashMap<AssetDeliveryPolicyConfigurationKey, String>();
configuration.put(AssetDeliveryPolicyConfigurationKey.WidevineBaseLicenseAcquisitionUrl, widevineUrl);

AssetDeliveryPolicyInfo assetDeliveryPolicy = mediaService.create(AssetDeliveryPolicy.create()
.setName("Reusable Widevine Delivery Policy")
.setAssetDeliveryConfiguration(configuration)
.setAssetDeliveryPolicyType(AssetDeliveryPolicyType.DynamicCommonEncryption)
.setAssetDeliveryProtocol(EnumSet.of(AssetDeliveryProtocol.Dash)));

 

Enjoy!

Azure Media Services updates in new Azure portal

Azure Portal

Last month, the Azure Media Services team announced the availability of Azure Media Services functionalities through portal.azure.com (codename Ibiza) in public preview; you can check all the details in this blog post by @mingfeiy. After this announcement, there were some updates to the new portal with more features, enhancements and several bug fixes.

In this post you will find a quick summary of what is new and what has changed in the new portal for Azure Media Services.

 

Add FairPlay DRM protection onto both VOD and live stream

You can now configure the default content key authorization policy for FairPlay DRM from the Content Protection blade by providing the App Certificate (.pfx file containing the private key along with its password) and the Application Secret Key (ASK) received when you generate the certification using Apple Developer portal.

Content Protection blades for FairPlay DRM

After configuring the default content key authorization policy for FairPlay DRM, two new encryption options are going to be available for both VOD and live stream assets.

  • PlayReady and Widevine with MPEG-DASH + FairPlay with HLS
  • FairPlay only with HLS

Adding FairPlay delivery policy in a VOD asset

Adding FairPlay delivery policy in a live stream asset

For more details about FairPlay Streaming (FPS) support, you can check this blog post: Stream premium content to Apple TV with Azure Media Services.

 

Enable/Disable CDN in Streaming Endpoint

After creating a streaming endpoint, you can now enable/disable CDN feature from the Streaming Endpoint Details blade. Remember that, in order to enable CDN feature, the streaming endpoint must be in Stopped state and have at least one streaming unit.

Streaming Endpoint Details blade

 

Improve delete Channel experience

In order to delete a channel, the Azure Media Services REST API validates that:

  • The channel is in Stopped state 
  • The channel does not contain any programs

The original implementation of the Delete command was only enabled in channels satisfying both conditions. To make it easier for Azure portal users, it is now always enabled and takes care of performing these operations (if necessary):

  • Stop all the programs in the channel
  • Delete all the programs in the channel
  • Stop the channel
  • Delete the channel

Channel blade

 

Show ‘Account ID’ property in the Summary and Properties blades

The Media Services ‘Account ID’ is now available in both the Summary and Properties blades. This value is useful, for example, when you want to submit a support request through the Azure portal and univocally identify your account.

Summary and Properties blades

 

Bug Fixes

  • Create Media Services Account blade: Account Name availability validation fails when the user has a disabled subscriptions
  • Create Media Services Account blade: Location dropdown is empty for some subscriptions
  • Asset blade: Encrypt command does not get automatically enabled when the asset type changes after an encoding job finishes
  • Publish the Asset blade: Notification error message when scaling streaming endpoint while creating a streaming locator
  • Asset Media Analytics blade: Remove frame limit for Azure Media Hyperlapse Media Processor
  • Create a new Channel wizard (Custom Create): Channel creation fails when ingest streaming protocol is set to RTP/MPEG-2
  • Create a new Channel wizard (Custom Create): Wrong ingest streaming protocol set when creating multiple channels
  • Create a Live Event blade: Binding issue in Archive Window field
  • Streaming Endpoint Details blade: Streaming units max limit is 5 but it should be 10
  • Streaming Endpoint Settings blade: Setting an entry in the Streaming Allowed IP Addresses table breaks media streaming
  • Media Services blades fail to load in Safari for Mac
  • Delete operations do not work after performing an update on the same entity

 

Enjoy!

Microsoft Azure Media Services SDK for Java v0.9.1 released with support for Widevine dynamic encryption

microsoft-azure-java-sdk-for-media-services

Last Friday, the Azure SDK team published a new release of the Azure SDK for Java Maven packages; you can find the full list at http://search.maven.org/#search|ga|1|com.microsoft.azure. In particular, there were was a minor new release (v0.9.1) of the Azure Media Services SDK for Java that adds support for Widevine (DRM) Dynamic Common Encryption and License Delivery Service; below I’m listing the change log.

If you want to use the Java SDK in your Maven project, you just need to add the “azure-media” dependency in your pom.xml file as follows:

<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-media</artifactId>
<version>0.9.1</version>
</dependency>

 

To demonstrate the new Java SDK features, I created the azure-media-dynamic-encryption-playready-widevine sample console Java application that contains a VOD end-to-end workflow that uses PlayReady and Widevine (DRM) Dynamic Common Encryption and the License Delivery Service for playback. It is based on the .NET sample explained in this documentation article: https://azure.microsoft.com/documentation/articles/media-services-protect-with-drm/.

You can access the full source code of this sample at: https://github.com/southworkscom/azure-sdk-for-media-services-java-samples/tree/master/azure-media-dynamic-encryption-playready-widevine.

Media Services SDK for Java sample projects in Eclipse

 

v0.9.1 Change Log

 

Enjoy!

New Microsoft Azure Media Services SDK for PHP release available with New features and samples

Azure Media Services SDK for PHP

Last week the Azure SDK team published a new release of the Azure SDK for PHP package that contains updates and new features for Microsoft Azure Media Services. In particular, the Azure Media Services SDK for PHP now supports the latest Content Protection features (AES and DRM – both PlayReady and Widevine – dynamic encryption with and without Token restriction), and listing/scaling Encoding Units. This release also includes three new PHP samples that show how to use these new features; below you can find the full change log with all the details about these updates.

In this post, I’ll focus on explaining how to use one of these new features: implement a VOD workflow that applies PlayReady and Widevine (DRM systems) with Dynamic Common Encryption (CENC) using Token restriction for the license.

  1. Make sure you have PEAR and Composer properly installed and configured (php.ini) in your local development box.
  2. Add the following dependencies in the composer.json file in the root of your project.
    "repositories": [
    {
    "type": "pear",
    "url": "http://pear.php.net",
    "vendor-alias": "pear-pear2.php.net"
    }
    ],
    "require": {
    "pear-pear.php.net/HTTP_Request2": "0.4.0",
    "pear-pear.php.net/mail_mime": "*",
    "pear-pear.php.net/mail_mimedecode": "*",
    "firebase/php-jwt": "^3.0",
    "microsoft/windowsazure": "dev-master"
    }

  3. In your index.php main file include the autoload.php file generated by Composer to load all the dependencies, and add the use statements for the required namespaces.
    require_once 'vendor/autoload.php';

    use WindowsAzure\Common\ServicesBuilder;
    use WindowsAzure\Common\Internal\MediaServicesSettings;
    use WindowsAzure\Common\Internal\Utilities;
    use WindowsAzure\MediaServices\Models\Asset;
    use WindowsAzure\MediaServices\Models\AccessPolicy;
    use WindowsAzure\MediaServices\Models\Locator;
    use WindowsAzure\MediaServices\Models\Task;
    use WindowsAzure\MediaServices\Models\Job;
    use WindowsAzure\MediaServices\Models\TaskOptions;
    use WindowsAzure\MediaServices\Models\ContentKey;
    use WindowsAzure\MediaServices\Models\ProtectionKeyTypes;
    use WindowsAzure\MediaServices\Models\ContentKeyTypes;
    use WindowsAzure\MediaServices\Models\ContentKeyAuthorizationPolicy;
    use WindowsAzure\MediaServices\Models\ContentKeyAuthorizationPolicyOption;
    use WindowsAzure\MediaServices\Models\ContentKeyAuthorizationPolicyRestriction;
    use WindowsAzure\MediaServices\Models\ContentKeyDeliveryType;
    use WindowsAzure\MediaServices\Models\ContentKeyRestrictionType;
    use WindowsAzure\MediaServices\Models\AssetDeliveryPolicy;
    use WindowsAzure\MediaServices\Models\AssetDeliveryProtocol;
    use WindowsAzure\MediaServices\Models\AssetDeliveryPolicyType;
    use WindowsAzure\MediaServices\Models\AssetDeliveryPolicyConfigurationKey;
    use WindowsAzure\MediaServices\Templates\PlayReadyLicenseResponseTemplate;
    use WindowsAzure\MediaServices\Templates\PlayReadyLicenseTemplate;
    use WindowsAzure\MediaServices\Templates\PlayReadyLicenseType;
    use WindowsAzure\MediaServices\Templates\MediaServicesLicenseTemplateSerializer;
    use WindowsAzure\MediaServices\Templates\WidevineMessage;
    use WindowsAzure\MediaServices\Templates\AllowedTrackTypes;
    use WindowsAzure\MediaServices\Templates\ContentKeySpecs;
    use WindowsAzure\MediaServices\Templates\RequiredOutputProtection;
    use WindowsAzure\MediaServices\Templates\Hdcp;
    use WindowsAzure\MediaServices\Templates\TokenRestrictionTemplateSerializer;
    use WindowsAzure\MediaServices\Templates\TokenRestrictionTemplate;
    use WindowsAzure\MediaServices\Templates\SymmetricVerificationKey;
    use WindowsAzure\MediaServices\Templates\TokenClaim;
    use WindowsAzure\MediaServices\Templates\TokenType;
    use WindowsAzure\MediaServices\Templates\WidevineMessageSerializer;

  4. Create a rest proxy instance for the Azure Media Services REST API.
    // Replace the placeholders with your Media Services credentials
    $restProxy = ServicesBuilder::getInstance()->createMediaServicesService(new MediaServicesSettings("%account-name%", "%account-key%"));

  5. Create a new asset using your mezzanine source file.
    // Replace the placeholder with your mezzanine file name and path
    $sourceAsset = uploadFileAndCreateAsset($restProxy, "%source-mezzanine-file.mp4%");

    function uploadFileAndCreateAsset($restProxy, $mezzanineFileName) {
    // Create an empty "Asset" by specifying the name
    $asset = new Asset(Asset::OPTIONS_NONE);
    $asset->setName("Mezzanine " . $mezzanineFileName);
    $asset = $restProxy->createAsset($asset);
    $assetId = $asset->getId();

    print "Asset created: name=" . $asset->getName() . " id=" . $assetId . "\r\n";

    // Create an Access Policy with Write permissions
    $accessPolicy = new AccessPolicy('UploadAccessPolicy');
    $accessPolicy->setDurationInMinutes(60.0);
    $accessPolicy->setPermissions(AccessPolicy::PERMISSIONS_WRITE);
    $accessPolicy = $restProxy->createAccessPolicy($accessPolicy);

    // Create a SAS Locator for the Asset
    $sasLocator = new Locator($asset, $accessPolicy, Locator::TYPE_SAS);
    $sasLocator->setStartTime(new \DateTime('now -5 minutes'));
    $sasLocator = $restProxy->createLocator($sasLocator);

    // Get the mezzanine file content
    $fileContent = file_get_contents($mezzanineFileName);

    print "Uploading...\r\n";

    // Perform a multi-part upload using the Block Blobs REST API storage operations
    $restProxy->uploadAssetFile($sasLocator, $mezzanineFileName, $fileContent);

    // Generate the asset files metadata
    $restProxy->createFileInfos($asset);

    print "File uploaded: size=" . strlen($fileContent) . "\r\n";

    // Delete the SAS Locator (and Access Policy) for the Asset
    $restProxy->deleteLocator($sasLocator);
    $restProxy->deleteAccessPolicy($accessPolicy);
    return $asset;
    }

  6. Submit a transcoding job for the source asset to generate a multi-bitrate output asset suitable for adaptive streaming.
    $encodedAsset = encodeToAdaptiveBitrateMP4Set($restProxy, $sourceAsset);

    function encodeToAdaptiveBitrateMP4Set($restProxy, $asset) {
    // Retrieve the latest 'Media Encoder Standard' processor version
    $mediaProcessor = $restProxy->getLatestMediaProcessor('Media Encoder Standard');

    print "Using Media Processor: {$mediaProcessor->getName()} version {$mediaProcessor->getVersion()}\r\n";

    // Create the Job; this automatically schedules and runs it
    $outputAssetName = "Encoded " . $asset->getName();
    $outputAssetCreationOption = Asset::OPTIONS_NONE;
    $taskBody = '<?xml version="1.0" encoding="utf-8"?><taskBody><inputAsset>JobInputAsset(0)</inputAsset><outputAsset assetCreationOptions="' . $outputAssetCreationOption . '" assetName="' . $outputAssetName . '">JobOutputAsset(0)</outputAsset></taskBody>';

    $task = new Task($taskBody, $mediaProcessor->getId(), TaskOptions::NONE);
    $task->setConfiguration('H264 Multiple Bitrate 720p');

    $job = new Job();
    $job->setName('Encoding Job');

    $job = $restProxy->createJob($job, array($asset), array($task));

    print "Created Job with Id: {$job->getId()}\r\n";

    // Check to see if the Job has completed
    $result = $restProxy->getJobStatus($job);

    $jobStatusMap = array('Queued', 'Scheduled', 'Processing', 'Finished', 'Error', 'Canceled', 'Canceling');

    while($result != Job::STATE_FINISHED && $result != Job::STATE_ERROR && $result != Job::STATE_CANCELED) {
    print "Job status: {$jobStatusMap[$result]}\r\n";
    sleep(5);
    $result = $restProxy->getJobStatus($job);
    }

    if ($result != Job::STATE_FINISHED) {
    print "The job has finished with a wrong status: {$jobStatusMap[$result]}\r\n";
    exit(-1);
    }

    print "Job Finished!\r\n";

    // Get output asset
    $outputAssets = $restProxy->getJobOutputMediaAssets($job);
    $encodedAsset = $outputAssets[0];

    print "Asset encoded: name={$encodedAsset->getName()} id={$encodedAsset->getId()}\r\n";

    return $encodedAsset;
    }

  7. Create a new Common Encryption content key and linked it to the multi-bitrate output asset.
    $contentKey = createCommonTypeContentKey($restProxy, $encodedAsset);

    function createCommonTypeContentKey($restProxy, $encodedAsset) {
    // Generate a new content key
    $keyValue = Utilities::generateCryptoKey(16);

    // Get the protection key id for content key
    $protectionKeyId = $restProxy->getProtectionKeyId(ContentKeyTypes::COMMON_ENCRYPTION);
    $protectionKey = $restProxy->getProtectionKey($protectionKeyId);

    $contentKey = new ContentKey();
    $contentKey->setContentKey($keyValue, $protectionKey);
    $contentKey->setProtectionKeyId($protectionKeyId);
    $contentKey->setProtectionKeyType(ProtectionKeyTypes::X509_CERTIFICATE_THUMBPRINT);
    $contentKey->setContentKeyType(ContentKeyTypes::COMMON_ENCRYPTION);

    // 3.3 Create the ContentKey
    $contentKey = $restProxy->createContentKey($contentKey);

    print "Content Key id={$contentKey->getId()}\r\n";

    // Associate the content key with the asset
    $restProxy->linkContentKeyToAsset($encodedAsset, $contentKey);

    return $contentKey;
    }

  8. Create a new content key authorization policy with PlayReady and Widevine options using Token restriction, and linked it to the content key.
    // You can also use TokenType::SWT 
    $tokenTemplateString = addTokenRestrictedAuthorizationPolicy($restProxy, $contentKey, TokenType::JWT);

    function addTokenRestrictedAuthorizationPolicy($restProxy, $contentKey, $tokenType) {
    // Create content key authorization policy restriction (Token)
    $tokenRestriction = generateTokenRequirements($tokenType);
    $restriction = new ContentKeyAuthorizationPolicyRestriction();
    $restriction->setName('Content Key Authorization Policy Restriction');
    $restriction->setKeyRestrictionType(ContentKeyRestrictionType::TOKEN_RESTRICTED);
    $restriction->setRequirements($tokenRestriction);

    // Configure PlayReady and Widevine license templates.
    $playReadyLicenseTemplate = configurePlayReadyLicenseTemplate();
    $widevineLicenseTemplate = configureWidevineLicenseTemplate();

    // Create content key authorization policy option (PlayReady)
    $playReadyOption = new ContentKeyAuthorizationPolicyOption();
    $playReadyOption->setName('PlayReady Authorization Policy Option');
    $playReadyOption->setKeyDeliveryType(ContentKeyDeliveryType::PLAYREADY_LICENSE);
    $playReadyOption->setRestrictions(array($restriction));
    $playReadyOption->setKeyDeliveryConfiguration($playReadyLicenseTemplate);
    $playReadyOption = $restProxy->createContentKeyAuthorizationPolicyOption($playReadyOption);

    // Create content key authorization policy option (Widevine)
    $widevineOption = new ContentKeyAuthorizationPolicyOption();
    $widevineOption->setName('Widevine Authorization Policy Option');
    $widevineOption->setKeyDeliveryType(ContentKeyDeliveryType::WIDEVINE);
    $widevineOption->setRestrictions(array($restriction));
    $widevineOption->setKeyDeliveryConfiguration($widevineLicenseTemplate);
    $widevineOption = $restProxy->createContentKeyAuthorizationPolicyOption($widevineOption);

    // Create content key authorization policy
    $ckapolicy = new ContentKeyAuthorizationPolicy();
    $ckapolicy->setName('Content Key Authorization Policy');
    $ckapolicy = $restProxy->createContentKeyAuthorizationPolicy($ckapolicy);

    // Link the PlayReady and Widevine options to the content key authorization policy
    $restProxy->linkOptionToContentKeyAuthorizationPolicy($playReadyOption, $ckapolicy);
    $restProxy->linkOptionToContentKeyAuthorizationPolicy($widevineOption, $ckapolicy);

    // Associate the authorization policy with the content key
    $contentKey->setAuthorizationPolicyId($ckapolicy->getId());
    $restProxy->updateContentKey($contentKey);

    print "Added Content Key Authorization Policy: name={$ckapolicy->getName()} id={$ckapolicy->getId()}\r\n";
    return $tokenRestriction;
    }

    function generateTokenRequirements($tokenType) {
    $template = new TokenRestrictionTemplate($tokenType);

    $template->setPrimaryVerificationKey(new SymmetricVerificationKey());
    $template->setAudience("urn:contoso");
    $template->setIssuer("https://sts.contoso.com");
    $claims = array();
    $claims[] = new TokenClaim(TokenClaim::CONTENT_KEY_ID_CLAIM_TYPE);
    $template->setRequiredClaims($claims);

    return TokenRestrictionTemplateSerializer::serialize($template);
    }

    function configurePlayReadyLicenseTemplate() {
    $responseTemplate = new PlayReadyLicenseResponseTemplate();

    $licenseTemplate = new PlayReadyLicenseTemplate();
    $licenseTemplate->setLicenseType(PlayReadyLicenseType::NON_PERSISTENT);
    $licenseTemplate->setAllowTestDevices(true);
    $responseTemplate->setLicenseTemplates(array($licenseTemplate));

    return MediaServicesLicenseTemplateSerializer::serialize($responseTemplate);
    }

    function configureWidevineLicenseTemplate() {
    $template = new WidevineMessage();
    $template->allowed_track_types = AllowedTrackTypes::SD_HD;

    $contentKeySpecs = new ContentKeySpecs();
    $contentKeySpecs->required_output_protection = new RequiredOutputProtection();
    $contentKeySpecs->required_output_protection->hdcp = Hdcp::HDCP_NONE;
    $contentKeySpecs->security_level = 1;
    $contentKeySpecs->track_type = "SD";
    $template->content_key_specs = array($contentKeySpecs);

    $policyOverrides = new \stdClass();
    $policyOverrides->can_play = true;
    $policyOverrides->can_persist = true;
    $policyOverrides->can_renew = false;
    $template->policy_overrides = $policyOverrides;

    return WidevineMessageSerializer::serialize($template);
    }

  9. Create a new asset delivery policy for PlayReady and Widevine dynamic common encryption for the MPEG-DASH streaming protocol, and linked it to the multi-bitrate output asset.
    createAssetDeliveryPolicy($restProxy, $encodedAsset, $contentKey);

    function createAssetDeliveryPolicy($restProxy, $encodedAsset, $contentKey) {
    // Get the license acquisition URLs
    $playReadyUrl = $restProxy->getKeyDeliveryUrl($contentKey, ContentKeyDeliveryType::PLAYREADY_LICENSE);
    $widevineURl = $restProxy->getKeyDeliveryUrl($contentKey, ContentKeyDeliveryType::WIDEVINE);

    // Generate the asset delivery policy configuration
    $configuration = [AssetDeliveryPolicyConfigurationKey::PLAYREADY_LICENSE_ACQUISITION_URL => $playReadyUrl,
    AssetDeliveryPolicyConfigurationKey::WIDEVINE_LICENSE_ACQUISITION_URL => $widevineURl];
    $confJson = AssetDeliveryPolicyConfigurationKey::stringifyAssetDeliveryPolicyConfiguartionKey($configuration);

    // Create the asset delivery policy
    $adpolicy = new AssetDeliveryPolicy();
    $adpolicy->setName('Asset Delivery Policy');
    $adpolicy->setAssetDeliveryProtocol(AssetDeliveryProtocol::DASH);
    $adpolicy->setAssetDeliveryPolicyType(AssetDeliveryPolicyType::DYNAMIC_COMMON_ENCRYPTION);
    $adpolicy->setAssetDeliveryConfiguration($confJson);

    $adpolicy = $restProxy->createAssetDeliveryPolicy($adpolicy);

    // Link the delivery policy to the asset
    $restProxy->linkDeliveryPolicyToAsset($encodedAsset, $adpolicy->getId());

    print "Added Asset Delivery Policy: name={$adpolicy->getName()} id={$adpolicy->getId()}\r\n";
    }

  10. Publish the multi-bitrate output asset with an origin locator to generate the base streaming URL.
    publishEncodedAsset($restProxy, $encodedAsset);

    function publishEncodedAsset($restProxy, $encodedAsset) {
    // Get the .ISM asset file
    $files = $restProxy->getAssetAssetFileList($encodedAsset);
    $manifestFile = null;

    foreach($files as $file) {
    if (endsWith(strtolower($file->getName()), '.ism')) {
    $manifestFile = $file;
    }
    }

    if ($manifestFile == null) {
    print "Unable to found the manifest file\r\n";
    exit(-1);
    }

    // Create a 30-day read-only access policy
    $access = new AccessPolicy("Streaming Access Policy");
    $access->setDurationInMinutes(60 * 24 * 30);
    $access->setPermissions(AccessPolicy::PERMISSIONS_READ);
    $access = $restProxy->createAccessPolicy($access);

    // Create an origin locator for the asset
    $locator = new Locator($encodedAsset, $access, Locator::TYPE_ON_DEMAND_ORIGIN);
    $locator->setName("Streaming Locator");
    $locator = $restProxy->createLocator($locator);

    // Create the base streaming URL for dynamic packaging
    $stremingUrl = $locator->getPath() . $manifestFile->getName() . "/manifest";

    print "Base Streaming URL: {$stremingUrl}\r\n";
    }

    function endsWith($haystack, $needle) {
    $length = strlen($needle);
    if ($length == 0) {
    return true;
    }

    return (substr($haystack, -$length) === $needle);
    }

  11. Generate a test Token to retrieve the PlayReady/Widevine license and enable playback in Azure Media Player.
    generateTestToken($tokenTemplateString, $contentKey);

    function generateTestToken($tokenTemplateString, $contentKey) {
    $template = TokenRestrictionTemplateSerializer::deserialize($tokenTemplateString);
    $contentKeyUUID = substr($contentKey->getId(), strlen("nb:kid:UUID:"));
    $expiration = strtotime("+12 hour");
    $token = TokenRestrictionTemplateSerializer::generateTestToken($template, null, $contentKeyUUID, $expiration);

    print "Token Type {$template->getTokenType()}\r\nBearer={$token}\r\n";
    }

  12. Run the code using the following PHP command and make sure to copy the Base Streaming URL and Token values displayed in the console.
    php -d display_errors=1 index.php

  13. Try the Base Streaming URL and Token values in the Azure Media Player demo site: http://amsplayer.azurewebsites.net/. Make sure to use the Advanced Options form to the set Protection value to DRM (PlayReady and Widevine) and paste the token.

 

For more coding details about enabling PlayReady and Widevine dynamic common encryption, you can check the vodworkflow_drm_playready_widevine.php sample.

 

Change Log

 

Enjoy!

Microsoft Azure Media Services SDK for Java v0.8.0 released and new samples available

Azure Media Services SDK for Java

This week the Azure SDK team published new releases of the Azure SDK for Java packages that contain updates and support for more Microsoft Azure Platform Services features. You can find the full list of packages at http://search.maven.org/#search|ga|1|g:”com.microsoft.azure”.

In particular, there was a new release (v.0.8.0) of the Azure Media Services SDK for Java that contains lots of new features such as Content Protection (Dynamic Encryption) and support for more entities/operations (StreamingEndpoint, EncodingReservedUnitType, StorageAccount, etc.). Below you can find the full change log for this release.

Here at Southworks I’ve been working with Emanuel Vecchio on preparing some Java console sample applications showing how to use the new features recently added to the Azure Media Services SDK for Java. As a result, we created the azure-sdk-for-media-services-java-samples GitHub repository containing the following samples:

Media Services SDK for Java sample projects in Eclipse

 

v0.8.0 Change Log

 

Enjoy!

Reusing Azure Media Services Locators to avoid facing the "5 Shared Access Policy" limitation

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:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"> <m:code /> <m:message xml:lang="en-US">Server does not support setting more than 5 shared access policy identifiers on a single container.</m:message> </m:error>

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):

  1. 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.
  2. 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.
  3. 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.

namespace Microsoft.WindowsAzure.MediaServices.Client { using System; using System.Linq; using System.Threading.Tasks; 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 .Locators .Where(l => (l.AssetId == asset.Id) && (l.Type == locatorType)) .OrderByDescending(l => l.ExpirationDateTime) .ToList() .Where(l => (l.AccessPolicy.Permissions & permissions) == permissions) .FirstOrDefault(); 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); } return assetLocator; } 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)) { return task.Result; } } } }

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));

 

Enjoy!

[Spanish] Construyendo aplicaciones Media con Microsoft Azure Media Services @ Global Azure Bootcamp 2015 Buenos Aires, Argentina

For those who don’t read Spanish, this blog post provides details about a Spanish speaking session at the Global Azure Bootcamp 2015 Buenos Aires, Argentina.


Global Azure Bootcamp 2015 Buenos Aires, Argentina

El sábado pasado junto con Mariano Vazquez presentamos Microsoft Azure Media Services en el Global Azure Bootcamp 2015 Buenos Aires, Argentina. La charla duró aproximadamente 90 minutos y cubrimos los siguientes temas:

  • Introducción a conceptos de Media en general, como Progressive Download vs. Adaptive Streaming, protocolos disponibles de Adaptive Streaming, transcoding vs. transmuxing, etc.
  • Arquitectura de Microsoft Azure Media Services (PaaS)
  • Principales características de la plataforma:
    • Video-on-Demand (VOD)
    • Live Streaming
    • Dynamic Packaging
    • Dynamic Encryption (content protection)
  • Demostraciones:
  • Nuevas características anunciadas recientemente:

Queremos agradecerles a todos los que asistieron al evento y les repetimos que pueden contactarnos (@marianodvazquez y @mconverti) en caso de que tengan preguntas o dudas sobre alguno de los temas presentados. El material que utilizamos para la presentación ya esta disponible para ser descargado desde nuestro repositorio GitHub @ https://github.com/mconverti/bootcamp2015-aplicaciones-media-con-azure-media-services.

Construyendo aplicaciones Media con Microsoft Azure Media Services

Por úlitmo, les dejamos algunos links con recursos adicionales relacionados con esta charla:

 

Enjoy!

More Azure Management Portal updates for Azure Media Services

Last week, the Azure Media Services team published more updates in the Azure Management Portal that include some new features. In this post you will find a quick summary of what’s new.

 

Added support for JSON Web Token (JWT) format

Last month, the Azure Media Services team added support for JSON Web Token (JWT) format to restrict delivery of content keys; before this update, only Simple Web Token (SWT) format was supported.

With the latest Azure Portal updates, there is a new Token Type option in the Content Protection tab for both the AES and PlayReady authorization policies to let you set the format: JSON Web Token (default) or Simple Web Token.

Please take into account that:

  • After applying changes to the authorization policies, it could take up to a few minutes to take effect.
  • The Content Protection tab only lets you configure the authorization policies that are used for the encryption features in the Azure Portal. If you have a custom authorization policy, you will need to configure it programmatically using the SDK/REST API.

To learn more about how to use JWT format with dynamically encryption, you can read these blog posts by @GEOTRIF:

Token Type option in Content Protection tab

 

Disabled Play command when the asset has custom delivery policies

The Play command in the Content tab is now disabled for assets that have a custom delivery policy created programmatically using the SDK/REST API (i.e. not with the Encryption command).

The reason behind this update is that the Azure Portal relies on the delivery policies created with the Encryption command. If you create a custom one, it might not cover the delivery protocols used by the Azure Portal. Therefore, to avoid any potential playback issues that could be misleading (users might think that there is an issue in the dynamic encryption configuration), the Play command is now disabled in this scenario.

Play command disabled with custom delivery policies

 

Added support for Streaming Endpoints advanced configuration

The Configure sub-tab for streaming endpoints now have three new sections:

  • Cross Domain Policy: This section lets you specify the cross domain access policy for Adobe Flash clients. For more information, see Cross-domain policy file specification.
  • Client Access Policy: This section lets you specify the client access policy for Microsoft Silverlight clients. For more information, see Making a Service Available Across Domain Boundaries.
  • Custom Host Names: This section lets you configure a streaming endpoint to accept traffic directed to a custom host name. This allows for easier traffic management configuration through a Global Traffic Manager (GTM) and also for branded domain names to be used as the streaming. To perform CRUD operations on the custom host names, click the Configure button to open the Manage Custom Host Names dialog.

Please take into account that these sections (along with the rest) are only enabled for editing when the streaming endpoint has at least one streaming unit.

Streaming Endpoint advanced configuration

Manage streaming endpoint custom host names 

 

Enjoy!

Connecting to Microsoft Azure Media Services REST API

If you are a .NET/C# developer, you can consume all the Azure Media Services features using the .NET SDK. It is available as a NuGet package which you can easily get from Visual Studio, and its source code can be found in GitHub. There is also an Extensions .NET SDK that you can use to simplify the implementation of common media workflows (also available as a NuGet package and in GitHub).

If you are working in a platform/language where there is no official SDK currently available, you can leverage Azure Media Services by implementing your own client for the Azure Media Services REST API. Luckily, this is not a complex task: the REST API is straightforward in terms of design, supports JSON/XML formats and it’s very well documented in MSDN, so you only need a standard HTTP client and you are good to go Smile.

In the case of the latter, you will need to perform some connection steps before accessing the Azure Media Services REST API:

  1. Get the access token (SWT format) from the ACS instance by providing the Media Services account name and key. You can find more details here.
  2. Send an authenticated GET request (with the bearer access token) to https://media.windows.net/ and you will receive a 301 Moved Permanently response with the REST API endpoint in the Location header. You can find more details here.
  3. Use the bearer access token and the new endpoint for sending all your subsequent requests to the REST API.

A common mistake when performing step #2 is not disabling the auto-redirect logic in the HTTP client used to send the request. This feature is likely to be enabled by default for GET requests in a standard HTTP client, so it would follow the redirection automatically, send a second request to the new location (similar to https://wamsbayclus001rest-hs.cloudapp.net/api/) and get a second response without the Location header and a body as follows:

{  
   "d":{  
      "EntitySets":[  
         "AccessPolicies",
         "Locators",
         "ContentKeys",
         "ContentKeyAuthorizationPolicyOptions",
         "ContentKeyAuthorizationPolicies",
         "Files",
         "Assets",
         "AssetDeliveryPolicies",
         "IngestManifestFiles",
         "IngestManifestAssets",
         "IngestManifests",
         "StorageAccounts",
         "Tasks",
         "NotificationEndPoints",
         "Jobs",
         "TaskTemplates",
         "JobTemplates",
         "MediaProcessors",
         "EncodingReservedUnitTypes",
         "Operations",
         "StreamingEndpoints",
         "Channels",
         "Programs"
      ]
   }
}

Of course, this error will prevent you from continuing with step #3 to actually access the Azure Media Services resources you want. Therefore, as a summary, if you are building your own Azure Media Services client, do not use auto-redirection logic in your HTTP client configuration when connecting to the REST API.

Happy coding!

Microsoft Azure Management Portal updates for Azure Media Services

Last week, the Azure Media Services team published some updates in the Azure Management Portal that include several improvements and new features. In this post you will find a quick summary of what has changed and what is new.

 

Content Protection tab

As part of the General Availability announcement of the Content Protection (AES and DRM) service, the following changes were applied in the Content Protection tab:

  • The Preview tag next to the Content Protection tab was removed since the service is now in General Availability. This also means that you no longer need to sign up for the PlayReady license service via the Azure Preview features page; this feature is now active in all the Media Services accounts.
  • The Branding Reporting table at the bottom is now optional; this means that the PlayReady license service is always enabled.

Content Protection tab

 

Content tab

These are the improvements and new features that were added in the Content tab:

  • The Encode command was renamed Process, since you can now select another media processor (not just the Azure Media Encoder).
  • The Process dialog (formerly Encode) was refactored as follows:
    • The encoding configuration for Azure Media Encoder was reorganized into these categories to make it clearer for users:
      • Presets for Adaptive Streaming (dynamic packaging): produces multi-bitrate MP4 files ready for dynamic packaging
      • Presets for Progressive Download: produces a single MP4 file ready for progressive download
      • Legacy Presets for Adaptive Streaming: produces Smooth Streaming files ready for adaptive streaming
      • Other (common workflows).
    • As of the General Availability announcement of the Azure Media Indexer back in September 2014, you can now select the Azure Media Indexer processor to submit an indexing job. You can also enter the optional title and description metadata fields for the task configuration preset, and choose whether to copy the generated TTML caption file back to the input asset. For more information related to the Azure Media Indexer you can check this blog post.
  • The Publish and Play commands now notify the user if the Media Services account does not have any streaming units when the content is dynamically encrypted (AES or PlayReady). Remember that the dynamic packaging and dynamic encryption features will only work in streaming endpoints that have at least one streaming unit.
  • The Publish command now creates a SAS locator (for progressive download) instead of an Origin locator (for adaptive streaming) when the asset contains a single MP4 file. This is the case, for example, when you generate an asset by submitting a job using any of the H264 Broadband * encoding configuration presets.

Content tab

 

Channels and Programs tabs

With the AES encryption and PlayReady encryption announcements for the Live Streaming service back in  November/December 2014, the Azure Media Services team has added support to configure these features from the portal:

  • The Start Streaming command in the Channels tab now includes three sub-commands:
    • Unencrypted: creates a channel program with default settings, publishes the associated asset with an Origin locator and starts the program.
    • AES Encrypted: same as Unencrypted command, but it also adds an asset delivery policy for AES dynamic encryption.
    • PlayReady Encrypted: same as Unencrypted command, but it also adds an asset delivery policy for PlayReady dynamic encryption.
  • The Create a new program dialog now includes the Streaming Encryption Type setting:
    • Unencrypted: creates a program with custom settings.
    • AES Encrypted: same as Unencrypted command, but it also adds an asset delivery policy for AES dynamic encryption.
    • PlayReady Encrypted: same as Unencrypted command, but it also adds an asset delivery policy for PlayReady dynamic encryption.
  • The Programs table now has a new Encryption column specifying the streaming encryption type.

Channels and Programs tabs

 

Enjoy!