The NexGuard library forensic watermarking tool QuickMark embeds a unique, invisible serial number onto video/audio content. It functions in two modes:
Push mode
The application retrieves the QuickMark blob (for example, through a gateway) and pushes it to the SDK.
Pull mode
The application configures the QuickMark client SDK to access a QuickMark server. The SDK handles the communication with the server to retrieve the blob.
The minimum Android API for using the watermarking feature is 21 (Lollipop 5.0).
Watermark requires Android System Webview Version 42 or higher to be installed on the device.
Having any UI element rendered over the OTVVideoView
may prevent the watermark from working properly.
Prerequisites
A working watermark requires the following parameters to work correctly:
A context to call Watermark
constructor.
A valid OTVVideoView
to bind the watermark to.
A valid customer/system generated token of type String
.
A valid URL of type String
.
A valid secret key of type String
.
The last optional value is to set the ApiKey
which is also of type String
. This parameter is not mandatory but may be required depending on the server deployment - it is used to restrict access to the server to authorized users.
If these values are available, a working QuickMarkView
can be created.
The core class of the watermarking function is Watermark
. Its purpose is to take the above parameters passed by the user to create a new object of the QuickMarkView
class and bind it to the provided OTVVideoView
before starting the watermark playback. The class is also responsible for handling error message events and starting and stopping the QuickMarkView
whenever video is played or paused.
Currently, cases that trigger an error are:
The following errors do not get thrown (errors are silently ignored):
Invalid URL string
Incorrect token value
Dependencies
The QuickMark library is embedded within the SDK .aar library but brings a dependency on the OkHttp library. Include the following in the dependencies block in your Gradle build script:
GROOVY
dependencies {
…
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
…
}
Example code
Attaching a watermark is quite simple if you have the prerequisite parameters above. Since OTVVideoView
is required to bind and unbind the QuickMarkView
, NAGRA recommends you tie the watermark lifecycle to the OTVVideoView
lifecycle. Brief examples of implementing the watermark feature in each mode are shown below.
QuickMark Pull mode
JAVA
…
import nagra.otv.sdk.OTVVideoView;
import nagra.otv.sdk.watermark.Watermark;
import nagra.otv.sdk.watermark.WatermarkErrorListener;
import nagra.otv.sdk.watermark.WatermarkMessageListener;
public class MainActivity extends Activity {
…
private static final String STREAM_URI = "https://example.stream.address";
private static final String WATERMARK_URL = "https://example.watermark.address";
private static final String WATERMARK_TOKEN = "WATERMARK_TOKEN";
private static final String WATERMARK_API_KEY = "API_KEY";
private static final String WATERMARK_SECRET_KEY = "SECRET_KEY";
private FrameLayout mFrame = null;
private OTVVideoView mOTVVideoView = null;
private Watermark mWatermark;
private WatermarkErrorListener mWatermarkErrorListener = (watermarkErrorId, s) ->
Log.d(TAG, "Watermark error: " + s);
private WatermarkMessageListener mWatermarkMessageListener = s ->
Log.d(TAG, "Watermark message: " + s);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
…
mFrame = findViewById(R.id.frame);
if (mOTVVideoView == null) {
mOTVVideoView = new OTVVideoView(this);
mOTVVideoView.setVideoPath(STREAM_URI);
mFrame.addView(mOTVVideoView);
}
mWatermark = Watermark.createInPullMode(this, WATERMARK_URL, WATERMARK_TOKEN, WATERMARK_API_KEY, WATERMARK_SECRET_KEY);
if (mOTVVideoView != null && mWatermark != null) {
mWatermark.addErrorListener(mWatermarkErrorListener);
mWatermark.addMessageListener(mWatermarkMessageListener);
mWatermark.bind(mOTVVideoView);
}
}
…
//Match the watermark lifecycle with the video view lifecycle.
@Override
public void onDestroy() {
super.onDestroy();
if (mOTVVideoView != null && mWatermark != null) {
mWatermark.removeErrorListener(mWatermarkErrorListener);
mWatermark.removeMessageListener(mWatermarkMessageListener);
mWatermark.unbind(mOTVVideoView);
}
}
}
All mWatermark
configuration must be set before the binding call.
QuickMark Push mode
JAVA
…
import nagra.otv.sdk.watermark.WatermarkErrorId;
import nagra.otv.sdk.OTVLog;
import nagra.otv.sdk.OTVSDK;
import nagra.otv.sdk.OTVVideoView;
import nagra.otv.sdk.watermark.Watermark;
import nagra.otv.sdk.watermark.WatermarkErrorListener;
import nagra.otv.sdk.watermark.WatermarkMessageListener;
public class MainActivity extends Activity {
…
private static final String SECRET_KEY = "SECRET_KEY";
private OTVVideoView mOTVVideoView = null;
private Watermark mWatermark;
private WatermarkMessageListener mWMMessageListener = message ->
Log.i(TAG, "Watermark: " + message);
private WatermarkErrorListener mWMErrorListener = (errorId, message) ->
Log.e(TAG, "Watermark error: " + errorId + "," + message);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
…
//Create and config Watermark in push mode
mWatermark = Watermark.createInPushMode(this, SECRET_KEY);
if (mWatermark != null) {
mWatermark.addMessageListener(mWMMessageListener);
mWatermark.addErrorListener(mWMErrorListener);
mWatermark.bind(mOTVVideoView);
} else {
Toast.makeText(getBaseContext(), "Create watermark failed.", Toast.LENGTH_LONG).show();
}
…
}
// You may want to update the watermark periodically, or upon some external event
private void updateBlob(byte[] blobData) {
boolean bSuccess = mWatermark.setBlob(blobData, true);
if (!bSuccess) {
//The Application can stop playback according to own rules.
} else {
// The update is successful
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mOTVVideoView != null) {
mWatermark.unbind(mOTVVideoView);
}
}
}