Source SDK Development Guide
What the SDK can do
The ExpressPlay Source SDK is designed to make it simple to build highly-customized secure media players and ebook reader applications for Marlin protected audio, video and ebook content.
The Source SDK can be used with iOS, Android, OSX Windows and Linux. The ExpressPlay SDK shares a common core API set across all platforms. On some platforms the SDK includes an extended set of APIs unique to that platform.
For development of media applications on iOS and Android the SDK offers a simple API providing access to DRM control and media playback functions, sufficient to credential an application, acquire DRM licenses and trigger playback of Marlin-protected content. Content may be a contiguous media file or an adaptive bit rate (ABR) stream. The SDK works in conjunction with the ExpressPlay Service for device initialization (Marlin personalization) and token processing for playback (Marlin content license acquisition) while meeting the compliance and robustness rules of the MTMO (Marlin Trust Management Organization).
What are the steps to play
The steps below are a high-level view of the steps required to play content. However, the exact sequence of steps to play content in your application may vary due to your embedded platform requirements.
- Start the DRM engine
- Get a unique player ID (personalization)
- Get the content URL
- Get a token for playback
- Process the token
- Start the player
What are the steps for ebooks
- Initialize the DRM Engine
- Get a unique player ID (Personalization)
- Get a BB license for the content ID of the ebook, and store that license in the license store
- Instantiate a MediaStream object (passing it a reference to the encrypted ebook file)
- Read bytes from the mediastream object until there are none remaining. Now you have a decrypted ebook.
- Instantiate a webview object, and pass it the decrypted ebook
Key Concepts
Below is a list of key concepts that a developer should know before using the ExpressPlay SDK. It's a good idea to go through all the concepts before using the SDK as they are reference frequently throughout the SDK documentation.
SDK Usage Guidelines
In addition to the key concepts above, the ExpressPlay SDK Usage Guidelines is an overview of best practices when using the ExpressPlay SDK. The major sections of the guidelines are listed below for convenience.
Personalization
Personalization is a Marlin term for device initialization. When an app requests to be personalized by ExpressPlay, it provides a unique fingerprint. The ExpressPlay service, responds by providing the client a credential which is unique to that application copy running on a given device. Without this unique Marlin credential, the device is not able to request tokens for playback.
Additional information on Personalization can be found here.
Content License Acquisition
There are two ways to obtain a Marlin content license, using SAS (via MS3) or using a Marlin Broadband (BB) license. Either may be obtained from the ExpressPlay service. You can obtain additional information for MS3 and Marlin BroadBand licenses here.
Media Playback
As an example of usage of some of the different ExpressPlay APIs, this section discusses a typical sequence of API usage occurring when playback of a media file or stream is requested on an embedded Linux system.
When such playback is requested, the first thing the application or media stack responsible for playback does is determine what kind of file/stream it is and whether it is protected. The ExpressPlay SDK Media File component, accessed via WSB_MediaFile methods, can be used to determine this information. If the file/stream is not protected, the normal media playback functionality for cleartext files can often be used. However, if the file type is understood by ExpressPlay SDK but not by the application or media stack, then ExpressPlay Packager or third-party interfaces may be called to do the parsing.
If the file/stream is Marlin-protected, the next step needed is to determine whether playback of the content is allowed. This determination requires trying to find a license that grants access to the content. WSB_PlaybackEnabler_EnableMediaFile can be called to obtain a particular media file’s content ID(s) and track IDs (if any) and to find a currently valid license. The Playback Enabler actually invokes the Rights Enabler, which utilizes other interfaces, such as those provided by WSB_LicenseStore as part of its search for a valid license. After such a license is found, other WSB_PlaybackEnabler methods can be called to check that the license allows the content to be played, and to store in a WSB_KeyManager object the key(s) required to decrypt the content.
The WSB_KeyManager_GetKeyByName method can then be called one or more times to obtain from the license the key(s). The application or media stack decrypts the data and sends the samples or the stream in the clear to the rendering engine. In the case of MP4 files, a ExpressPlay Packager class called AP4_SampleDecrypter is sometimes used to do the decryption. On desktop (Windows or Mac) systems, you do not need to directly call methods in any of these classes. Instead, any GUI application you develop can call methods in the WSB_Player class to perform all the Marlin-related functionality. It attempts to find a valid license, obtain the keys needed to decrypt the content, decrypt and decode the content, and supply the resulting cleartext output to whatever window you specify. The WSB_Player methods internally invoke the Media File, Playback Enabler, etc. components as needed.
MediaStream (MS) for ebooks
MediaStream interface supports decryption of HTTP or file based encrypted data other than the typical Marlin protected audio/video media. Which could consist of the eBook media type, where many container formats exist, including a Marlin defined ePub container. In this scenario, either the whole eBook or specific elements of the ebook container can be encrypted and packaged into a type supported by this new interface. The application then parses the container, and uses the interface combined with a Marlin license to decrypt the content.
Additional information for ebooks can be found here.
Other DRM Functions
The Extended DRM functionality is accessible to applications using the source sdk. Including such functions as the ability to access content keys, or query the consequences of a license evaluation (to assess for instance if some form of output controls must be applied and do so as a result). This greater functionality however requires that these applications provide their own security and hardening mechanisms to prevent the loss of key and content material.
Selected Classes for Embedded Development
WSB_ActionResultInfo, WSB_ActionResultConstraint, and WSB_ActionResultOutputControlConstraint
A WSB_ActionResultInfo object represents information about a SHI_ActionResult that was previously obtained, for example, by a call to WSB_PlaybackEnabler_PerformPlayAction. A SHI_ActionResult provides information about what a license to particular content allows and requires for a particular action, such as a “Play” action. A WSB_ActionResultInfo provides a high-level interface for obtaining the information contained in a SHI_ActionResult, such as whether the license allows the specified action (typically content playback), and whether certain constraints referred to as obligations and callbacks are placed on applications performing the action. If an application cannot handle all of the mandatory obligations and callbacks, it is not allowed to access the content.
Creating a WSB_ActionResultInfo Object
A WSB_ActionResultInfo object is created by calling WSB_ActionResultInfo_Create. You pass it a pointer to the SHI_ActionResult you want to analyze (by calling WSB_ActionResultInfo methods).
Determining Whether Content Access Is Allowed
To determine whether the content license allows the associated action (typically, a “Play” action), call WSB_ActionResultInfo_IsGranted. If this method returns WSB_TRUE, the license allows the action.
If a call to WSB_ActionResultInfo_IsPermanent returns WSB_TRUE, the granting of the action is permanent. Otherwise, it is transient. If it is transient, the application must always reevaluate the rights, that is, determine whether access is allowed, each time the content is to be rendered (played).
Determining Why Content Access Is Not Allowed
Whenever an action (such as “Play”) is not allowed, such as when WSB_ActionResultInfo_IsGranted returns WSB_FALSE, you can get more information as to why the action was not allowed by calling WSB_ExplainActionResultFailure. Pass this method the following:
- A pointer to the SHI_ActionResult.
- A pointer to a SHI_Engine (or NULL to have the function create an engine).
- A pointer to the WSB_MediaFile, when available, or NULL if not.
- The address of a SHI_Data object that is set to contain a goto URL (see below), when relevant. This can be set to NULL if you are not interested in having such a URL.
This method examines the SHI_ActionResult and returns an error code that is as meaningful as possible.
It returns:
- WSB_ERROR_DRM_LICENSE_EXPIRED if the license expired.
- WSB_ERROR_DRM_SUBSCRIPTION_EXPIRED if the subscription expired.
- WSB_ERROR_DRM_PLAY_COUNT_EXCEEDED if the playcount (maximum number of times the content may be played) has been exceeded.
- WSB_ERROR_DRM_MISSING_CREDENTIALS if a missing link (such as for subscription link renewal) was detected.
- WSB_ERROR_DRM_DENY_RIGHTS if the method could not determine a more useful explanation as to why the action failed.
- WSB_SUCCESS if the action was actually granted.
Whether or not a goto URL is created, and its semantics, depends on the context. When a subscription has expired, the goto URL is set to a link renewal URL, if possible. In other cases, a rights issuer URL may be returned. Another example of when you might want to call WSB_ExplainActionResultFailure is the following: When handling a license event during a call to WSB_PlaybackEnabler_EnableMediaFile, you will want to determine whether the license that has been found grants the desired action (typically by calling WSB_Config_ValidateActionResult), and if it does not, you may want to call WSB_ExplainActionResultFailure in order to get an explanation for the failure. One final example is that after calling WSB_PlaybackEnabler_PerformPlayAction, you must determine whether the “Play” action is actually granted, again typically by calling WSB_Config_ValidateActionResult, and you can call WSB_ExplainActionResultFailure if desired to learn more if the action was not granted.
Determining Whether There Are Obligations or Callbacks
Some licenses may grant access to the content (WSB_ActionResultInfo_IsGranted returns WSB_TRUE), but may specify further requirements that should be satisfied. These requirements are constraints called obligations and callbacks. Obligations represent things that the application should perform or obey. Callbacks are functions that the application should call under specified conditions. Each obligation or callback is either mandatory or optional. If there are any mandatory obligations or callbacks that the application cannot handle, then access to the content is not allowed, even though WSB_ActionResultInfo_IsGranted returned WSB_TRUE.
To determine whether obligations or callbacks are specified, call WSB_ActionResultInfo_HasObligations or WSB_ActionResultInfo_HasCallbacks, respectively. Each of these methods returns WSB_TRUE if there are any of the specified constraints (obligations or callbacks), whether mandatory or optional. To determine whether there are any mandatory obligations or callbacks, call WSB_ActionResultInfo_HasMandatoryObligations or WSB_ActionResultInfo_HasMandatoryCallbacks, respectively.
Obtaining Obligations
Call WSB_ActionResultInfo_GetConstraintCount to find out how many constraints there are. This method has a WSB_Boolean argument which, if WSB_TRUE, indicates that the method should return a count of just the mandatory constraints; otherwise, it returns a count of all the constraints. Please note: Currently, this method only counts obligations, not callbacks.
You can iterate through the constraints (currently, only obligations), obtaining one at a time. Pass WSB_ActionResultInfo_GetConstraintByIndex the (0-based) index for the constraint to be obtained, a WSB_Boolean indicating whether only mandatory constraints should be returned, and the address of a pointer to a WSB_ActionResultConstraint.
WSB_ActionResultInfo_GetConstraintByIndex will create a WSB_ActionResultConstraint for the specified constraint and set the pointer to refer to this object. Here is an example of obtaining the count of mandatory constraints and iterating through the constraints. In this example, AR_info is a pointer to the WSB_ActionResultInfo:
WSB_Cardinal count = 0;
WSB_Ordinal indx;
WSB_Result result;
WSB_ActionResultConstraint* constraint = NULL;
count = WSB_ActionResultInfo_GetConstraintCount(AR_info,
WSB_TRUE);
if (count > 0) {
/* There are mandatory constraints, let's see what they are */
for (indx = 0; indx < count; indx++) {
result =
WSB_ActionResultInfo_GetConstraintByIndex(AR_info,
WSB_TRUE,
indx,
&constraint);
if (WSB_FAILED(result)) {
/* Handle error */
. . .;
}
/* We have a constraint. Get more info about it. */
. . .;
}
}
You can call WSB_ActionResultConstraint methods to obtain further information about each constraint obtained.
Explicitly Accepting Obligations or Callbacks
In most cases, after the application has determined that it can handle (at least) all the mandatory obligations and callbacks, it must explicitly indicate this, for example, by calling WSB_PlaybackEnabler_AcceptActionResult, prior to accessing the content. To determine whether explicitly accepting the constraints is actually a requirement, call WSB_ActionResultInfo_MustAccept.
Destroying the WSB_ActionResultInfo
When the WSB_ActionResultInfo object is no longer needed, destroy it by calling WSB_ActionResultInfo_Destroy.
WSB_ActionResultConstraint and WSB_ActionResultOutputControlConstraint Methods
A WSB_ActionResultConstraint object represents a single constraint associated with a SHI_ActionResult.
A WSB_ActionResultOutputControlConstraint object is a specialized case of a WSB_ActionResultConstraint that represents a single output control constraint. Usage of WSB_ActionResultConstraint and WSB_ActionResultOutputControlConstraint.
Obtaining WSB_ActionResultConstraint Objects
A WSB_ActionResultConstraint is created whenever WSB_ActionResultInfo_GetConstraintByIndex is called.
A WSB_ActionResultOutputControlConstraint is an existing WSB_ActionResultConstraint object simply recast to WSB_ActionResultOutputControlConstraint (by WSB_ActionResultConstraint_GetAsOutputControlConstraint) when it is determined that the object is a constraint with the output control type, as described below.
Determining the Type and Nature of the Constraint
Call WSB_ActionResultConstraint_IsMandatory to find out whether the constraint is mandatory.
Call WSB_ActionResultConstraint_Type to obtain the constraint type. The possible constraint types are defined in WsbActionResultConstraint.h. Currently, they are the following:
WSB_ACTION_RESULT_CONSTRAINT_TYPE_UNKNOWN
WSB_ACTION_RESULT_CONSTRAINT_TYPE_DO_NOT_STORE
WSB_ACTION_RESULT_CONSTRAINT_TYPE_OUTPUT_CONTROL
WSB_ACTION_RESULT_CONSTRAINT_TYPE_METERING
A DO_NOT_STORE obligation means that it is forbidden to store the content on the device; it can only be streamed.
A METERING obligation indicates that the application must perform metering to report the start and stop times of content playing, as described in the Sushi (Marlin Client) SDK API document. OUTPUT_CONTROL obligations are described in the following section.
Handling an Output Control Obligation
An OUTPUT_CONTROL constraint specifies an output control obligation. An output control obligation is characterized by three elements: a technology, a parameter, and a value. When you receive a WSB_ActionResultConstraint of this type, call WSB_ActionResultConstraint_GetAsOutputControlConstraint to cast it to a WSB_ActionResultOutputControlConstraint so that you can access this further information. Call WSB_ActionResultOutputControlConstraint_GetTechnology to obtain the technology type. The possible technology types are defined in WsbActionResultConstraint.h with the following names:
WSB_ACTION_RESULT_CONSTRAINT_OUTPUT_CONTROL_TECHNOLOGY_UNKNOWN
WSB_ACTION_RESULT_CONSTRAINT_OUTPUT_CONTROL_TECHNOLOGY_BASIC_CCI
WSB_ACTION_RESULT_CONSTRAINT_OUTPUT_CONTROL_TECHNOLOGY_DTCP
Call WSB_ActionResultOutputControlConstraint_GetParameter to obtain the parameter type, one of the following:
WSB_ACTION_RESULT_CONSTRAINT_OUTPUT_CONTROL_PARAM_UNKNOWN
WSB_ACTION_RESULT_CONSTRAINT_OUTPUT_CONTROL_PARAM_EPN
WSB_ACTION_RESULT_CONSTRAINT_OUTPUT_CONTROL_PARAM_CCI
WSB_ACTION_RESULT_CONSTRAINT_OUTPUT_CONTROL_PARAM_IMAGE_CONSTRAINT_TOKEN
WSB_ACTION_RESULT_CONSTRAINT_OUTPUT_CONTROL_PARAM_DIGITAL_ONLY_TOKEN
WSB_ACTION_RESULT_CONSTRAINT_OUTPUT_CONTROL_PARAM_APS
WSB_ACTION_RESULT_CONSTRAINT_OUTPUT_CONTROL_PARAM_RETENTION_MOVE_MODE
WSB_ACTION_RESULT_CONSTRAINT_OUTPUT_CONTROL_PARAM_RETENTION_STATE
Call WSB_ActionResultOutputControlConstraint_GetValue to obtain the integer value associated with the output control technology and parameter. For possible values, see the Marlin Output Control Specification.
Destroying the WSB_ActionResultConstraint
When a WSB_ActionResultConstraint object is no longer needed, destroy it by calling WSB_ActionResultConstraint_Destroy.
WSB_Config
Usage
The WSB_Config methods are ones whose default implementations can be replaced and customized for different platforms. Currently, there is only one such method: WSB_Config_ValidateActionResult.
WSB_Config_ValidateActionResult can be called to analyze a SHI_ActionResult. A SHI_ActionResult is obtained, for example, when WSB_PlaybackEnabler_PerformPlayAction is called. A SHI_ActionResult is also obtained whenever a license is found during a license search initiated by a call to WSB_PlaybackEnabler_EnableMediaFile or WSB_RightsEnabler_EnableMediaFile.
The SHI_ActionResult contains information about access rights and requirements that must be satisfied prior to playing the relevant content (such as the content for which a license was being searched). These requirements include constraints referred to as obligations and callbacks. Obligations represent things that the application is required to perform or obey. Callbacks are functions that the application is required to call under specified conditions.
WSB_Config_ValidateActionResult has four parameters:
- A pointer to the SHI_ActionResult you want analyzed.
- A WSB_Boolean whose value should be WSB_TRUE if the SHI_ActionResult was generated as a result of a SHI_Action_Perform (which is the case if it was generated by a call to WSB_PlaybackEnabler_PerformPlayAction). The value should be WSB_FALSE if the SHI_ActionResult was generated as a result of a SHI_Action_Check, which is the case whenever a license is found during a license search.
- The name of an action. WSB_Config_ValidateActionResult determines whether the specified action is allowed. Typically, you want to know whether playback of the content is allowed, so for this parameter you specify SHI_ACTION_PLAY. The text below assumes you have specified this action.
- Any parameters required by the action. This is typically NULL, since the Play action does not have any parameters. WSB_Config_ValidateActionResult calls WSB_ActionResultInfo_Create, passing it the SHI_ActionResult, and then subsequently calls WSB_ActionResultInfo methods to determine whether the Play action is allowed (that is, whether the content governed by the license is allowed to be played) and whether all of the mandatory obligations and callbacks (if any) can be satisfied. If the action is granted and all mandatory obligations and callbacks can be satisfied, WSB_Config_ValidateActionResult returns WSB_SUCCESS. If the action is not granted, it returns WSB_ERROR_DRM_DENY_RIGHTS, and it returns WSB_ERROR_DRM_LICENSE_UNSUPPORTED if there are mandatory callbacks or obligations that cannot be satisfied. If the Play action is not granted, you can call WSB_ExplainActionResultFailure to get a more detailed error code as to why the action is not granted.
WSB_Config_ValidateActionResult Default and Sample Implementations
WSB_Config_ValidateActionResult is a replaceable function. That is, there is a default implementation, but that implementation can be replaced prior to building Wasabi. The header file declaring the function is at $WASABI_HOME/Source/Config/Validator/WsbConfigActionResultValidator.h The default implementation is at $WASABI_HOME/Source/Config/Validator/Default/WsbConfigActionResultValidator.cpp The default implementation refuses access to the content if either the Play action is not granted or there are any mandatory obligations or callbacks.
There is also a sample more complex implementation at $WASABI_HOME/Source/Config/Validator/Sample/WsbConfigActionResultValidator.cpp This sample validator allows mandatory DTCP output control obligations and MS3 do-not-store obligations, which are described further in the sample .cpp file and in the Marlin Output Control Specification. It also allows BasicCCI DigitalOnlyToken and APS parameter values. The sample code cannot be used as is. It shows how to allow such obligations, but the code must in addition in some cases take actions required to properly enforce the specified constraints. For example, if the device supports CGMS-A (for the Analog Protection System), then allowing the BasicCCI APS parameter is okay, as long as the corresponding output control flags have been set in the device hardware or software.
The default validator can always be used for an application that does not support any obligations or callbacks. If an application could actually support some obligations or callbacks, or the device has characteristics that would make a particular obligation or callback irrelevant, then the default validator should be replaced with a more suitable validator. For example, if a device would never store MS3 content, then a mandatory do-not-store obligation could be considered to be satisfied, and the existence of such an obligation should not result in denying access to the content.
WSB_EcmDecrypter
Usage
A WSB_EcmDecrypter object can be used to aid an application processing a Marlin Broadband Transport Stream (Marlin BBTS), which is an MPEG-2 Transport Stream containing Marlin-protected content. The WSB_EcmDecrypter interface contains methods for processing PMTs (Program Map Tables), ECMs (Entitlement Control Messages), CATs (Conditional Access Tables), and MRTs (Marlin Rights Tables), supplying the client information needed, such as the traffic keys required to decrypt the Transport Stream media content packets. Usage of WSB_EcmDecrypter is illustrated in the example utility named WasabiEcmDecrypter.cpp.
Creating a WSB_EcmDecrypter Object
Call WSB_EcmDecrypter_Create to create an instance of a WSB_EcmDecrypter object. You pass it the address of a WSB_EcmDecrypter pointer that is set by this method to refer to the WSB_EcmDecrypter object that the method creates. You also pass it an indication of the format in which you want the traffic keys supplied, either WSB_MP2TS_KEY_FORMAT_CLEAR (indicating cleartext 16-byte AES keys, with 16-byte initialization vectors) or WSB_MP2TS_KEY_FORMAT_SKB (indicating Secure Key Box-exported keys, that is, keys exported using the SKB API SKB_SecureData_Export method). There is also a parameter for specifying key format parameters, but there are not yet any such parameters defined, so currently just specify NULL for key_format_params. Also pass WSB_EcmDecrypter_Create a “listener” that is notified of events occurring while the WSB_EcmDecrypter_UpdateEcm and WSB_EcmDecrypter_UpdateMrt methods are processing ECMs (known as KSMTs in IEC 62455) and MRTs (Marlin Rights Tables), respectively. A listener is declared as follows:
typedef struct {
/** Listener instance pointer; gets passed into callback
* methods.
*/
void* instance;
/**
* Callback method invoked on a new or updated media file
* detection.
*/
WSB_Result (*OnNewMediaFile)(void* instance,
WSB_MediaFile* media_file,
WSB_KeyManager* key_manager);
/**
* Callback method invoked on a new MPEG2 TS traffic key.
*/
void (*OnKsmInfoChange)(void* instance,
const WSB_KsmInfo* key_info,
WSB_UInt32 change_mask);
} WSB_EcmDecrypter_Listener;
WSB_EcmDecrypter Methods and Usage
The WSB_EcmDecrypter interface has four methods that facilitate the processing of Marlin BBTS streams: WSB_EcmDecrypter_UpdatePmt,
WSB_EcmDecrypter_UpdateEcm, WSB_EcmDecrypter_UpdateCat, and WSB_EcmDecrypter_UpdateMrt. A client basically processes a Marlin BBTS stream the same way it processes any MPEG-2 TS stream whose content it plays, except for needing to do a few Marlin-specific steps. One of the first things the client should do is create a WSB_PlaybackEnabler object that is needed in subsequent steps.
Here is an example call:
WSB_PlaybackEnabler* enabler = NULL;
WSB_PlaybackEnabler_Create(enabler_listener, &enabler);
The client should also create a WSB_EcmDecrypter, after first defining the listener callback methods that are called by the WSB_EcmDecrypter when it is processing an ECM. These callback methods are described below. Sample code for creation of a WSB_EcmDecrypter object is the following:
WSB_EcmDecrypter* ecm_dec = NULL;
WSB_EcmDecrypter_Create(
ecm_listener, WSB_MP2TS_KEY_FORMAT_CLEAR, NULL, &ecm_dec);
The client should have a URL to the content to be played. If there is any chance the URL is an MS3 (Marlin Simple Secure Streaming) URL, WSB_PlaybackEnabler_ResolveUrl should be called. Here's a sample call, where url is the original URL, and content_url is a pointer to where the method stores the content URL (which is the same as the original URL, if it was not an MS3 URL):
SHI_Data* content_url = NULL;
WSB_PlaybackEnabler_ResolveUrl(enabler, url, &content_url);
The client should then use the content URL to obtain the content, and should start processing it as usual, for example extracting PAT and PMT tables.
Processing PMTs and ECMs
The WSB_EcmDecrypter_UpdatePmt method should be called by the client to process a PMT (Program Management Table). Pass it a pointer to the table bytes and an indication of the number of bytes. This method extracts from the PMT useful information it may need later, such as the CA descriptor, and returns to the caller the PID (Packet ID) of the ECMs (Entitlement Control Messages) for the program referenced by the PMT. Here is a sample call: WSB_EcmDecrypter_UpdatePmt(ecm_dec, pmt, pmt_length, &ecm_pid);
For each ECM with that PID, the client should call WSB_EcmDecrypter_UpdateEcm, providing it a pointer to the ECM bytes and an indication of the number of bytes. Here is a sample call:
WSB_EcmDecrypter_UpdateEcm(ecm_dec, ecm, ecm_length);
This method processes the ECM. Whenever it encounters a new content ID specified in the ECM, it calls the WSB_EcmDecrypter listener method OnNewMediaFile, passing it a pointer to a WSB_MediaFile object representing the content, and a pointer to a WSB_KeyManager in which the keys needed to decrypt the Marlin content should be stored. The OnNewMediaFile method must try to “enable” the new media file through the WSB_PlaybackEnabler API, ensuring that a license for the content is found and evaluated to determine whether playback of the content is allowed, and, if so, also ensuring that the WSB_KeyManager object is filled in with the keys needed to decrypt the content. Sample pseudocode is the following, where enabler is assumed to be the WSB_PlaybackEnabler object previously created by a call to WSB_PlaybackEnabler_Create:
ecm_listener.OnNewMediaFile(media_file, key_manager) {
// Try to have a valid license for the content found:
WSB_PlaybackEnabler_EnableMediaFile(
enabler, media_file, NULL, 0);
// If a license was found, determine whether playback is allowed:
WSB_PlaybackEnabler_PerformPlayAction(enabler, &action_result);
// Handle any DRM/license issues here.
// Determine whether app can handle all critical obligations
// and callbacks... If so, and playback is allowed, call the
// following to obtain the content keys and supply them to
// the WSB_KeyManager:
WSB_PlaybackEnabler_AcceptActionResult(
enabler, key_manager, NULL);
}
Whenever WSB_EcmDecrypter_UpdateEcm encounters in the ECM a new traffic key and any other ECM/KSM information changes, it decrypts the traffic key (which was encrypted using the Marlin content key) and it calls the listener’s OnKsmInfoChange method, passing it a WSB_KsmInfo structure and a change mask indicating which of the WSB_KsmInfo fields have been modified since the last time OnKsmInfoChange was called. The WSB_KsmInfo structure is defined as follows:
typedef struct {
/**
* A set of WSB_KsmInfoFlag values, bitwise or'ed together,
* indicating which fields of the structure were present in the
* ECM.
*/
WSB_UInt32 present_mask;
WSB_Mpeg2TsTrafficKey_Format format;
const void* format_params;
WSB_Mpeg2TsTrafficKey_Parity current_key_parity;
const WSB_Byte* current_key;
WSB_Size current_key_size;
const WSB_Byte* current_iv;
WSB_Size current_iv_size;
const WSB_Byte* next_key;
WSB_Size next_key_size;
const WSB_Byte* next_iv;
WSB_Size next_iv_size;
WSB_Mpeg2TsTrafficKey_ProtectionSystem protection;
WSB_Size access_criteria_count;
WSB_UInt64 timestamp;
WSB_UInt8 key_lifetime;
WSB_UInt8 permissions_category;
} WSB_KsmInfo;
The current_key_parity indicates whether the key specified by current_key is an even key (if the value is WSB_ MP2TS_KEY_EVEN) or an odd key (WSB_ MP2TS_KEY_ODD). As you can see, the current key, and the next key, and their initialization vectors, are specified, as well as other information such as access criteria and timestamps, whose meanings and usage are defined in the Marlin BBTS Specification.
The OnNewKsmInfo method should do whatever it needs to do to utilize the new traffic key and other information, so that it can decrypt subsequent TS content packets using the appropriate key.
Processing CATs and MRTs
The WSB_EcmDecrypter_UpdateCat method should be called by the client to process a CAT (Conditional Access Table). Pass it a pointer to the table bytes and an indication of the number of bytes. This method extracts from the CAT useful information it may need later, and returns to the caller (in the final parameter pointed to by the call) the PID (Packet ID) of the MRT (Marlin Rights Table).
Here is a sample call:
WSB_EcmDecrypter_UpdateCat(ecm_dec, cat, cat_length, &mrt_pid);
For each MRT with that PID, the client should call WSB_EcmDecrypter_UpdateMrt, providing it a pointer to the MRT bytes and an indication of the number of bytes.
Here is a sample call:
WSB_EcmDecrypter_UpdateMrt(ecm_dec, mrt, mrt_length);
This method processes the MRT. Whenever it encounters a new Silent License Acquisition URL, it calls the WSB_EcmDecrypter listener method OnNewMediaFile, passing it a pointer to a WSB_MediaFile object representing the content, and a pointer to a WSB_KeyManager in which the keys needed to decrypt the Marlin content should be stored. If the client has already found a license for the specified content, the OnNewMediaFile method can simply ignore the call. Otherwise, the passed-in WSB_MediaFile will now include the Silent License Acquisition URL, which may make it possible to now obtain the license when WSB_PlaybackEnabler_EnableMediaFile is called. OnNewMediaFile should call that method and, if successful, call the other methods shown in the sample OnNewMediaFile shown above, to determine whether playback is allowed and to fill in the WSB_KeyManager with the content keys.
Destroying the WSB_EcmDecrypter
When you are done with the WSB_EcmDecrypter object, destroy it by calling WSB_EcmDecrypter_Destroy.
WSB_KeyManager
Usage
A WSB_KeyManager object can be used to get content keys for a playback session. Call WSB_KeyManager_CreateEx to create an instance of a WSB_KeyManager object, passing it a WSB_ContentKeyFormatInfo structure indicating the format in which content keys should be stored in the WSB_KeyManager.
This structure is defined as follows:
typedef struct {
WSB_ContentKeyFormat format;
const void* parameters;
/**< NULL except for format WSB_CONTENT_KEY_FORMAT_SKB_CUSTOM */
} WSB_ContentKeyFormatInfo;
WSB_ContentKeyFormat is one of the following:
typedef enum {
WSB_CONTENT_KEY_FORMAT_CLEARTEXT,
WSB_CONTENT_KEY_FORMAT_SKB_STANDARD,
WSB_CONTENT_KEY_FORMAT_SKB_CUSTOM
} WSB_ContentKeyFormat;
Here, SKB refers to Secure Key Box, and the standard and custom SKB formats are as defined in the SKB API. Calling WSB_KeyManager_Create will create an instance of a WSB_KeyManager object in which the content key format is WSB_CONTENT_KEY_FORMAT_CLEARTEXT. The WSB_KeyManager_GetContentKeyFormatInfo method can be called to obtain the WSB_ContentKeyFormatInfo for the passed-in WSB_KeyManager. To obtain a particular key that has been stored in the WSB_KeyManager (by the Playback Enabler), call WSB_KeyManager_GetKeyByName. You pass it a string representing the “name” of the key.
This string could be one of the following:
- A content ID.
- “@track.n”, where n is the track ID (e.g., “@track.1”), for content that has a track-based protection scheme.
- “@file” for content that has a file-based protection scheme.
You also pass, in the key_data parameter, a pointer to a buffer in which the key data should be written, and, in the key_data_size parameter, an indication of the buffer size. The key length can be queried by setting key_data to NULL. That is, if you pass NULL for the key_data buffer pointer, this method simply returns the key length in *key_data_size, and returns WSB_SUCCESS.
At any given time, the WSB_KeyManager is considered to hold the keys for a particular media file. Whenever you want to reuse it to hold the keys for a different media file, first call WSB_KeyManager_Clear to clear its context and remove the keys it currently holds.
When you are done with the WSB_KeyManager object, destroy it by calling WSB_KeyManager_Destroy.
WSB_LicenseStore and WSB_License
A WSB_LicenseStore object is used to access the Wasabi License Store database in which licenses may be stored. Its methods can be called to store licenses, to obtain licenses that are stored, and to remove licenses from the License Store.
Obtaining a WSB_LicenseStore Object
The WSB_LicenseStore_Open method is called to open the License Store and create a new WSB_LicenseStore object to refer to it. If the License Store (database) does not yet exist, it is created.
WSB_LicenseStore Methods
Once the License Store is open, WSB_LicenseStore_AddLicense can be called to add a license into the License Store. In the call, you specify the license data and its size, as well as an optional tag string in which you can specify whatever textual information you like, such as the name of the application that is adding the license to the License Store. This method returns the license ID assigned by the License Store to the license that was added.
WSB_LicenseStore_RemoveLicense removes from the License Store the license with a specified license ID.
WSB_LicenseStore_GetLicenseById can be called to obtain from the License Store a license with a particular license ID.
WSB_LicenseStore_FindLicensesByContentIds is passed an array of one or more content IDs, and it finds in the License Store the license(s) with the specified content ID(s). It returns a SHI_Data object of type SHI_DATA_TYPE_ARRAY containing the licenses. If you call the method with an empty content ID list, all licenses in the License Store are returned, but please note that it is far better to instead call WSB_LicenseStore_EnumerateLicenses to obtain all the licenses and control which license-related data is included, especially if there are a large number of licenses in the License Store.
WSB_LicenseStore_FindContentIdsByLicense is passed a license ID and returns an array of the content ID(s) governed by the license with the specified license ID. WSB_LicenseStore_EnumerateLicenses obtains from the License Store an array of WSB_License objects representing all the licenses in the License Store. Since there may be a large number of licenses in the License Store, it may be useful to reduce the amount of memory required for each of the returned WSB_License objects by specifying, in a flags parameter, which license-related data (the bytes for the license, the expiration date, the insertion date, and/or the tag) you want to be included. Any such items whose corresponding value is not set in the flags parameter is not included. WSB_LicenseStore_EnumerateContentIds obtains from the License Store an array of all content IDs governed by the licenses in the License Store.
When you are done with the WSB_LicenseStore object, close the License Store and release the WSB_LicenseStore by calling WSB_LicenseStore_Close.
WSB_License Methods
A WSB_License object represents information about a license. WSB_License objects are created as a result of calls to WSB_LicenseStore_GetLicenseById (which obtains the license with a specified ID), WSB_LicenseStore_FindLicensesByContentIds, (which obtains the license or licenses with specified content IDs), and WSB_LicenseStore_EnumerateLicenses (which obtains all licenses in the License Store). The WSB_License objects are pointed to from a structure in the SHI_Data returned by those methods. The caller is responsible for calling SHI_Data_Release to release the SHI_Data object when it is no longer needed. The WSB_License objects pointed to from the SHI_Data are also freed by the SHI_Data_Release call.
WSB_License Methods
Various methods can be called to obtain information about a particular license. In most cases, the specified WSB_License passed as a parameter has the requested information and will return it. However, if the license was obtained as one of the licenses returned by a call to WSB_LicenseStore_EnumerateLicenses, and the flags in that call did not indicate that the specified type of information should be included, then a suitable return value, such as a NULL or a -1, indicates that such data is not available through that particular WSB_License.
Call WSB_License_GetId to obtain the license ID for the license referenced by a particular WSB_License.
WSB_License_GetExpirationDate returns the license expiration date. The value is represented as the number of UTC (Coordinated Universal Time) minutes since January 1, 1970.
WSB_License_GetInsertionDate returns the time when the license was added to the License Store. As with WSB_License_GetExpirationDate, the value is represented as the number of UTC minutes since January 1, 1970. WSB_License_GetData returns a byte array containing the bytes representing the license. WSB_License_GetTag returns the license tag for a specified license. A license tag can be set when a license is added to the License Store (see WSB_LicenseStore_AddLicense). It is simply a NULL-terminated character string that could, for example, indicate the name of the application that inserted the license. WSB_License_GetPriority is not currently used.
WSB_MediaFile and WSB_TrackInfo
A WSB_MediaFile object can be used to obtain information about a media file, to embed a license within a media file, and to obtain a license embedded within a media file.
Obtaining a WSB_MediaFile Object
Call WSB_MediaFile_Open to open a specified media file and create a WSB_MediaFile object referencing it. Pass it either a filename or an HTTP URL. If you know the MIME type, pass that as well.
Obtaining File Information
A WSB_MediaFile is used to obtain various types of information about the media file it references. WSB_MediaFile_GetName returns the file name, WSB_MediaFile_GetProtectionType returns the protection type (indicating whether it is a DCF file, a PDCF file, etc.), and WSB_MediaFile_GetContentType returns the file’s content type as a MIME type. The MIME type represents the media data type before protection. The following constants are defined for this property:
#define WSB_CONTENT_TYPE_VIDEO_MP4 "video/mp4"
#define WSB_CONTENT_TYPE_AUDIO_MP4 "audio/mp4"
#define WSB_CONTENT_TYPE_VIDEO_MPEG2TS "video/MP2T"
#define WSB_CONTENT_TYPE_DASH "application/dash+xml"
#define WSB_CONTENT_TYPE_HLS "application/vnd.apple.mpegurl"
#define WSB_CONTENT_TYPE_DCF "application/vnd.oma.drm.dcf"
#define WSB_CONTENT_TYPE_DECE "video/vnd.dece.mp4"
Note: MIME types are case-insensitive.
If the protection type is WSB_PROTECTION_TYPE_DCF, you can call WSB_MediaFile_GetContentId to obtain the content ID for the content in the media file. This is only relevant for DCF files, which have a single content ID, reported at the file level. For files that do not have a content ID at the file level (e.g., PDCF files, which report content IDs on a per-track basis), this method returns NULL. To obtain the content ID for a particular track in a PDCF file, create a WSB_TrackInfo object referencing the track (see “Track Related Methods” below) and then call WSB_TrackInfo_GetContentId.
Call WSB_MediaFile_GetProgress to find out the total number of bytes for the file (if known) and the number that are currently available, which are less than the total number if the file is currently being downloaded. If the type of media doesn’t enable gathering the total size information, such as in streaming, the total number of bytes reported is zero.
Call WSB_MediaFile_GetMetadata to obtain the metadata for the file, indicating information such as the album, artist, lyrics, copyright, and so on. You pass this method the address of a SHI_Attribute pointer that is set to point to a SHI_Attribute object containing the metadata. The SHI_Attribute type is SHI_ATTRIBUTE_TYPE_LIST, and each child of the list is an attribute for a single metadata item. The caller is responsible for calling SHI_Attribute_Release to release the SHI_Attribute object when it is no longer needed.
Obtaining Media Information
If you call WSB_MediaFile_GetMediaInfo, passing it a pointer to a WSB_MediaInfo structure, it will fill in that structure with the following information:
typedef struct WSB_MediaInfo {
WSB_MediaType type;
WSB_MediaFormat format;
WSB_UInt32 duration; /**< in ms */
WSB_UInt32 bitrate; /**< in bits/sec */
WSB_StreamEncryptionMethod encryption_method;
union {
WSB_AudioMediaInfo audio;
WSB_VideoMediaInfo video;
} specific;
} WSB_MediaInfo;
The structures referenced by WSB_MediaInfo are the following:
typedef enum {
WSB_MEDIA_TYPE_UNKNOWN = 0,
WSB_MEDIA_TYPE_AUDIO = 1,
WSB_MEDIA_TYPE_VIDEO = 2
} WSB_MediaType;
typedef enum {
WSB_MEDIA_FORMAT_UNKNOWN = 0,
WSB_MEDIA_FORMAT_AAC = 1,
WSB_MEDIA_FORMAT_AVC = 2,
WSB_MEDIA_FORMAT_MP3 = 3,
WSB_MEDIA_FORMAT_MPEG4_VID = 4
} WSB_MediaFormat;
typedef enum {
WSB_STREAM_ENCRYPTION_METHOD_UNKNOWN = 0,
WSB_STREAM_ENCRYPTION_METHOD_NULL = 1,
WSB_STREAM_ENCRYPTION_METHOD_AES_128_CBC = 2,
WSB_STREAM_ENCRYPTION_METHOD_AES_128_CTR = 3,
WSB_STREAM_ENCRYPTION_METHOD_DVB_CSA = 4
} WSB_StreamEncryptionMethod;
typedef struct WSB_AudioMediaInfo {
WSB_UInt32 channel_count;
WSB_UInt32 sample_rate;
WSB_UInt32 sample_size;
} WSB_AudioMediaInfo;
typedef struct WSB_VideoMediaInfo {
WSB_UInt32 width;
WSB_UInt32 height;
WSB_UInt32 depth;
} WSB_VideoMediaInfo;
Track-Related Methods
Call WSB_MediaFile_GetTrackCount to find out the number of tracks in the media file. To obtain information about a particular track, call WSB_MediaFile_GetTrackInfo. This method returns a WSB_TrackInfo object whose methods can be called to obtain various types ofinformation about the track.
License-Related Methods
If the license corresponding to the content in the file is embedded in the file, WSB_MediaFile_GetLicense returns the embedded license. If you have a license that you want embedded within a media file, call
WSB_MediaFile_SetLicense.
WSB_MediaFile_GetRightsIssuerUrls returns the rights issuer URLs corresponding to each content ID in a specified media file. You pass it a pointer to the relevant WSB_MediaFile, and the address of a SHI_Attribute pointer. This pointer is set to NULL if no rights issuer URLs are found. Otherwise, this method creates a SHI_Attribute of type
SHI_ATTRIBUTE_TYPE_LIST, and sets the passed-in SHI_Attribute pointer to refer to this SHI_Attribute. The names of the children in the list are content IDs, and for each, the value is of type SHI_ATTRIBUTE_TYPE_STRING and it contains the rights issuer URL associated with that content ID. The caller is responsible for releasing the SHI_Attribute when no longer needed (the children will automatically be released) by calling SHI_Attribute_Release. WSB_MediaFile_GetSilentLicenseAcquisitionUrls returns the silent license acquisition URLs contained in the media file, which are used as needed (e.g., by the Wasabi Rights Enabler) to attempt to obtain the license corresponding to the content in the file, if the license is not embedded within the file. Pass this method a pointer to the relevant WSB_MediaFile, and the address of a SHI_Attribute pointer. This pointer is set to NULL if no silent license acquisition URLs are found.
Otherwise, this method creates a SHI_Attribute of type SHI_ATTRIBUTE_TYPE_LIST, and sets the passed-in SHI_Attribute pointer to refer to this SHI_Attribute. The names of the children in the list are content IDs, and for each, the value is of type SHI_ATTRIBUTE_TYPE_STRING and it contains the silent license acquisition URL associated with that content ID. The caller is responsible for releasing the SHI_Attribute when no longer needed (the children will automatically be released) by calling SHI_Attribute_Release.
Releasing the WSB_MediaFile
When you no longer need a WSB_MediaFile object, close the media file it references and release the WSB_MediaFile by calling WSB_MediaFile_Close.
WSB_TrackInfo Methods
A WSB_TrackInfo object represents information about a track in a media file.
Obtaining WSB_TrackInfo Objects
WSB_TrackInfo objects are created as a result of calls to WSB_MediaFile_GetTrackInfo. You pass that method a WSB_MediaFile representing the file whose track information you want to obtain, and a 0-based index indicating the number of the track whose information you want.
WSB_TrackInfo Methods
Once you have a WSB_TrackInfo object, you can call WSB_TrackInfo_GetTrackId to obtain the track ID, and WSB_TrackInfo_GetContentId to obtain the content ID.
If you call WSB_TrackInfo_GetMediaInfo, and pass it a pointer to a WSB_MediaInfo structure, it will fill in the structure with the following information:
typedef struct WSB_MediaInfo {
WSB_MediaType type;
WSB_MediaFormat format;
WSB_UInt32 duration; /**< in ms */
WSB_UInt32 bitrate; /**< in bits/sec */
WSB_StreamEncryptionMethod encryption_method;
union {
WSB_AudioMediaInfo audio;
WSB_VideoMediaInfo video;
} specific;
} WSB_MediaInfo;
When you no longer need a WSB_TrackInfo, release it by calling WSB_TrackInfo_Release.
Media File-related Data Types
The enums and structures utilized by WSB_MediaFile and WSB_TrackInfo objects are the following:
WSB_ AudioMediaInfo Information about audio media
WSB_FileProgress Information about the total number of bytes in a file, and the number that are currently available, e.g., while a download is in progress
WSB_MediaFileProtectionType Identifiers for media file protection types
WSB_MediaFormat Identifiers for media formats
WSB_MediaInfo Comprehensive information about amedia file, including the WSB_MediaType, WSB_MediaFormat, etc.
WSB_MediaType Identifiers for the different media types
WSB_StreamEncryptionMethod Identifiers for stream encryption methods
WSB_VideoMediaInfo Information about video media
WSB_PlaybackEnabler
A WSB_PlaybackEnabler object can be used to do everything possible to obtain a valid license for a given WSB_MediaFile object or for content with a specified content ID (using an internal WSB_RightsEnabler object) and then exercise this license and populate a WSB_KeyManager object with the content keys.
Creating a WSB_PlaybackEnabler and Registering a Callback Listener
A WSB_PlaybackEnabler object is created by calling WSB_PlaybackEnabler_Create. You pass this method the address of a WSB_PlaybackEnabler pointer that is set to refer to the WSB_PlaybackEnabler object that the method creates. You also pass it a “listener” that is optionally notified of events occurring while the WSB_PlaybackEnabler_EnableMediaFile or WSB_PlaybackEnabler_EnableContentId method (see below) is looking for a valid license. If the listener’s handler method pointer is not NULL, the handler method is called for each event, and its event argument specifies the type of event and supplies event information. The listener is a WSB_RightsEnabler_EventListener which, is defined as follows:
typedef struct {
void* instance;
WSB_RightsEnabler_ContinuationType
(*handler)(void* instance,
const WSB_RightsEnabler_Event* event);
} WSB_RightsEnabler_EventListener;
As an example, if the event type is WSB_RIGHTSENABLER_EVENT_TYPE_LICENSE, the event data structure is a WSB_RightsEnabler_LicenseEvent, defined as follows:
typedef struct {
WSB_RightsEnabler_Event base;
WSB_Result processing_result;
const void* license_data;
WSB_Size license_data_size;
WSB_Boolean is_granted;
SHI_ActionResult* action_result;
} WSB_RightsEnabler_LicenseEvent;
This event contains the license, an indication of whether the license that was found grants access to the content, and a pointer to a SHI_ActionResult containing information about the license requirements, including constraints referred to as obligations and callbacks. The processing_result is WSB_SUCCESS if the license was processed correctly, WSB_ERROR_DRM_BAD_LICENSE_FORMAT if the license format is invalid, or WSB_ERROR_DRM_CLIENT_SYSTEM_ERROR if the license could not be processed because of a DRM system failure.
Obtaining a Content URL from an MS3 URL
MS3 (Marlin Simple Secure Streaming) compound URLs allow a client to retrieve both the content URL and the access rights to the content using the MS3 protocol. All MS3 URLs use the HTTPS scheme, and thus employ TLS to securely obtain content and rights.
If there is any possibility that the content URL is an MS3 URL, the caller should invoke WSB_PlaybackEnabler_ResolveUrl. This function returns the URL, unchanged, if it is not an MS3 URL. Otherwise, it retrieves the access rights (an SAS, Stream Access Statement) and extract from the compound URL the content URL, and returns the content URL to the caller. The application can then proceed with enabling the media file or content, as described in the next section. Regardless of whether or not the suppled URL is an MS3 URL, a content URL is returned, so the caller can use this value in any case. The return value is a SHI_Data object, of type SHI_DATA_TYPE_STRING. The content URL should be freed by the caller when no longer needed by calling SHI_Data_Release.
The WSB_PlaybackEnabler API does not otherwise handle content URLs. In the case of a media file or stream that is Marlin-defined, the content URL returned by this API should be passed to WSB_MediaFile_Open in order to open the specified content file and create a WSB_MediaFile object referencing it that can be passed to WSB_PlaybackEnabler_EnableMediaFile.
Obtaining a Valid License
Two different methods can be called to try to obtain a working license for content. Call WSB_PlaybackEnabler_EnableMediaFile if you can obtain a WSB_MediaFile referencing a media file or stream whose format is Marlin-defined—and thus Wasabi knows how to process it, for example to extract an embedded license or silent license acquisition URL, if any. Call WSB_PlaybackEnabler_EnableContentId when you cannot obtain such a WSB_MediaFile, but a license or MS3 SAS may grant access to content with a specified ID. It is useful, for example, for situations where the content ID changes during streaming. It is also useful in situations where a license grants access to content in a media file whose format is not Marlin defined. In this case, an application can use this method to find such a license and can subsequently use the Wasabi Key Manager to obtain the content key(s) from the license.
For either method, if you actually already have a license for the content, and you want the method to check whether that license is currently valid, then you can optionally also pass the license bytes and a value indicating the number of bytes.
WSB_PlaybackEnabler_EnableMediaFile calls WSB_MediaFile and WSB_TrackInfo methods to obtain the content IDs and track IDs (if it is a type of file that has tracks) for the media file. If no errors occur, it then calls WSB_RightsEnabler_EnableMediaFile, which performs a number of steps to do everything possible to try to find a currently valid license.
WSB_PlaybackEnabler_EnableContentId calls WSB_RightsEnabler_EnableContentId.
Prior to calling either of those methods, you can optionally call WSB_PlaybackEnabler_SetRightsEnablerOptions to set option flags controlling WSB_RightsEnabler_EnableMediaFile behavior. By default, all flags are off. The possible flags, defined in WsbRightsEnabler.h, are named the following:
WSB_RIGHTSENABLER_OPTION_FLAG_DISABLE_SLA
WSB_RIGHTSENABLER_OPTION_FLAG_DISABLE_LINK_RENEWAL
WSB_RIGHTSENABLER_OPTION_FLAG_DISABLE_STORING_LICENSE_IN_FILE
Each of these flags disables a particular potential action normally taken by WSB_RightsEnabler_EnableMediaFile or WSB_RightsEnabler_EnableContentId.
Whenever WSB_RightsEnabler_EnableMediaFile or WSB_RightsEnabler_EnableContentId finds a license, the license is supplied to the Playback Enabler in an event of type WSB_RIGHTSENABLER_EVENT_TYPE_LICENSE. The Playback Enabler stores the license data and also forwards the event to the client listener passed in the call to WSB_PlaybackEnabler_Create, if the listener handler method pointer is not NULL. This case is described first, followed by information regarding what happens if it is NULL.
The listener should determine whether it is satisfied with the specified license (in which case, it returns WSB_RIGHTSENABLER_HALT_SEARCH) or would prefer to have the search continue for another license (in which case, it returns WSB_RIGHTSENABLER_CONTINUE_SEARCH).
In order to examine the license, it is common to call WSB_Config_ValidateActionResult. If the Play action is granted and all mandatory obligations and callbacks can be satisfied, WSB_Config_ValidateActionResult returns WSB_SUCCESS. If the action is not granted, it returns WSB_ERROR_DRM_DENY_RIGHTS, and it returns WSB_ERROR_DRM_LICENSE_UNSUPPORTED if there are mandatory callbacks or obligations that cannot be satisfied.
If the action is not granted, you can call WSB_ExplainActionResultFailure to obtain an error code with a more explicit explanation as to why the action is not granted (such as the license having expired).
Here is an example of how a listener handler method might handle a license event:
static WSB_RightsEnabler_ContinuationType
OnEnablerEvent(void* instance, const WSB_RightsEnabler_Event* event)
{
WSB_Result result;
// we're only interested in license data
if (event->type == WSB_RIGHTSENABLER_EVENT_TYPE_LICENSE) {
WSB_RightsEnabler_LicenseEvent* license_event =
(WSB_RightsEnabler_LicenseEvent*)event;
if (WSB_FAILED(license_event->processing_result)) {
// license just could not be processed; handle this case
. . .;
} else {
result = WSB_Config_ValidateActionResult(
license_event->action_result,
WSB_FALSE,
SHI_ACTION_PLAY,
NULL);
if (WSB_SUCCEEDED(result)) {
// we've found a valid license!
return WSB_RIGHTSENABLER_HALT_SEARCH;
} else if (result == WSB_ERROR_DRM_DENY_RIGHTS) {
// play action not granted, get more information
SHI_Data* goto_url_data = NULL;
result = WSB_ExplainActionResultFailure(
license_event->action_result,
NULL,
media_file,
&goto_url_data);
/* if desired, save or further process this result */
. . .
}
}
}
return WSB_RIGHTSENABLER_CONTINUE_SEARCH;
}
If the listener handler
method is NULL, then when a license is supplied to the Playback
Enabler, the Playback Enabler does the same things as the client listener would typically do
(described above), except that it always returns WSB_RIGHTSENABLER_HALT_SEARCH if
WSB_Config_ValidateActionResult returns WSB_SUCCESS. In other words, the default
behavior is that the Playback Enabler always stops the search as soon as any valid license is
found.
In the case of MS3 content, the WSB_PlaybackEnabler_EnableMediaFile or WSB_PlaybackEnabler_EnableContentId method should still be invoked, and a NULL license_data parameter should be supplied. The rights acquired during the call to WSB_PlaybackEnabler_ResolveURL is used to enable the media file.
Checking the License and Obtaining Keys
At any point after calling WSB_PlaybackEnabler_EnableMediaFile or WSB_PlaybackEnabler_EnableContentId, and prior to attempting content playback, call WSB_PlaybackEnabler_PerformPlayAction to perform the “Play” action on the license (or, in the case of MS3 content, the access rights). Essentially, this determines whether the Play action is currently allowed, and creates a SHI_ActionResult containing information about what is allowed and what is required. WSB_PlaybackEnabler_PerformPlayAction does not actually play the content, but it must be executed as one of the steps prior to playing the content. Pass this method the address of a SHI_ActionResult pointer that will be set to refer to the SHI_ActionResult object created by this call. A WSB_Result of WSB_SUCCESS does not mean that the Play action is granted. The SHI_ActionResult returned by the call must be analyzed to determine that, as described in the following.
The SHI_ActionResult should be analyzed by calling WSB_Config_ValidateActionResult. As stated above, this method is responsible for analyzing the SHI_ActionResult to determine whether the Play action is granted, and whether all of the mandatory obligations and callbacks (if any) can be satisfied.
WSB_Config_ValidateActionResult returns WSB_SUCCESS if the application should be allowed access to the protected content. In this case, call WSB_KeyManager_Create to instantiate a WSB_KeyManager object, and then call WSB_PlaybackEnabler_AcceptActionResult, passing it the WSB_KeyManager. This method obtains the content keys and supply them to the WSB_KeyManager. This method also has an optional parameter specifying the address of a SHI_Action pointer which, if specified, is set to refer to the internal SHI_Action object, which may be needed for exercising callbacks. The caller does not take ownership, and therefore must not destroy this SHI_Action.
Destroying the WSB_PlaybackEnabler
When the WSB_PlaybackEnabler object is no longer needed, destroy it by calling WSB_PlaybackEnabler_Destroy.
WSB_ProxyManager
A WSB_ProxyManager object can be used to set global (per application) proxies for HTTP and HTTPS transactions.
Call WSB_ProxyManager_SetHttpProxy to set the proxy to be used for HTTP transactions. You pass it the hostname and port.
Call WSB_ProxyManager_SetHttpsProxy to set the proxy to be used for HTTPS transactions. As with WSB_ProxyManager_SetHttpProxy, you pass the hostname and port to be used.
Here is how proxy selection works in ExpressPlay SDK:
- If the proxies have been set using the WSB_ProxyManager methods, then those proxies are utilized.
- For convenience in testing and in running sample applications (not for running applications in production environments), it is possible to use a proxy without modifying the code to call the WSB_ProxyManager methods. If proxies are not set, as described above, or are unusable (e.g., set to an empty string), ExpressPlay SDK checks the values of certain environment variables, as described in the following, to try to determine what proxies to use, if any.
- An environment variable named NEPTUNE_NET_CONFIG_PROXY_SELECTOR can have 3 values. If it is set to no proxy (or the variable does not exist), then no proxy is used. If it is set to system, then that indicates that the system’s default proxies (if set) should be used. If it is set to env, then the proxy set in the http_proxy environment variable is used for HTTP transactions, and the proxy set in the https_proxy environment variable is used for HTTPS transactions.
- However, there are two other environment variables whose values affect which proxy is actually used, and even whether a proxy is used. The NO_PROXY environment variable contains a comma-separated list of hosts for which no proxy should be used. If it has the special value of “*”, then no proxies are used for any hosts. The environment variable named ALL_PROXY specifies the proxy that should be used if the proxy hostname set in http_proxy or https_proxy is invalid.
Please note that http_proxy must be all lowercase, but https_proxy, no_proxy, and all_proxy can each be either all lowercase or all uppercase.
WSB_RightsEnabler
A WSB_RightsEnabler object can be used to have everything possible done to obtain a license granting access to specified content. A WSB_RightsEnabler object is created and utilized internally as needed by other Wasabi classes, such as WSB_PlaybackEnabler, and the Rights Enabler functionality is also available to external applications via the WSB_RightsEnabler methods.
Creating a WSB_RightsEnabler Object
A WSB_RightsEnabler object is created by calling WSB_RightsEnabler_Create. You pass it the address of a WSB_RightsEnabler pointer that is set by this method to refer to the WSB_RightsEnabler object that the method creates. You also pass it a “listener” that is notified of events occurring while the WSB_RightsEnabler_EnableMediaFile or WSB_RightsEnabler_EnableContentId method (see below) is looking for a valid license. The listener’s handler method is called for each event, and its event argument specifies the type of event and supplies event information, as discussed below under “Event Structures.” The listener has the following type:
typedef struct {
void* instance;
WSB_RightsEnabler_ContinuationType
(*handler)(void* instance,
const WSB_RightsEnabler_Event* event);
} WSB_RightsEnabler_EventListener
The handler return value is of the following type:
typedef enum {
WSB_RIGHTSENABLER_HALT_SEARCH,
WSB_RIGHTSENABLER_CONTINUE_SEARCH
} WSB_RightsEnabler_ContinuationType;
The return value is primarily relevant for license events. When such an event occurs, indicating that a license was found, the handler can decide whether it wants the Rights Enabler to stop looking or to continue the search for a different license (as discussed further below), and the handler indicates its choice by the appropriate return value. For other events, the return value is considered more of a request, and a request to halt may or may not be satisfied due to internal reasons.
Event Structures
WSB_RightsEnabler_Event is the base class for all event structures. It is defined as follows:
typedef struct {
WSB_RightsEnabler_EventType type;
} WSB_RightsEnabler_Event;
The types of events that may be reported to the listener are the following:
typedef enum {
WSB_RIGHTSENABLER_EVENT_TYPE_GOTO_URL,
WSB_RIGHTSENABLER_EVENT_TYPE_TRANSACTION,
WSB_RIGHTSENABLER_EVENT_TYPE_LICENSE
} WSB_RightsEnabler_EventType;
When the listener is called, it is passed a pointer to a WSB_RightsEnabler_Event. A more detailed structure, with WSB_RightsEnabler_Event as the base, is actually passed to the listener, and this structure depends on the event type. The event pointer should be cast to the appropriate data structure type. The data structures to which events of various types should be cast are the following:
Event Type | Data Structure |
WSB_RIGHTSENABLER_EVENT_TYPE_
GOTO_URL |
WSB_RightsEnabler_GotoURLEvent |
WSB_RIGHTSENABLER_EVENT_TYPE_
LICENSE |
WSB_RightsEnabler_LicenseEvent |
WSB_RIGHTSENABLER_EVENT_TYPE_
TRANSACTION |
WSB_RightsEnabler_TransactionEvent |
If the Rights Enabler has been directed to a service for obtaining needed rights, the WSB_RightsEnabler_GotoURLEvent reports this fact and specifies the URL that the Rights Enabler was directed to.
typedef struct {
WSB_RightsEnabler_Event base;
const char* url;
} WSB_RightsEnabler_GotoURLEvent;
Whenever the Rights Enabler finds a license, it calls the listener with a WSB_RightsEnabler_LicenseEvent:
typedef struct {
WSB_RightsEnabler_Event base;
WSB_Result processing_result;
const void* license_data;
WSB_Size license_data_size;
WSB_Boolean is_granted;
SHI_ActionResult* action_result;
} WSB_RightsEnabler_LicenseEvent;
This event contains the license, an indication of whether the license that was found grants access to the content, and a pointer to a SHI_ActionResult that contains information about the license requirements, including constraints referred to as obligations and callbacks. The processing_result is WSB_SUCCESS if the license was processed correctly, WSB_ERROR_DRM_BAD_LICENSE_FORMAT if the license format is invalid, or WSB_ERROR_DRM_CLIENT_SYSTEM_ERROR if the license could not be processed because of a DRM system failure.
When the listener receives an event of this type, it should decide whether it is satisfied with the specified license or would prefer to have the Rights Enabler continue its search for another license. In the former case, the listener handler method should return WSB_RIGHTSENABLER_HALT_SEARCH. Otherwise it should return WSB_RIGHTSENABLER_CONTINUE_SEARCH. In order to determine whether or not to continue the search, or simply to get information about the license requirements, the listener may want to analyze the SHI_ActionResult. It can do so, for example, by calling WSB_Config_ValidateActionResult to determine whether content playback is allowed and whether all of the mandatory obligations and callbacks (if any) can be satisfied.
A WSB_RightsEnabler_TransactionEvent indicates that a service transaction (link renewal or silent license acquisition) has been initiated in an attempt to obtain rights.
typedef struct {
WSB_RightsEnabler_Event base;
WSB_RightsEnabler_TransactionType transaction_type;
} WSB_RightsEnabler_TransactionEvent;
Obtaining a Valid License
The purpose of the WSB_RightsEnabler object is to try everything possible to obtain a working license for content with one or more specified IDs. Call WSB_RightsEnabler_EnableMediaFile or WSB_RightsEnabler_EnableContentId to invoke this functionality. The former is called when you can create a WSB_MediaFile referencing a media file or stream whose format is Marlin-defined—and thus Wasabi knows how to process it, for example to extract an embedded license or silent license acquisition URL, if any. The latter is called when you cannot create such a WSB_MediaFile. It is useful, for example, for situations where the content ID changes during streaming. It is also useful in situations where a license grants access to content in a media file whose format is not Marlin-defined.
First, prior to calling either of those methods, you can optionally call WSB_RightsEnabler_SetOptions to set option flags controlling their behavior, disabling one or more actions normally taken (which are described below). By default, all flags are off. The flags that can be set are the following:
WSB_RIGHTSENABLER_OPTION_FLAG_DISABLE_SLA
If this flag is set, no silent license acquisitions are attempted.
WSB_RIGHTSENABLER_OPTION_FLAG_DISABLE_LINK_RENEWAL
If this flag is set, no subscription links are renewed.
WSB_RIGHTSENABLER_OPTION_FLAG_DISABLE_STORING_LICENSE_IN_FILE
If this flag is set, WSB_RightsEnabler_EnableMediaFile does not attempt to write a valid license to the media file.
WSB_RightsEnabler_EnableMediaFile
Prior to calling WSB_RightsEnabler_EnableMediaFile, you create a WSB_MediaFile referencing the media file or stream whose content you want to find a license for, and you obtain from that media file/stream the content ID(s). You then pass WSB_RightsEnabler_EnableMediaFile the WSB_MediaFile, an array of the content IDs, and a number indicating how many content IDs are in the array. If you actually already have a license for the content, and you want this method to check whether that license is currently valid, then you can optionally also pass the license bytes and a value indicating the number of bytes.
WSB_RightsEnabler_EnableMediaFile performs the steps below, in order, in an attempt to find a license for the content with the specified content IDs. For each license it finds, it returns the license to the caller in a WSB_RightsEnabler_LicenseEvent structure passed to the listener registered in the call to WSB_RightsEnabler_Create. In that event structure, the Rights Enabler also provides the information as to whether or not the license grants access, and supplies the SHI_ActionResult containing further information about the license requirements, as discussed in the previous section. The listener returns to the Rights Enabler a WSB_RightsEnabler_ContinuationType value instructing the Rights Enabler whether to halt the search or to continue searching for another license. If the latter, the Rights Enabler continues the search steps shown below. Otherwise, the search is stopped.
If the most recent license found before the search was halted is a valid one, and WSB_RIGHTSENABLER_OPTION_FLAG_DISABLE_STORING_LICENSE_IN_FILE is not set, WSB_RightsEnabler_EnableMediaFile then tries to write the license to the media file, if it is not already embedded within that media file. If WSB_RightsEnabler_EnableMediaFile cannot embed the license within the media file, or the specified option is set, WSB_RightsEnabler_EnableMediaFile stores the license in the Wasabi License Store. In any case, it then returns to the caller.
Here are the steps WSB_RightsEnabler_EnableMediaFile performs to try to find a currently valid license:
- Check the license that was passed in (if any).
- Check the license embedded in the media file (if any).
- Check the License Store to see whether it contains a license for the content.
- If WSB_RIGHTSENABLER_OPTION_FLAG_DISABLE_LINK_RENEWAL is not set, try automatic renewal of the subscription (if any) to the content, and recheck all of the licenses found up to this point—the passed-in license, the embedded license, or a license in the License Store, in that order.
- If the media file/stream has a silent license acquisition header, and WSB_RIGHTSENABLER_OPTION_FLAG_DISABLE_SLA is not set, then use the specified URL to attempt to automatically acquire a license. Check whether the returned license is valid. If not, try updating the subscription referenced by the license (if any) and then check (and report in a license event) the license again.
Please note: During WSB_RightsEnabler_EnableMediaFile execution, control programs that govern access to content will be executed. There is a limit of 40 million execution steps allowed, and if that number is reached (which would only happen if there was a control program bug resulting in an infinite loop), WSB_RightsEnabler_EnableMediaFile returns WSB_ERROR_DRM_DENY_RIGHTS.
WSB_RightsEnabler_EnableContentId
WSB_RightsEnabler_EnableContentId essentially performs the same actions as WSB_RightsEnabler_EnableMediaFile to find a license, except that in this case, there is no WSB_MediaFile, therefore, no media file-related actions are performed by the method. As with WSB_RightsEnabler_EnableMediaFile, WSB_RightsEnabler_EnableContentId returns each license it finds to the caller in a WSB_RightsEnabler_LicenseEvent structure passed to the listener registered in the call to WSB_RightsEnabler_Create.
Here are the steps taken by WSB_RightsEnabler_EnableContentId to try to find a valid license:
- Check the license that was passed in (if any).
- Check the License Store to see whether it contains a license for the content.
- If WSB_RIGHTSENABLER_OPTION_FLAG_DISABLE_LINK_RENEWAL is not set, try automatic renewal of the subscription (if any) to the content, and recheck all of the licenses found up to this point—the passed-in license or a license in the License Store, in that order.
- If a silent license acquisition URL was passed in the call, and WSB_RIGHTSENABLER_OPTION_FLAG_DISABLE_SLA is not set, then use the specified URL to attempt to automatically acquire a license. Check whether the returned license is valid. If not, try updating the subscription referenced by the license (if any) and then check (and report in a license event) the license again.
Destroying the WSB_RightsEnabler
When the WSB_RightsEnabler object is no longer needed, destroy it by calling WSB_RightsEnabler_Destroy.
Rights Enabler-related Data Types
The enums and structures utilized by WSB_RightsEnabler objects are the following:
WSB_RightsEnabler_Event |
Base structure for all event structures |
WSB_RightsEnabler_EventListener |
Structure pairing a listener instance value with a function pointer for receiving events |
WSB_RightsEnabler_EventType |
Identifiers for event types |
WSB_RightsEnabler_GotoURLEvent |
Event data for reporting that the Rights Enabler has been directed to a service for obtaining the license |
SB_RightsEnabler_LicenseEvent |
Event data for reporting a valid license that was found |
WSB_RightsEnabler_TransactionEvent |
Event data for reporting that a data service transaction has been initiated in an attempt to obtain the needed rights |
WSB_RightsEnabler_TransactionType |
Identifiers for transactions |