Write a custom module that integrates Wowza Streaming Engine with DRM key server technology

Wowza Streaming Engine™ media server software has a rich API for securing your video streams. Use the best practices described in this article to create a custom module that integrates with a third-party Digital Rights Management (DRM) key service for securing media streaming.

The best way to learn is to read the overview articles about the Wowza Streaming Engine Java API. Start with About the Wowza Streaming Engine Java API.

Below are articles that describe in more detail how to secure streaming for HLS and MPEG-DASH:

HLS

Secure HLS streaming using DRM encryption with Wowza Streaming Engine
Add custom playlist headers to HLS manifests in Wowza Streaming Engine

MPEG-DASH

Secure MPEG-DASH streams using Common Encryption in Wowza Streaming Engine

Most likely, you'll create a single Wowza Streaming Engine custom module that's used to encrypt on-demand and live streams. That module will be configured on a per-application basis using application-level properties in [install-dir]/conf/[application-name]/Application.xml. You can use the properties to configure the customer's account credentials (user name and password), URL to a remote key server API, and a map file for mapping stream names to DRM information. The suggested property name format is:

drm[service-name][camel-case-property-name]

For example, if your service name is ACME Encryption, the properties to configure your service might be:

<Property>
<Name>drmACMEEncryptionUserName</Name>
<Value>JUser</Value>
<Type>String</Type>
</Property>
<Property>
<Name>drmACMEEncryptionPassword</Name>
<Value>password1</Value>
<Type>String</Type>
</Property>
<Property>
<Name>drmACMEEncryptionRESTUrl</Name>
<Value>https://acmedrm.com/rest</Value>
<Type>String</Type>
</Property>
<Property>
<Name>drmACMEEncryptionStreamNameMapFile</Name>
<Value>${com.wowza.wms.context.VHostConfigHome}/${com.wowza.wms.context.Application}/conf/acmedrmstreammap.txt</Value>
<Type>String</Type>
</Property>

The following are the common methods used to extract properties set in Application.xml:

String WMSProperties.getPropertyStr(String name, String defaultVal)
boolean WMSProperties.getPropertyBoolean(String name, boolean defaultVal)
public int WMSProperties.getPropertyInt(String name, int defaultVal)
public long WMSProperties.getPropertyLong(String name, long defaultVal)
public double WMSProperties.getPropertyDouble(String name, double defaultVal)

For example:

public void onAppStart(IApplicationInstance appInstance)
{
String userName = props.getPropertyStr("drmACMEEncryptionUserName", null;);
String password = props.getPropertyStr("drmACMEEncryptionPassword", null;);
String restUrl = props.getPropertyStr("drmACMEEncryptionRESTUrl", null;);
}

In your module, the path variables for the drmACMEEncryptionStreamNameMapFile property can be resolved using the following code in your onAppStart method:

public static final String DEFAULT_DRMSTREAMNAMEMAPFILE = "${com.wowza.wms.context.VHostConfigHome}/${com.wowza.wms.context.Application}/conf/acmedrmstreammap.txt";

...

public void onAppStart(IApplicationInstance appInstance)
{

...

WMSProperties props = appInstance.getProperties();

Map pathMap = new HashMap();

pathMap.put("com.wowza.wms.context.VHost", appInstance.getVHost().getName());
pathMap.put("com.wowza.wms.context.VHostConfigHome", appInstance.getVHost().getHomePath());
pathMap.put("com.wowza.wms.context.Application", appInstance.getApplication().getName());
pathMap.put("com.wowza.wms.context.ApplicationInstance", appInstance.getName());

String mapFilePath = props.getPropertyStr("drmACMEEncryptionStreamNameMapFile", DEFAULT_DRMSTREAMNAMEMAPFILE);
mapFilePath =  SystemUtils.expandEnvironmentVariables(mapFilePath, pathMap);

...

}
The general process to apply DRM encryption to a stream involves the following steps:
 
  1. Map a stream name to a private encryption key and public key identifier, as well as to additional DRM metadata.
     
  2. Call the Wowza Streaming Engine API to apply the encryption key, key identifier, and DRM metadata to the stream.

In general, two components are needed to perform the first step: (1) a map file to map stream names to DRM key information and (2) a remote service or local calculation for using the DRM key information to retrieve the private encryption key. The suggested method for mapping stream names to DRM key information is to use a map file, which maps a single stream to DRM key information. The suggested format of this file is a hybrid JSON format like this:

stream1={"publicKeyId":"stream1", "keyDomain":"myContent"}
stream2={"publicKeyId":"stream2", "keyDomain":"myContent"}

The left side is the stream name and the right side is JSON data that represents DRM key information that's needed to fetch or calculate the appropriate private encryption key. Wowza Streaming Engine includes the Jackson JSON Processor. To use this API, import the following libraries.

import com.wowza.util.JSON;

The following example code shows how to parse the map file and extract the stream name and JSON parameters:

String delimiter = "=";

BufferedReader inf = new BufferedReader(new FileReader(file));
String line;
while ((line = inf.readLine()) != null)
{
    line = line.trim();
    if (line.startsWith("#"))
        continue;

    if (line.length() == 0)
        continue;

    int pos = line.indexOf(delimiter);
    if (pos < 0)
    {
        // report malformed map entry line
        continue;
    }

    String streanName = line.substring(0, pos).trim();
    String jsonStr = line.substring(pos+1).trim();

    JSON data = new JSON();
    data.readValues(jsonStr);
    Map<String, Object> entries = data.getEntrys();
}

inf.close();
After the per-stream DRM information is known, you must retrieve or calculate the private encryption key for the stream. For many DRM systems, this is done by communicating with a remote key server using REST or a web services API. If communicating with a remote API over the network, it's very important that this is done in a way that doesn't impede the Wowza Streaming Engine operations. The following are some recommendations:
 
  • If possible, do network communication outside of encryption callbacks so that network latency or service downtime don't impact streaming.
     
  • Set short network timeouts where possible.
     
  • Create a thread, which runs in the background, to periodically verify that your network service is running. If it goes down for a while, then suspend calls to the service until it's restored. In the case where the service is down, it might make sense to encrypt streams with a random encryption key so that they're not playable.
     
  • Cache key requests so that they only need to be retrieved once per-stream name.
The goal is to try and avoid or limit the time spent blocked, waiting for network traffic in the encryption callbacks.