package com.kaltura.playersdk;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.kaltura.playersdk.drm.DrmAdapter;
import com.kaltura.playersdk.helpers.CacheManager;
import com.kaltura.playersdk.utils.Utilities;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import static com.kaltura.playersdk.utils.LogUtils.LOGE;
import static com.kaltura.playersdk.utils.LogUtils.LOGI;
/**
* Created by noamt on 04/01/2016.
*/
public class LocalAssetsManager {
private static final String TAG = "LocalAssetsManager";
private static final int JSON_BYTE_LIMIT = 1024 * 1024;
public interface AssetRegistrationListener {
void onRegistered(String assetPath);
void onFailed(String assetPath, Exception error);
}
public interface AssetStatusListener {
void onStatus(String assetPath, long expiryTimeSeconds, long availableTimeSeconds);
}
public interface AssetRemovalListener {
void onRemoved(String assetPath);
}
public static boolean registerAsset(@NonNull final Context context, @NonNull final KPPlayerConfig entry, @NonNull final String flavor,
@NonNull final String localPath, @Nullable final AssetRegistrationListener listener) {
return registerOrRefreshAsset(context, entry, flavor, localPath, false, listener);
}
public static boolean refreshAsset(@NonNull final Context context, @NonNull final KPPlayerConfig entry, @NonNull final String flavor,
@NonNull final String localPath, @Nullable final AssetRegistrationListener listener) {
return registerOrRefreshAsset(context, entry, flavor, localPath, true, listener);
}
public static boolean unregisterAsset(@NonNull final Context context, @NonNull final KPPlayerConfig entry,
@NonNull final String localPath, final AssetRemovalListener listener) {
doInBackground(new Runnable() {
@Override
public void run() {
// Remove cache
CacheManager cacheManager = null;
try {
cacheManager = getCacheManager(context, entry);
cacheManager.removeCachedResponse(Uri.parse(entry.getVideoURL()));
DrmAdapter drmAdapter = DrmAdapter.getDrmAdapter(context, localPath);
drmAdapter.unregisterAsset(localPath, listener);
} finally {
if (cacheManager != null) {
cacheManager.release();
}
}
}
});
return true;
}
public static boolean checkAssetStatus(@NonNull final Context context, @NonNull final String localPath,
@Nullable final AssetStatusListener listener) {
final DrmAdapter drmAdapter = DrmAdapter.getDrmAdapter(context, localPath);
doInBackground(new Runnable() {
@Override
public void run() {
drmAdapter.checkAssetStatus(localPath, listener);
}
});
return true;
}
private static boolean registerOrRefreshAsset(@NonNull final Context context, @NonNull final KPPlayerConfig entry, @NonNull final String flavor,
@NonNull final String localPath, final boolean refresh, @Nullable final AssetRegistrationListener listener) {
// Preflight: check that all parameters are valid.
checkNotNull(entry.getPartnerId(), "entry.partnerId"); // can be an empty string (but not null)
checkNotEmpty(entry.getServerURL(), "entry.domain");
checkNotEmpty(entry.getUiConfId(), "entry.uiConfId");
checkNotEmpty(entry.getEntryId(), "entry.entryId");
checkNotEmpty(entry.getLocalContentId(), "entry.localContentId");
checkNotEmpty(localPath, "localPath");
if (! Utilities.isOnline(context)) {
LOGI(TAG, "Can't register/refresh when offline");
return false;
}
doInBackground(new Runnable() {
@Override
public void run() {
CacheManager cacheManager = null;
try {
cacheManager = getCacheManager(context, entry);
Uri uri = Uri.parse(entry.getVideoURL());
if (refresh) {
cacheManager.refreshCachedResponse(uri);
} else {
cacheManager.cacheResponse(uri);
}
DrmAdapter drmAdapter = DrmAdapter.getDrmAdapter(context, localPath);
DrmAdapter.DRMScheme scheme = drmAdapter.getScheme();
Uri licenseUri = prepareLicenseUri(entry, flavor, scheme);
drmAdapter.registerAsset(localPath, String.valueOf(licenseUri), listener);
} catch (JSONException | IOException e) {
LOGE(TAG, "Error", e);
if (listener != null) {
listener.onFailed(localPath, e);
}
} finally {
if (cacheManager != null) {
cacheManager.release();
}
}
}
});
return true;
}
@NonNull
private static CacheManager getCacheManager(@NonNull Context context, @NonNull KPPlayerConfig entry) {
CacheManager cacheManager = new CacheManager(context.getApplicationContext());
cacheManager.setBaseURL(Utilities.stripLastUriPathSegment(entry.getServerURL()));
cacheManager.setCacheSize(entry.getCacheSize());
return cacheManager;
}
private static Uri prepareLicenseUri(KPPlayerConfig config, @Nullable String flavor, @NonNull DrmAdapter.DRMScheme drmScheme) throws IOException, JSONException {
String overrideUrl = config.getConfigValueString("Kaltura.overrideDrmServerURL");
if (overrideUrl != null) {
return Uri.parse(overrideUrl);
}
if (drmScheme == DrmAdapter.DRMScheme.Null) {
return null;
}
// load license data
Uri getLicenseDataURL = prepareGetLicenseDataURL(config, flavor, drmScheme);
String licenseData = Utilities.loadStringFromURL(getLicenseDataURL, JSON_BYTE_LIMIT);
// parse license data
JSONObject licenseDataJSON = new JSONObject(licenseData);
if (licenseDataJSON.has("error")) {
throw new IOException("Error getting license data: " + licenseDataJSON.getJSONObject("error").getString("message"));
}
String licenseUri = licenseDataJSON.getString("licenseUri");
return Uri.parse(licenseUri);
}
private static Uri prepareGetLicenseDataURL(KPPlayerConfig config, String flavor, DrmAdapter.DRMScheme drmScheme) throws IOException, JSONException {
Uri serviceURL = Uri.parse(config.getServerURL());
// URL may either point to the root of the server or to mwEmbedFrame.php. Resolve this.
if (serviceURL.getPath().endsWith("/mwEmbedFrame.php")) {
serviceURL = Utilities.stripLastUriPathSegment(serviceURL);
} else {
serviceURL = resolvePlayerRootURL(serviceURL, config.getPartnerId(), config.getUiConfId(), config.getKS());
}
// Now serviceURL is something like "http://cdnapi.kaltura.com/html5/html5lib/v2.38.3".
String drmName = null;
switch (drmScheme) {
case WidevineCENC:
drmName = "wvcenc";
break;
case WidevineClassic:
drmName = "wvclassic";
break;
}
Uri.Builder builder = serviceURL.buildUpon()
.appendPath("services.php")
.encodedQuery(config.getQueryString())
.appendQueryParameter("service", "getLicenseData")
.appendQueryParameter("drm", drmName);
if (flavor != null) {
builder.appendQueryParameter("flavor_id", flavor);
}
return builder.build();
}
private static Uri resolvePlayerRootURL(Uri serverURL, String partnerId, String uiConfId, String ks) throws IOException, JSONException {
// serverURL is something like "http://cdnapi.kaltura.com";
// we need to get to "http://cdnapi.kaltura.com/html5/html5lib/v2.38.3".
// This is done by loading UIConf data, and looking at "html5Url" property.
String jsonString = loadUIConf(serverURL, partnerId, uiConfId, ks);
String embedLoaderUrl;
JSONObject uiConfJSON = new JSONObject(jsonString);
if (uiConfJSON.has("message")) {
throw new IOException("Error getting UIConf: " + uiConfJSON.getString("message"));
}
embedLoaderUrl = uiConfJSON.getString("html5Url");
Uri serviceUri;
if (embedLoaderUrl.startsWith("/")) {
serviceUri = serverURL.buildUpon()
.appendEncodedPath(embedLoaderUrl)
.build();
} else {
serviceUri = Uri.parse(embedLoaderUrl);
}
return Utilities.stripLastUriPathSegment(serviceUri);
}
private static String loadUIConf(Uri serverURL, String partnerId, String uiConfId, String ks) throws IOException {
Uri.Builder uriBuilder = serverURL.buildUpon()
.appendEncodedPath("api_v3/index.php")
.appendQueryParameter("service", "uiconf")
.appendQueryParameter("action", "get")
.appendQueryParameter("format", "1")
.appendQueryParameter("p", partnerId)
.appendQueryParameter("id", uiConfId);
if (ks != null) {
uriBuilder.appendQueryParameter("ks", ks);
}
return Utilities.loadStringFromURL(uriBuilder.build(), JSON_BYTE_LIMIT);
}
private static void checkArg(boolean invalid, String message) {
if (invalid) {
throw new IllegalArgumentException(message);
}
}
private static void checkNotNull(Object obj, String name) {
checkArg(obj == null, name + " must not be null");
}
private static void checkNotEmpty(String obj, String name) {
checkArg(obj == null || obj.length() == 0, name + " must not be empty");
}
private static void doInBackground(Runnable runnable) {
new Thread(runnable).start();
}
}