Skip to content

NCG Android SDK Guide

DoveRunner NCG Android SDK makes it easy to apply INKA’s NCG(Netsync Content Guard) DRM when developing media service apps for Android. This document describes how to use the libraries and sample project included in the SDK.

Details of DoveRunner Multi DRM service integration are provided in License Token Guide guides. For technical questions about using the SDK, please visit our Helpdesk site.

This video is a tutorial for playing DRM content using the sample project included in the SDK.

For optimal playback, select ‘1080p’ as the video quality and enable subtitle (Korean or English) before starting playback.

NOTE : This document is prepared in accordance with the following environment.

  • Android 5.0 (Lollipop) or later

To apply DoveRunner NCG Android SDK to your project, copy the SDK files to your local Maven repository and configure your Gradle build files.

Step 1: Copy files to your local Maven repository

  • On macOS: Copy to ${HOME}/.m2/repository/com/doverunner/ncg/
  • On Windows: Copy to %USERPROFILE%\.m2\repository\com\doverunner\ncg\
Terminal window
# macOS
cp -R libs/* ${HOME}/.m2/repository/com/doverunner/ncg/
Terminal window
:: Windows
xcopy libs\* %USERPROFILE%\.m2\repository\com\doverunner\ncg\ /E /I

Step 2: Configure your Gradle repositories

Add mavenLocal() as the first repository in your build.gradle or settings.gradle:

repositories {
mavenLocal() // Add this as the first repository
google()
mavenCentral()
}

Step 3: Add the SDK dependency

dependencies {
implementation 'com.doverunner:ncg:x.x.x'
}
### AndroidManifest.xml Permission Setting
For the use of DoveRunner Android SDK, the below permissions are required:
|Item | Description |
|:------- | :----- |
|READ_LOGS | Permission to read LOG |
|INTERNET | Permission to use network |
|WRITE_EXTERNAL_STORAGE| Permission to use external storage |
|READ_PHONE_STATE | Permission to read the status of device |
|MOUNT_UNMOUNT_FILESYSTEMS | Permission to edit file system |
## Initialization
Only one NCG Core object is generated as a single tone.
```java
public class DemoLibrary {
static Ncg2Agent g_ncgAgent = Ncg2SdkFactory.getNcgAgentInstance();
public final static Ncg2Agent getNcgAgent() {
return g_ncgAgent;
}
...
}

In order to use Ncg2Agent object, it should be initialized by init() method. In general, init() method of Ncg2Agent may be called when the app starts(onCreate of android.app.Application).

ItemDescription
NotSupportOfflineNot support offline mode
OfflineSupportSupport offline mode

If you set the offline policy to OfflineSupport then you can limit the count of app execution. The default limit count is 10 and the value can be changed by calling the setCountOfExecutionLimit method in OfflineSupportPolicy enum class.

public class DemoApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initializeNcgAgent();
}
private void initializeNcgAgent() {
try {
DemoLibrary.getNcgAgent().setHttpRequestCallback(mHttpRequestCallback);
Ncg2Agent.OfflineSupportPolicy policy = Ncg2Agent.OfflineSupportPolicy.OfflineSupport;
policy.setCountOfExecutionLimit(20);
DemoLibrary.getNcgAgent().init(this, policy);
DemoLibrary.getNcgAgent().enableLog();
} catch (Ncg2Exception e) {
e.printStackTrace();
String errorMsg = "Exception in init() : " + e.getMessage();
Log.e(DemoLibrary.TAG, errorMsg);
Toast.makeText(this, errorMsg, Toast.LENGTH_LONG).show();
}
}
...
}

Controlling HTTP communication inside DoveRunner NCG Android SDK requires implementing HttpRequestCallback in App and then initializing it before setting callback. You need to call Ncg2Agent.setHttpRequestCallback method to set HttpRequestCallback object.

public class DemoApplication extends Application{
private Ncg2Agent.HttpRequestCallback mHttpRequestCallback = new Ncg2Agent.HttpRequestCallback() {
@Override
public String sendRequest(String url, String param){
...
}
@Override
public byte[] sendRequestResponseBytes(String url, String param) throws NcgHttpRequestException {
...
}
@Override
public byte[] sendRequest(String url, String param, int begin, int end) throws NcgHttpRequestException {
...
}
}
private void initializeNcgAgent() {
...
DemoLibrary.getNcgAgent().setHttpRequestCallback(mHttpRequestCallback);
...
}
}

A DRM license is required to play DRM-protected content. Licensed data contains the rights for content including usage period and whether TV-out is permitted, etc. So the service app should acquire DRM license from license server before playing the content. The below functions are provided to check the license data:

interface Ncg2Agent{
// Confirm whether a file situated in the entered path and URL is NCG file.
public boolean isNcgContent(String strFileOrURL) throws Ncg2Exception;
// Confirm the license of file situated in the entered path and URL for validity.
public boolean isLicenseValid(String strFileOrURL) throws Ncg2Exception;
// Confirm the license of content for validity.
public LicenseValidation checkLicenseValid(String path) throws Ncg2Exception;
}

The LicenseValidation enum type returned from checkLicenseValid method is as follows:

ItemDescription
ValidLicenseThe license is valid.
NotExistLicenseNo license or invalid license
ExternalDeviceDisallowedLicense with external output device disallowed
RootedDeviceDisallowedLicense disallowed the use of rooted device
ExpiredLicenseThe license is expired.
DeviceTimeModifiedDetected device time manipulation
OfflineNotSupportedFailed to access server and detected offline while initializing under the policy of not supporting offline mode
OfflineStatusTooLongOnline connection is needed because of too many counts of execution offline. For more information, please refer to the following note.
NotAuthorizedAppIDFailed to initialize since the executing App is not authenticated in server.
ScreenRecorderDetectedDetected a screen recording app

In Sample Project, confirm license for validity in the following order:

if( DemoLibrary.getNcgAgent().isNcgContent(path) == false ) {
return true;
}
Ncg2Agent.LicenseValidation validation = DemoLibrary.getNcgAgent().checkLicenseValid(path);
if( validation == LicenseValidation.ValidLicense ) {
// Routine to process license for validity
...
} else if( validation == LicenseValidation.NotExistLicense ) {
// No license or invalid license
...
// Request license to license server
DemoLibrary.getNcgAgent().acquireLicenseByPath( path, DemoLibrary.getUserID(), DemoLibrary.getOrderID() );
if( DemoLibrary.getNcgAgent().checkLicenseValid(path) == LicenseValidation.ValidLicense ) {
// Acquire valid license from server
} else {
// Not acquire license from server
}
} else if( validation == LicenseValidation.ExternalDeviceDisallowed) {
// Not allow to play to the connection to external device such as HDMI device
...
} else if( validation == LicenseValidation.RootedDeviceDisallowed) {
// License not allowed in Rooting terminal.
...
} else if(validation == LicenseValidation.ExpiredLicense){
// Expired license
...
} else if(validation == LicenseValidation.ScreenRecorderDetected {
// You can check the package name and the appName of screen recorder app.
HashMap<String,String> data = validation.getExtraData();
String appName = data.get("AppName");
String packageName = data.get("AppPackageName");
...
}

NOTE : To detect the device time manipulation, check if it executes more than limit value set by setCountOfExecutionLimit method in the internal offline mode, OfflineStatusTooLong value is returned. OfflineStatusTooLong value is returned only when it is set as OfflineSupport parameter option at init() method called.

DRM license may be acquired at any time before playing the content; however, SDK Sample App Project tries to issue license at the start of playback. The timing to issue license depends on App scenario. The below functions are provided in DoveRunner NCG Android SDK to issue license:

interface Ncg2Agent{
// Acquire license via the entered path with UserID and OrderID.
public void acquireLicenseByPath(String path, String userId, String orderId) throws Ncg2Exception;
// Acquire license via the entered path with UserID and OrderID, there can be acquired temporary license by setting a temporary.
public abstract void acquireLicenseByPath( String path, String userID, String orderID, boolean temporary ) throws Ncg2Exception;
// Acquire license using Content ID.
public void acquireLicenseByCID(String cid, String userID, String orderID, String acquisitionURL) throws Ncg2Exception;
// Acquire license using Content ID.
public void acquireLicenseByCID(String cid, String userID, String siteID, String orderID, String acquisitionURL) throws Ncg2Exception;
// Acquire license using Content ID, there can be acquired temporary license by setting a temporary.
public abstract void acquireLicenseByCID(String cid, String userID, String siteID, String orderID, String acquisitionURL, boolean temporary ) throws Ncg2Exception;
}

NOTE : To support offline scenario, it is needed to acquire license when the content is downloaded online.

One-time license may be removed at any time after use. In the SDK Sample, license is removed when the video play is stopped. The below functions are provided in DoveRunner NCG Android SDK:

interface Ncg2Agent{
// Delete license via all CIDs of the issued licenses.
public void removeLicenseAllCID() throws Ncg2Exception;
// Delete temporary license.
public abstract void removeAllTemporaryLicense() throws Ncg2Exception;
// Delete license via CID.
public void removeLicenseByCID(strContentsID) throws Ncg2Exception;
// Delete license of the content in the file path.
public void removeLicenseByPath(strFilename) throws Ncg2Exception;
}

To play content after acquiring license, you can control the local web server by using Ncg2LocalWebServer interface. When you specify the path of DRM content in the playback API, the virtual URL of the local web server is returned, and then you can play the URL with MediaPlayer or third party player. Below is the virtual URL functions provided by Ncg2LocalWebServer.

interface Ncg2LocalWebServer{
// In case of Local File,
public String addLocalFilePathForPlayback(Activity activity, String url, long fileSize) throws Ncg2InvalidLicenseException, Ncg2Exception;
// In case of PD contents,
public String addProgressiveDownloadUrlForPlayback(Activity activity, String url) throws Ncg2InvalidLicenseException, Ncg2Exception;
// In case of HLS,
public String addHttpLiveStreamUrlForPlayback(Activity activity, String url) throws Ncg2InvalidLicenseException, Ncg2Exception;
// In case of HLS (live),
public String addHttpLiveStreamUrlForPlayback(Activity activity, String url, boolean isLiveHLS) throws Ncg2InvalidLicenseException, Ncg2Exception;
// In case of HLS,
// unlike addHttpLiveStreamUrlForPlayback(), do not prior check key file equivalent to m3u8 path.
public String addHttpLiveStreamUrlForPlaybackWithoutChecking(Activity activity, String url) throws Ncg2InvalidLicenseException, Ncg2Exception;
// In case of HLS,
// You can call this method to add HLS url without accessing the URLs of m3u8 and key file.
public String addHttpLiveStreamUrlForPlayback(Activity activity, String url, String cid) throws Ncg2InvalidLicenseException, Ncg2Exception;
// In case of HLS (live),
// You can call this method to add HLS url without accessing the URLs of m3u8 and key file.
public String addHttpLiveStreamUrlForPlayback(Activity activity, String url, String cid, boolean isLiveHLS) throws Ncg2InvalidLicenseException, Ncg2Exception;
}

The following code shows how to obtain virtual URL from DoveRunner contents.

public class PlayerActivity extends Activity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
Ncg2Agent ncgAgent = Ncg2SdkFactory.getNcgAgentInstance();
Ncg2LocalWebServer ncgLocalWebServer = ncgAgent.getLocalWebServer();
ncgLocalWebServer.setWebServerListener(mWebServerCallback);
ncgLocalWebServer.clearPlaybackUrls();
try {
if( mNcgFilePath.contains(".m3u8") ) {
// HLS Playback
mPlaybackURL = ncgLocalWebServer.addHttpLiveStreamUrlForPlayback(mNcgFilePath);
} else if(mNcgFilePath.startsWith("http://")) {
// PD Playback
mPlaybackURL = ncgLocalWebServer.addProgressiveDownloadUrlForPlayback(mNcgFilePath);
} else {
// Local Playback
mPlaybackURL = ncgLocalWebServer.addLocalFilePathForPlayback(mNcgFilePath, mNcgFileSize);
}
} catch (Ncg2Exception e) {
e.printStackTrace();
Toast.makeText(MediaPlayerActivity.this, "[onCreate] Error Occurred. : " + e.getMessage(), Toast.LENGTH_LONG).show();
return;
}
}
}

The following code shows how to decrypt and use virtual URL.

try {
mPlayer.setDataSource(mPlaybackURL);
mPlayer.prepareAsync();
} catch (IllegalArgumentException e) {
e.printStackTrace();
errorMsg = e.getMessage();
} catch (IllegalStateException e) {
e.printStackTrace();
errorMsg = e.getMessage();
} catch (IOException e) {
e.printStackTrace();
errorMsg = e.getMessage();
}

DoveRunner NCG Android SDK utilizes local web server in a proxy server. The event data from the web server can be received via WebServerListener interface. As shown in the following codes, you can process notice or error of Ncg2LocalWebserver.

NOTE : You need to check and return the status of player by using onCheckPlayerStatus() method.

private Ncg2LocalWebServer.WebServerListener mWebServerCallback = new Ncg2LocalWebServer.WebServerListener(){
@Override
public void onNotification(int notificationCode) {
//Most of notice codes can be ignored. ⧸⧸⧸
}
@Override
public void onError(int errorCode, String errorMessage) {
//Show the applicable user error message.
}
@Override
public PlayerState onCheckPlayerStatus(String uri) {
if (mIsPrepared) {
// Return PlayerState.ReadyToPlay only when the player can start play.
return PlayerState.ReadyToPlay;
} else {
// Block playing when PlayerState.Fail is returned.
return PlayerState.Fail;
}
}
};

NOTE : In order to prevent the case that a hacker accesses content data via virtual URL, the state of player should be monitored. If the content is accessed via virtual URL but it is not playing, the app should stop the process.

DoveRunner NCG Android SDK provides the function to also decrypt NCG DRM files. Ncg2Agent can return NcgFile object to open and read decrypted data from NCG file. The SDK provides the following functions for NCG file decryption:

interface Ncg2Agent{
//Return NcgFile object.
public NcgFile createNcgFile();
interface NcgFile{
//Open NCG file.
public void open(String path) throws Ncg2InvalidLicenseException, Ncg2InvalidNcgFileException, Ncg2Exception;
//Set license and open NCG file.
public void open(String path, boolean prepare) throws Ncg2InvalidLicenseException, Ncg2InvalidNcgFileException, Ncg2Exception;
//Call the applicable method in a status after open and make it a status to enable decrypting.
public void prepare() throws Ncg2InvalidLicenseException, Ncg2InvalidNcgFileException, Ncg2Exception;
//Close the opened file.
public void close();
//Read content data from NCG file.
public long read(byte[] buff, long sizeToRead) throws Ncg2Exception;
//Move the location of file pointer to read.
public void seek(long offset, SeekMethod seekMethod) throws Ncg2Exception;
//Return the current location of file pointer.
public long getCurrentFilePointer() throws Ncg2Exception;
//Return the size of NCG file header.
public int getHeaderSize() throws Ncg2Exception;
//Return InputStream for NCG file.
public InputStream getInputStream() throws Ncg2Exception;
}
}

The following is a sample code to get decrypted content using NcgFile object.

try {
String ncgFilePath = mFilePath;
ncgFile = DemoLibrary.getNcgAgent().createNcgFile();
ncgFile.open(ncgFilePath);
ncgFile.seek(0, SeekMethod.End);
// the original file's size will be used for checking success of decryption.
long contentFileSize = ncgFile.getCurrentFilePointer();
ncgFile.seek(0, SeekMethod.Begin);
int pos = mFilePath.lastIndexOf("/");
// removes ".ncg" in the path of NCG file
String unpackFilePath = ncgFilePath.substring(0, pos+1) + "unpackFile.mp4";
fileOutStream = new BufferedOutputStream(new FileOutputStream(unpackFilePath));
long totalReadBytes = 0;
while( true ) {
long readBytes = ncgFile.read(buffer, 1024);
if( readBytes <= 0 ) {
break;
}
fileOutStream.write(buffer, 0, (int)readBytes);
totalReadBytes += readBytes;
}
if( totalReadBytes == contentFileSize ) {
Log.d(DemoLibrary.TAG, "Decryption succeeded");
} else {
Log.d(DemoLibrary.TAG, "Decryption failed");
Toast.makeText(mainActivity, "[unpackNcgFiles] decryption failed", Toast.LENGTH_LONG).show();
}
} catch (Ncg2Exception e) {
}

NOTE : It is recommended to decrypt and use the data only in memory, because there is a risk to expose original file if you save decrypted data to a file.

NcgEpubFile is designed for ePub content that requires reading multiple NCG files sharing the same CID (e.g., chapters of the same book). It reuses the internal crypto context across multiple open() calls for the same CID, reducing overhead significantly.

The following is the interface provided for NcgEpubFile:

interface Ncg2Agent {
// Returns a new NcgEpubFile handle.
public NcgEpubFile createNcgEpub();
interface NcgEpubFile {
// Opens the NCG ePub file for the given CID and file path.
// On the first call, performs full license validation and header parsing.
// On subsequent calls with the same CID, reuses the cached crypto context.
// You do NOT need to call close() before calling open() again with the same CID.
public void open(String cID, String path) throws Ncg2InvalidLicenseException, Ncg2Exception;
// Validates the license and prepares the file for decryption.
// Called automatically by open() on the first open.
public void prepare() throws Ncg2InvalidLicenseException, Ncg2Exception;
// Closes the current read session and releases the file stream.
// Optional for same-CID usage — you may call open() directly without close().
// Use when you want to explicitly free the file stream (e.g., pausing a session).
public void close();
// Fully releases all resources. Must be called when the book session is finished
// or before opening a different CID on the same instance.
public void release();
// Reads decrypted content data.
public long read(byte[] buff, long sizeToRead) throws Ncg2Exception;
// Moves the file pointer to the specified offset.
public void seek(long offset, SeekMethod seekMethod) throws Ncg2Exception;
// Returns the current file pointer position.
public long getCurrentFilePointer() throws Ncg2Exception;
// Returns the size of the decrypted content.
public long getContentSize() throws Ncg2Exception;
// Returns an InputStream for the NCG ePub file.
public InputStream getInputStream() throws Ncg2Exception;
}
}
MethodRequired?PurposeNext open() behavior
close()OptionalExplicitly release file stream (e.g., pause session)Fast reopen — skips full security check
release()RequiredEnd the entire book session or switch CIDFull license re-validation and header re-parse
// Create one NcgEpubFile per book
NcgEpubFile epubFile = DemoLibrary.getNcgAgent().createNcgEpub();
// Read multiple files of the same book — no close() needed between open() calls
epubFile.open(contentID, chapterPath1);
// ... read chapter 1 content ...
epubFile.open(contentID, chapterPath2); // directly reopen, no close() required
// ... read chapter 2 content ...
epubFile.open(contentID, chapterPath3);
// ... read chapter 3 content ...
// Done with the book — fully release all resources
epubFile.release();
// Keep one NcgEpubFile per CID
private final Map<String, NcgEpubFile> mEpubFileCache = new HashMap<>();
private NcgEpubFile getEpubFile(String cID) {
NcgEpubFile file = mEpubFileCache.get(cID);
if (file == null) {
file = DemoLibrary.getNcgAgent().createNcgEpub();
mEpubFileCache.put(cID, file);
}
return file;
}
// Reading a chapter — no close() between files of the same book
public void readChapter(String cID, String chapterPath) throws Ncg2Exception {
NcgEpubFile epubFile = getEpubFile(cID);
epubFile.open(cID, chapterPath);
// ... read ...
// No close() needed — next open() with same CID reuses the context directly
}
// When leaving a book entirely
public void releaseBook(String cID) {
NcgEpubFile epubFile = mEpubFileCache.remove(cID);
if (epubFile != null) {
epubFile.release();
}
}
// On Activity/Fragment destroy — release all
public void releaseAll() {
for (NcgEpubFile file : mEpubFileCache.values()) {
file.release();
}
mEpubFileCache.clear();
}

After Ncg2Agent object ends its use, release() method should be called for object release. The release() method should be called when the app is closed. (i.e., when a user explicitly terminates the app). SDK sample app shows a dialog in OnBackPressed() method to call release() method.

case DIALOG_EXIT :
builder.setMessage( getString( R.string.confirm_end ));
builder.setPositiveButton( getString( R.string.yes ), new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialog, int which) {
...
finish();
DemoLibrary.getNcgAgent().release();
}
});
builder.setNegativeButton( R.string.no, null );
dialog = builder.create();
break;

NOTE : There is a case to call Ncg2Agent.release method in onTerminate method of the app. However, onTerminate method is not always called explicitly so it is not recommended to call Ncg2Agent.release method in onTerminate.

Errors in DoveRunner NCG Android SDK is processed through exception. There is Ncg2Exception class which is a basic exception class; and then there exist a variety of separate exception classes which inherit the basic class.

NcgException class is a basic exception class of DoveRunner NCG Android SDK, which has internally ErrorCode and Error Message as its members. If ErrorCode is set, ErrorCode can be confirmed with getErrorCode() method; if ErrorCode is not set, -1 is returned. Error Message can be confirmed with getMessage() method. The following is an example code to catch Ncg2Exception exception when calling for the case to use setDataSource() method of Ncg2Player.

try {
mPlayer.setDataSource(mNcgFilePath, mNcgFileSize);
mPlayer.prepareAsync();
} catch (Ncg2Exception e) {
e.printStackTrace();
errorMsg = e.getMessage();
} catch (IllegalArgumentException e) {
e.printStackTrace();
errorMsg = e.getMessage();
} catch (IllegalStateException e) {
e.printStackTrace();
errorMsg = e.getMessage();
} catch (IOException e) {
e.printStackTrace();
errorMsg = e.getMessage();
}

NcgExceptionalEventListener is an interface that receives all internal event messages and exception information generated by the NCG SDK. It is intended to help diagnose and collect logs when DRM-related issues occur in a customer’s application.

interface Ncg2Agent {
interface NcgExceptionalEventListener {
// Receives internal event messages from the SDK
void log(String message);
// Receives internal exceptions from the SDK
void logException(Exception ex);
}
// Register the listener
public void setExceptionalEventListener(NcgExceptionalEventListener listener);
}

Register the listener during initialization in Application.onCreate():

public class DemoApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initializeNcgAgent();
}
private void initializeNcgAgent() {
DemoLibrary.getNcgAgent().setExceptionalEventListener(new Ncg2Agent.NcgExceptionalEventListener() {
@Override
public void log(String message) {
Log.e("NcgSDK", message); // Remove or disable in production builds
}
@Override
public void logException(Exception ex) {
Log.e("NcgSDK", ex.getMessage()); // Remove or disable in production builds
}
});
try {
DemoLibrary.getNcgAgent().init(this, Ncg2Agent.OfflineSupportPolicy.OfflineSupport);
} catch (Ncg2Exception e) {
e.printStackTrace();
}
}
}

The following API allows SDK usage in emulator environments during development. This must be removed or disabled before distributing the actual app.

// Enable emulator support (development only)
Ncg2Agent ncgAgent = Ncg2SdkFactory.getNcgAgentInstance();
ncgAgent.enableVirtualMachine();
// Disable emulator support (required for production)
ncgAgent.disableVirtualMachine();