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!



3 Comments

Leave a Reply