From 2684afb4cae8266e5a8dd8bc7e960ed57594b7b5 Mon Sep 17 00:00:00 2001 From: Hisham Date: Tue, 16 Oct 2018 20:04:34 +0530 Subject: [PATCH] custom protocol impl, working for online. --- demos/main/src/main/assets/media.exolist.json | 8 + .../demo/SampleChooserActivity.java | 31 ++- library/core/build.gradle | 2 +- .../exoplayer2/offline/LicenceObtainer.java | 10 +- .../exoplayer2/offline/SegmentDownloader.java | 4 +- .../upstream/DefaultHttpDataSource.java | 137 +----------- .../exoplayer2/upstream/HttpDataSource.java | 1 - .../vocabimate_stream/CustomDataSource.java | 201 ++++++++++++++++++ .../VocaDataSourceHelper.java | 73 ------- library/hls/build.gradle | 1 + .../exoplayer2/source/hls/HlsChunkSource.java | 5 +- protocol/.gitignore | 1 + protocol/build.gradle | 10 + .../protocol}/AesEncryptionUtil.java | 53 ++--- .../com/vocabimate/protocol/LicenceModel.java | 131 ++++++++++++ .../com/vocabimate/protocol/MainClass.java | 30 +++ .../protocol/TokenDecryptionHelper.java | 24 +++ .../protocol/VocAbsInputStream.java | 11 + .../protocol/VocabimateHttpUrlConnection.java | 107 ++++++++++ .../protocol/VocabimateInputStream.java | 19 ++ .../VocabimateStreamHandlerFactory.java | 17 ++ .../protocol/VocabimateURLStreamHandler.java | 28 +++ .../protocol/VocabimateUrlConnection.java | 37 ++++ settings.gradle | 2 +- 24 files changed, 697 insertions(+), 246 deletions(-) delete mode 100644 library/core/src/main/java/com/google/android/exoplayer2/upstream/vocabimate_stream/VocaDataSourceHelper.java create mode 100644 protocol/.gitignore create mode 100644 protocol/build.gradle rename {library/core/src/main/java/com/google/android/exoplayer2/upstream/vocabimate_stream => protocol/src/main/java/com/vocabimate/protocol}/AesEncryptionUtil.java (59%) create mode 100644 protocol/src/main/java/com/vocabimate/protocol/LicenceModel.java create mode 100644 protocol/src/main/java/com/vocabimate/protocol/MainClass.java create mode 100644 protocol/src/main/java/com/vocabimate/protocol/TokenDecryptionHelper.java create mode 100644 protocol/src/main/java/com/vocabimate/protocol/VocAbsInputStream.java create mode 100644 protocol/src/main/java/com/vocabimate/protocol/VocabimateHttpUrlConnection.java create mode 100644 protocol/src/main/java/com/vocabimate/protocol/VocabimateInputStream.java create mode 100644 protocol/src/main/java/com/vocabimate/protocol/VocabimateStreamHandlerFactory.java create mode 100644 protocol/src/main/java/com/vocabimate/protocol/VocabimateURLStreamHandler.java create mode 100644 protocol/src/main/java/com/vocabimate/protocol/VocabimateUrlConnection.java diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index 7f0b39142e4..753770645dc 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -22,6 +22,10 @@ "name": "Encrypted Custom", "uri": "https://vocatest-a40ab.firebaseapp.com/encrypted_android_no_key/prog_index.m3u8" }, + { + "name": "Video 30 - Master Playlist", + "uri":"https://voca2hosting.firebaseapp.com/vid30/playlist.m3u8" + }, { "name": "Encrypted Custom Small", "uri": "https://voca2hosting.firebaseapp.com/small_files/encrypted_without_key/index.m3u8" @@ -41,6 +45,10 @@ { "name": "Video from server in video list", "uri":"http://54.152.186.92:60801/drm/static/tutorial/playlist.m3u8" + }, + { + "name": "Video 20", + "uri":"http://54.152.186.92:60801/drm/static/video/inayat/sample_category/vid5/playlist.m3u8" } ] } diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java index 7812d795ccb..c7df24df3a8 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java @@ -145,16 +145,33 @@ public boolean onChildClick(ExpandableListView parent, View view, int groupPosit } public KeyHelperModel getKeyHelper(long videoId, UriSample sample) { - String key = null; - if(sample.uri.toString().contains("tutorial")){ - key = "http://54.152.186.92:60801/drm/static/tutorial/tutorial.key"; - } else if(sample.uri.toString().contains("sample")){ - key = "http://54.152.186.92:60801/static/sample/enc.key"; +// String key = null; +// if(sample.uri.toString().contains("tutorial")){ +// key = "http://54.152.186.92:60801/drm/static/tutorial/tutorial.key"; +// } else if(sample.uri.toString().contains("sample")){ +// key = "http://54.152.186.92:60801/static/sample/enc.key"; +// } + +// keyHelperModel.setVideoId(String.valueOf(model.getVideoId())); +// keyHelperModel.setM3u8Path(RetroUtils.BASE_URL +"/drm/" + model.getM3u8Url()); +// keyHelperModel.setToken("l8TmQpaBEdDGCtbefPfzTx54Bt4nOQLgaH8s3edJDhs="); +// keyHelperModel.setLicecnceUrl("http://54.152.186.92:60801/drm/get_key_for_a_video/20"); + +// return new KeyHelperModel().setVideoId("videoId: " + videoId) +// .setKeyPath(key) +// .setM3u8Path(sample.uri.toString()); +// + if(sample.uri.toString().contains("vid30")) { + return new KeyHelperModel().setVideoId("videoId: " + videoId) + .setM3u8Path("https://voca2hosting.firebaseapp.com/vid30/playlist.m3u8") + .setLicecnceUrl("https://voca2hosting.firebaseapp.com/vid30/licence") + .setToken("rmaC0c9VqdoDDCku3MsXLJw_LL2IM_62zw8lOwfJsLU="); } return new KeyHelperModel().setVideoId("videoId: " + videoId) - .setKeyPath(key) - .setM3u8Path(sample.uri.toString()); + .setM3u8Path("http://54.152.186.92:60801/drm/static/video/inayat/sample_category/vid5/playlist.m3u8") + .setLicecnceUrl("http://54.152.186.92:60801/drm/get_key_for_a_video/20") + .setToken("l8TmQpaBEdDGCtbefPfzTx54Bt4nOQLgaH8s3edJDhs="); } private void onSampleDownloadButtonClicked(Sample sample) { diff --git a/library/core/build.gradle b/library/core/build.gradle index d6c5762f569..16d51eaace0 100644 --- a/library/core/build.gradle +++ b/library/core/build.gradle @@ -69,7 +69,7 @@ dependencies { testImplementation 'com.google.auto.value:auto-value-annotations:' + autoValueVersion testAnnotationProcessor 'com.google.auto.value:auto-value:' + autoValueVersion implementation 'com.google.code.gson:gson:2.8.2' - + implementation project(':protocol') } ext { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/LicenceObtainer.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/LicenceObtainer.java index ce4ca732338..4bc6e321c26 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/LicenceObtainer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/LicenceObtainer.java @@ -1,19 +1,15 @@ package com.google.android.exoplayer2.offline; -import android.util.Log; import com.google.android.exoplayer2.upstream.HttpDataSource; -import com.google.android.exoplayer2.upstream.vocabimate_stream.VocaDataSourceHelper; +import com.vocabimate.protocol.LicenceModel; import com.google.gson.Gson; -import com.google.gson.JsonObject; -import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; -import java.util.Map; public class LicenceObtainer { @@ -59,7 +55,7 @@ public void getLicence() throws IOException InputStream in = urlConnection.getInputStream(); String result = readStream(in); - listener.onLicenceReceived(new Gson().fromJson(result, VocaDataSourceHelper.LicenceModel.class)); + listener.onLicenceReceived(new Gson().fromJson(result, LicenceModel.class)); /*InputStreamReader isw = new InputStreamReader(in); int data = isw.read(); @@ -102,6 +98,6 @@ private String readStream(InputStream in) { interface ILicenceData { - public void onLicenceReceived(VocaDataSourceHelper.LicenceModel licenceModel); + public void onLicenceReceived(LicenceModel licenceModel); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java index 9df296e2567..42c7351097f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java @@ -24,14 +24,12 @@ import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import com.google.android.exoplayer2.upstream.cache.CacheUtil; import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters; -import com.google.android.exoplayer2.upstream.vocabimate_stream.VocaDataSourceHelper; import com.google.android.exoplayer2.util.PriorityTaskManager; -import com.google.gson.Gson; + import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.ListIterator; import java.util.concurrent.atomic.AtomicBoolean; /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index b549a46cba1..872933cbda6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -19,19 +19,16 @@ import android.text.TextUtils; import android.util.Log; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.upstream.vocabimate_stream.AesEncryptionUtil; +import com.vocabimate.protocol.AesEncryptionUtil; import com.google.android.exoplayer2.upstream.vocabimate_stream.CustomDataSource; -import com.google.android.exoplayer2.upstream.vocabimate_stream.VocaDataSourceHelper; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Predicate; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.vocab.KeyHelperModel; -import com.google.gson.Gson; -import java.io.BufferedReader; + import java.io.EOFException; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.InterruptedIOException; import java.io.OutputStream; import java.lang.reflect.Method; @@ -267,121 +264,6 @@ public long open(DataSpec dataSpec) throws HttpDataSourceException { // Note: Th return bytesToRead; } - /** - * Custom method for custom scheme. - */ - private HttpURLConnection makeConnectionCustom() throws IOException { - - if (this instanceof CustomDataSource) { - KeyHelperModel keyHelper = ((CustomDataSource) this).getKeyHelperModel(); - if (keyHelper != null) { - String token = keyHelper.getToken(); - if(token != null && token.length() > 0) { - connection.setRequestProperty("access_token", token); - } - - String videoId = keyHelper.getVideoId(); // todo check this - if (videoId == null) { - throw new NullPointerException("Video id is not set."); - } - - URL keyUrl = null; - if (keyHelper.getKeyPath() == null) { - String licenceUrl = keyHelper.getLicecnceUrl(); //"https://voca2hosting.firebaseapp.com/small_files/license_key_path_absolute.json"; - URL url = new URL(licenceUrl); - - // parse licence - HttpURLConnection httpURLConnection = null; - try { - httpURLConnection = (HttpURLConnection) url - .openConnection(); - - InputStream in = httpURLConnection.getInputStream(); - String result = readStream(in); - VocaDataSourceHelper.LicenceModel licenceModel = new Gson() - .fromJson(result, VocaDataSourceHelper.LicenceModel.class); - if (licenceModel != null) { - keyUrl = new URL(licenceModel.getPath()); - } - - /*InputStreamReader isw = new InputStreamReader(in); - int data = isw.read(); - while (data != -1) { - char current = (char) data; - data = isw.read(); - System.out.print(current); - }*/ - } catch (Exception e) { - e.printStackTrace(); - } finally { - if (httpURLConnection != null) { - httpURLConnection.disconnect(); - } - } - } else { - keyUrl = new URL(keyHelper.getKeyPath()); - } - HttpURLConnection connection = null; -// keyUrl = new URL("http://54.152.186.92:60801/static/sample/enc.key"); // temp - if (keyUrl != null) { - connection = (HttpURLConnection) keyUrl.openConnection(); - connection.setConnectTimeout(connectTimeoutMillis); - connection.setReadTimeout(readTimeoutMillis); - if (defaultRequestProperties != null) { - for (Map.Entry property : defaultRequestProperties.getSnapshot() - .entrySet()) { - connection.setRequestProperty(property.getKey(), property.getValue()); - } - } - for (Map.Entry property : requestProperties.getSnapshot().entrySet()) { - connection.setRequestProperty(property.getKey(), property.getValue()); - } - connection.setRequestProperty("User-Agent", userAgent); - connection.setRequestMethod("GET"); -// if(!TextUtils.isEmpty(TokenManager.getToken())) { - if (this instanceof CustomDataSource) { - KeyHelperModel keyHelperModel = ((CustomDataSource) this).getKeyHelperModel(); - if (keyHelperModel != null) { - String token2 = keyHelper.getToken(); - if(token2 != null && token2.length() > 0) { - connection.setRequestProperty("access_token", token2); - } - } - } - -// } - Log.d(TAG, "ResponseCode: " + connection.getResponseCode()); - } - return connection; - } - } - return connection; - } - - private String readStream(InputStream in) { - BufferedReader reader = null; - StringBuffer response = new StringBuffer(); - try { - reader = new BufferedReader(new InputStreamReader(in)); - String line = ""; - while ((line = reader.readLine()) != null) { - response.append(line); - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - return response.toString(); - } - - @Override public int read(byte[] buffer, int offset, int readLength) throws HttpDataSourceException { try { @@ -464,10 +346,7 @@ private HttpURLConnection makeConnection(DataSpec dataSpec) throws IOException { try { url = new URL(dataSpec.uri.toString()); } catch (MalformedURLException e) { - if (e.getMessage().contains("vcb")) { - connection = makeConnectionCustom(); - return connection; - } + e.printStackTrace(); } byte[] postBody = dataSpec.postBody; long position = dataSpec.position; @@ -531,6 +410,7 @@ private HttpURLConnection makeConnection(URL url, byte[] postBody, long position String token = keyHelperModel.getToken(); if(token != null && token.length() > 0) { defaultRequestProperties.set("access_token", token); + defaultRequestProperties.set("licence_url", keyHelperModel.getLicecnceUrl()); } } } @@ -722,13 +602,16 @@ private int readInternal(byte[] buffer, int offset, int readLength) throws IOExc * We then override the read variable with the result received after our encryption. * When we get -1 from read variable that means file has ended. */ - if(dataSpec.key != null && dataSpec.key.contains(".key")) { + if(dataSpec.key != null && (dataSpec.key.contains(".key") || dataSpec.key.contains("vcb"))) { +// if(inputStream == null){ +// read = parseData(buffer); +// } else { read = inputStream.read(buffer); +// } if (read == 16) { byte[] testValue = new byte[16]; System.arraycopy(buffer, 0, testValue, 0, 16); - byte[] finalDataWritten = AesEncryptionUtil - .encrypt("Bar12345Bar12345", "pppppppppppppppp", testValue); + byte[] finalDataWritten = AesEncryptionUtil.encrypt("Bar12345Bar12345", "pppppppppppppppp", testValue); System.arraycopy(finalDataWritten, 0, buffer, 0, finalDataWritten.length); read = finalDataWritten.length; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java index 6388594270c..648da4a620f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java @@ -18,7 +18,6 @@ import android.support.annotation.IntDef; import android.text.TextUtils; -import com.google.android.exoplayer2.upstream.vocabimate_stream.AesEncryptionUtil; import com.google.android.exoplayer2.util.Predicate; import com.google.android.exoplayer2.util.Util; import java.io.IOException; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/vocabimate_stream/CustomDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/vocabimate_stream/CustomDataSource.java index dbe5498a443..0d46791118e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/vocabimate_stream/CustomDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/vocabimate_stream/CustomDataSource.java @@ -1,9 +1,22 @@ package com.google.android.exoplayer2.upstream.vocabimate_stream; +import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Predicate; import com.google.android.exoplayer2.vocab.KeyHelperModel; +import com.google.gson.Gson; +import com.vocabimate.protocol.LicenceModel; +import com.vocabimate.protocol.TokenDecryptionHelper; +import com.vocabimate.protocol.VocabimateStreamHandlerFactory; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLStreamHandlerFactory; /** * Created by Hisham on 12/Oct/2018 - 13:55 @@ -21,19 +34,207 @@ public KeyHelperModel getKeyHelperModel() { return keyHelperModel; } + @Override + public long open(DataSpec dataSpec) throws HttpDataSourceException { + return super.open(dataSpec); + } + public CustomDataSource(String userAgent, Predicate contentTypePredicate) { super(userAgent, contentTypePredicate); + init(); + } + + private void init() { + maybeInstall(new VocabimateStreamHandlerFactory()); +// URL.setURLStreamHandlerFactory(new VocabimateStreamHandlerFactory()); + } + + public static void maybeInstall(URLStreamHandlerFactory factory) { // todo maybe not the perfect way, need to check this later. + if(System.getProperty("com.vocabimate.streamHandlerFactoryInstalled") == null) { + URL.setURLStreamHandlerFactory(factory); + System.setProperty("com.vocabimate.streamHandlerFactoryInstalled", "true"); + } } public CustomDataSource(String userAgent, Predicate contentTypePredicate, TransferListener listener) { super(userAgent, contentTypePredicate, listener); + init(); } public CustomDataSource(String userAgent, Predicate contentTypePredicate, TransferListener listener, int connectTimeoutMillis, int readTimeoutMillis) { super(userAgent, contentTypePredicate, listener, connectTimeoutMillis, readTimeoutMillis); + init(); } public CustomDataSource(String userAgent, Predicate contentTypePredicate, TransferListener listener, int connectTimeoutMillis, int readTimeoutMillis, boolean allowCrossProtocolRedirects, RequestProperties defaultRequestProperties) { super(userAgent, contentTypePredicate, listener, connectTimeoutMillis, readTimeoutMillis, allowCrossProtocolRedirects, defaultRequestProperties); + init(); } + +// /** +// * Custom method for custom scheme. +// */ +// private HttpURLConnection makeConnectionCustom() throws IOException { +// +// if (this instanceof CustomDataSource) { +// KeyHelperModel keyHelper = ((CustomDataSource) this).getKeyHelperModel(); +// if (keyHelper != null) { +// String videoId = keyHelper.getVideoId(); +// if (videoId == null) { +// throw new NullPointerException("Video id is not set."); +// } +// +// URL keyUrl = null; +// if (keyHelper.getKeyPath() == null) { +// String licenceUrl = keyHelper.getLicecnceUrl(); //"https://voca2hosting.firebaseapp.com/small_files/license_key_path_absolute.json"; +// URL url = new URL(licenceUrl); +// +// // parse licence +// HttpURLConnection httpURLConnection = null; +// try { +// httpURLConnection = (HttpURLConnection) url.openConnection(); +// String token = keyHelper.getToken(); +// if(token != null && token.length() > 0) { +// httpURLConnection.setRequestProperty("access_token", token); +// } +// InputStream in = httpURLConnection.getInputStream(); +// String result = readStream(in); +// VocaDataSourceHelper.LicenceModel licenceModel = new Gson() +// .fromJson(result, VocaDataSourceHelper.LicenceModel.class); +// if(licenceModel != null && licenceModel.getEncKeyByteString() != null) { +// TokenDecryptionHelper tokenDecryptionHelper = new TokenDecryptionHelper(keyHelper.getToken(), licenceModel.getEncKeyByteString()); +// byte[] decrypt = tokenDecryptionHelper.decrypt(); +// if (licenceModel.getPath() != null) { +// keyUrl = new URL(licenceModel.getPath()); +// } +// } +// /*InputStreamReader isw = new InputStreamReader(in); +// int data = isw.read(); +// while (data != -1) { +// char current = (char) data; +// data = isw.read(); +// System.out.print(current); +// }*/ +// } catch (Exception e) { +// e.printStackTrace(); +// } finally { +// if (httpURLConnection != null) { +// httpURLConnection.disconnect(); +// } +// } +// } else { +// keyUrl = new URL(keyHelper.getKeyPath()); +// } +// HttpURLConnection connection = null; +//// keyUrl = new URL("http://54.152.186.92:60801/static/sample/enc.key"); // temp +// if (keyUrl != null) { +// connection = (HttpURLConnection) keyUrl.openConnection(); +// connection.setConnectTimeout(connectTimeoutMillis); +// connection.setReadTimeout(readTimeoutMillis); +// if (defaultRequestProperties != null) { +// for (Map.Entry property : defaultRequestProperties.getSnapshot() +// .entrySet()) { +// connection.setRequestProperty(property.getKey(), property.getValue()); +// } +// } +// for (Map.Entry property : requestProperties.getSnapshot().entrySet()) { +// connection.setRequestProperty(property.getKey(), property.getValue()); +// } +// connection.setRequestProperty("User-Agent", userAgent); +// connection.setRequestMethod("GET"); +//// if(!TextUtils.isEmpty(TokenManager.getToken())) { +// if (this instanceof CustomDataSource) { +// KeyHelperModel keyHelperModel = ((CustomDataSource) this).getKeyHelperModel(); +// if (keyHelperModel != null) { +// String token2 = keyHelper.getToken(); +// if(token2 != null && token2.length() > 0) { +// connection.setRequestProperty("access_token", token2); +// } +// } +// } +// +//// } +// Log.d(TAG, "ResponseCode: " + connection.getResponseCode()); +// } +// return connection; +// } +// } +// return connection; +// } + + + private int parseData(byte[] buffer) throws IOException { + KeyHelperModel keyHelper = ((CustomDataSource) this).getKeyHelperModel(); + if (keyHelper != null) { + String videoId = keyHelper.getVideoId(); + if (videoId == null) { + throw new NullPointerException("Video id is not set."); + } + String licenceUrl = keyHelper.getLicecnceUrl(); //"https://voca2hosting.firebaseapp.com/small_files/license_key_path_absolute.json"; + URL url = new URL(licenceUrl); + + // parse licence + HttpURLConnection licenseConnection = null; + try { + licenseConnection = (HttpURLConnection) url.openConnection(); + String token = keyHelper.getToken(); + if(token != null && token.length() > 0) { + licenseConnection.setRequestProperty("access_token", token); + } + InputStream in = licenseConnection.getInputStream(); + String result = readStream(in); + LicenceModel licenceModel = new Gson().fromJson(result, LicenceModel.class); + if(licenceModel != null && licenceModel.getLicenseFile().getDecryptionKey() != null) { + TokenDecryptionHelper tokenDecryptionHelper = new TokenDecryptionHelper(keyHelper.getToken(), licenceModel.getLicenseFile().getDecryptionKey()); + byte[] decrypt = tokenDecryptionHelper.decrypt(); + for (int i = 0; i < decrypt.length; i++) { + buffer[i] = decrypt[i]; + } + return 16; + } + + /*InputStreamReader isw = new InputStreamReader(in); + int data = isw.read(); + while (data != -1) { + char current = (char) data; + data = isw.read(); + System.out.print(current); + }*/ + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (licenseConnection != null) { + licenseConnection.disconnect(); + } + } + } + return -1; + } + + + + private String readStream(InputStream in) { + BufferedReader reader = null; + StringBuffer response = new StringBuffer(); + try { + reader = new BufferedReader(new InputStreamReader(in)); + String line = ""; + while ((line = reader.readLine()) != null) { + response.append(line); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return response.toString(); + } + + } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/vocabimate_stream/VocaDataSourceHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/vocabimate_stream/VocaDataSourceHelper.java deleted file mode 100644 index 2394b2b579e..00000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/vocabimate_stream/VocaDataSourceHelper.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.google.android.exoplayer2.upstream.vocabimate_stream; - -import android.net.Uri; -import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.gson.Gson; -import com.google.gson.annotations.SerializedName; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URLConnection; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Created by Hisham on 21/Sep/2018 - 17:13 - */ -public class VocaDataSourceHelper { - - private Gson gson; - -// public void parseData(URLConnection connection, -// DataSpec dataSpec) throws IOException { -// -// connection.connect(); -// InputStream stream = connection.getInputStream(); -// BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); -// StringBuffer buffer = new StringBuffer(); -// String line; -// while ((line = reader.readLine()) != null){ -// buffer.append(line); -// } -// -// String finalJson = buffer.toString(); -// try { -// JSONObject parentObject = new JSONObject(finalJson); -// -// LicenceModel licenceModel = gson.fromJson(parentObject.toString(), LicenceModel.class); -// String keyPath = licenceModel.getPath(); -// dataSpec.uri = Uri.parse(keyPath); -// } catch (JSONException e) { -// e.printStackTrace(); -// } - -// } - - public class LicenceModel { - @SerializedName("key_path") - private String path; - @SerializedName("key") - private String key; - - public String getPath() { - return path; - } - - public LicenceModel setPath(String path) { - this.path = path; - return this; - } - - public String getKey() { - return key; - } - - public LicenceModel setKey(String key) { - this.key = key; - return this; - } - } - -} diff --git a/library/hls/build.gradle b/library/hls/build.gradle index 51ba979c25e..f18522897e9 100644 --- a/library/hls/build.gradle +++ b/library/hls/build.gradle @@ -39,6 +39,7 @@ dependencies { compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion implementation project(modulePrefix + 'library-core') testImplementation project(modulePrefix + 'testutils-robolectric') + implementation project(':protocol') } ext { diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index 7085241f688..82e29dc4b30 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -32,10 +32,11 @@ import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.android.exoplayer2.upstream.vocabimate_stream.AesEncryptionUtil; import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.UriUtil; import com.google.android.exoplayer2.util.Util; +import com.vocabimate.protocol.AesEncryptionUtil; + import java.io.IOException; import java.math.BigInteger; import java.util.Arrays; @@ -382,7 +383,7 @@ public void onChunkLoadCompleted(Chunk chunk) { * Here it loads the decryption key, which we should have already encrypted using our AesEncryptionUtil, so we need * to decrypt it first. See {@Link DefaultHttpDataSource#readInternal() Line: 616} for more details. */ - if(chunk.dataSpec != null && chunk.dataSpec.uri != null && chunk.dataSpec.uri.toString().contains(".key")) { // hisham - decrypting key here + if(chunk.dataSpec != null && chunk.dataSpec.uri != null && (chunk.dataSpec.uri.toString().contains(".key") || chunk.dataSpec.uri.toString().contains("vcb"))) { // hisham - decrypting key here encryptionKeyChunk.result = AesEncryptionUtil.decrypt("Bar12345Bar12345", "pppppppppppppppp",encryptionKeyChunk.getResult()); } diff --git a/protocol/.gitignore b/protocol/.gitignore new file mode 100644 index 00000000000..796b96d1c40 --- /dev/null +++ b/protocol/.gitignore @@ -0,0 +1 @@ +/build diff --git a/protocol/build.gradle b/protocol/build.gradle new file mode 100644 index 00000000000..db86c0c9e2f --- /dev/null +++ b/protocol/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'java-library' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.google.code.gson:gson:2.8.5' + implementation "com.google.guava:guava:14.0.1" +} + +sourceCompatibility = "1.8" +targetCompatibility = "1.8" diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/vocabimate_stream/AesEncryptionUtil.java b/protocol/src/main/java/com/vocabimate/protocol/AesEncryptionUtil.java similarity index 59% rename from library/core/src/main/java/com/google/android/exoplayer2/upstream/vocabimate_stream/AesEncryptionUtil.java rename to protocol/src/main/java/com/vocabimate/protocol/AesEncryptionUtil.java index 487e01ffc00..6f2f46dc1a4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/vocabimate_stream/AesEncryptionUtil.java +++ b/protocol/src/main/java/com/vocabimate/protocol/AesEncryptionUtil.java @@ -1,42 +1,48 @@ -package com.google.android.exoplayer2.upstream.vocabimate_stream; +package com.vocabimate.protocol; + +import com.google.common.io.BaseEncoding; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public class AesEncryptionUtil { - public static byte[] encrypt(String key, String initVector, byte[] value) { - try { - IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8")); - SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES"); + public static byte[] encrypt(String key, String initVector, byte[] value) { + try { + IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8")); + SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES"); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); - cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); + cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv); - byte[] encrypted = cipher.doFinal(value); + byte[] encrypted = cipher.doFinal(value); // System.out.println(encrypted); - String fileInString = new String(android.util.Base64.encode(encrypted, android.util.Base64.DEFAULT)); +// String fileInString = new String(android.util.Base64.encode(encrypted, android.util.Base64.DEFAULT)); +// String fileInString = Base64.getEncoder().encodeToString(encrypted); // String fileInString = Base64.encodeBase64String(encrypted); // System.out.println("encrypted string: " + fileInString); - byte[] fileInBytes = fileInString.getBytes(); + String fileInString = BaseEncoding.base64().encode(encrypted); + byte[] fileInBytes = fileInString.getBytes(); - return fileInBytes; - } catch (Exception ex) { - ex.printStackTrace(); - } + return fileInBytes; + } catch (Exception ex) { + ex.printStackTrace(); + } - return null; - } + return null; + } - public static byte[] decrypt(String key, String initVector, byte[] encodedfileInBytes) { + public static byte[] decrypt(String key, String initVector, byte[] encodedfileInBytes) { try { - String encodedfileInString=new String(encodedfileInBytes); + String encodedfileInString = new String(encodedfileInBytes); IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8")); SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv); - byte[] decodeFileInBytes= android.util.Base64.decode(encodedfileInString, android.util.Base64.DEFAULT); +// byte[] decodeFileInBytes = android.util.Base64.decode(encodedfileInString, android.util.Base64.DEFAULT); +// byte[] decodeFileInBytes = Base64.getDecoder().decode(encodedfileInString);// encodedfileInString.getBytes(); + byte[] decodeFileInBytes = BaseEncoding.base64().decode(encodedfileInString); byte[] originalFile = cipher.doFinal(decodeFileInBytes); return originalFile; @@ -46,8 +52,8 @@ public static byte[] decrypt(String key, String initVector, byte[] encodedfileI return null; } - - + + // public static void main(String[] args) { // // File file = new File("D:\\crypt0.key"); @@ -98,7 +104,6 @@ public static byte[] decrypt(String key, String initVector, byte[] encodedfileI // // System.out.println(new String(d)); //} - - - + + } diff --git a/protocol/src/main/java/com/vocabimate/protocol/LicenceModel.java b/protocol/src/main/java/com/vocabimate/protocol/LicenceModel.java new file mode 100644 index 00000000000..e90b925d8ed --- /dev/null +++ b/protocol/src/main/java/com/vocabimate/protocol/LicenceModel.java @@ -0,0 +1,131 @@ +package com.vocabimate.protocol; + +import com.google.gson.annotations.SerializedName; + +import java.util.Date; + +/** + * Created by Hisham on 16/Oct/2018 - 14:27 + */ +public class LicenceModel { + + @SerializedName("licenseFile") + private Licence licenseFile; + + public Licence getLicenseFile() { + return licenseFile; + } + + public LicenceModel setLicenseFile(Licence licenseFile) { + this.licenseFile = licenseFile; + return this; + } + + public class Licence{ + + private int userId; + private int videoId; + private String decryptionKey; + private Date validateDt; + private Date createDt; + private String createUserId; + private Date updateDt; + private String updateUserId; + private String delInd; + + public Licence() { //no operation + } + + public Licence(int userId, int videoId) { + super(); + this.userId = userId; + this.videoId = videoId; + } + + public Licence(int userId, int videoId, Date validateDt, Date createDt, String createUserId, Date updateDt, String updateUserId, String delInd) { + super(); + this.userId = userId; + this.videoId = videoId; + this.validateDt = validateDt; + this.createDt = createDt; + this.createUserId = createUserId; + this.updateDt = updateDt; + this.updateUserId = updateUserId; + this.delInd = delInd; + } + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } + + public int getVideoId() { + return videoId; + } + + public void setVideoId(int videoId) { + this.videoId = videoId; + } + + public Date getValidateDt() { + return validateDt; + } + + public void setValidateDt(Date validateDt) { + this.validateDt = validateDt; + } + + public Date getCreateDt() { + return createDt; + } + + public void setCreateDt(Date createDt) { + this.createDt = createDt; + } + + public String getCreateUserId() { + return createUserId; + } + + public void setCreateUserId(String createUserId) { + this.createUserId = createUserId; + } + + public Date getUpdateDt() { + return updateDt; + } + + public void setUpdateDt(Date updateDt) { + this.updateDt = updateDt; + } + + public String getUpdateUserId() { + return updateUserId; + } + + public void setUpdateUserId(String updateUserId) { + this.updateUserId = updateUserId; + } + + public String getDelInd() { + return delInd; + } + + public void setDelInd(String delInd) { + this.delInd = delInd; + } + + public String getDecryptionKey() { + return decryptionKey; + } + + public void setDecryptionKey(String decryptionKey) { + this.decryptionKey = decryptionKey; + } + + } + +} diff --git a/protocol/src/main/java/com/vocabimate/protocol/MainClass.java b/protocol/src/main/java/com/vocabimate/protocol/MainClass.java new file mode 100644 index 00000000000..1c1af375c5d --- /dev/null +++ b/protocol/src/main/java/com/vocabimate/protocol/MainClass.java @@ -0,0 +1,30 @@ +package com.vocabimate.protocol; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; + +public class MainClass { + + public static void main(String args[]){ + System.out.print("Hello"); + + URL.setURLStreamHandlerFactory(new VocabimateStreamHandlerFactory()); + try { + URL url = new URL("vcb://"); + URLConnection connection = url.openConnection(); + InputStream inputStream = connection.getInputStream(); + int i = inputStream.read(); + while(i != -1){ + System.out.print(i); + } + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/protocol/src/main/java/com/vocabimate/protocol/TokenDecryptionHelper.java b/protocol/src/main/java/com/vocabimate/protocol/TokenDecryptionHelper.java new file mode 100644 index 00000000000..a117f069640 --- /dev/null +++ b/protocol/src/main/java/com/vocabimate/protocol/TokenDecryptionHelper.java @@ -0,0 +1,24 @@ +package com.vocabimate.protocol; + +/** + * Created by Hisham on 12/Oct/2018 - 19:10 + */ +public class TokenDecryptionHelper { + private final String token; + private final String tokenEncryptedStringFromByteArray; + + public TokenDecryptionHelper(String token, String tokenEncryptedStringFromByteArray) { + this.token = token; + this.tokenEncryptedStringFromByteArray = tokenEncryptedStringFromByteArray; + } + + public byte[] decrypt(){ + + String key = token.substring(0, 16); +// String iv = token.substring(token.length() - 16, token.length()); + String iv = token.substring(token.length() - 17, token.length() - 1); // abcdefgh + byte[] keyBytes = tokenEncryptedStringFromByteArray.getBytes(); + byte[] decryptKey = AesEncryptionUtil.decrypt(key, iv, keyBytes); + return decryptKey; + } +} diff --git a/protocol/src/main/java/com/vocabimate/protocol/VocAbsInputStream.java b/protocol/src/main/java/com/vocabimate/protocol/VocAbsInputStream.java new file mode 100644 index 00000000000..ac4edd24365 --- /dev/null +++ b/protocol/src/main/java/com/vocabimate/protocol/VocAbsInputStream.java @@ -0,0 +1,11 @@ +package com.vocabimate.protocol; + +import java.io.InputStream; + +/** + * Created by Hisham on 15/Oct/2018 - 18:03 + */ +abstract class VocAbsInputStream extends InputStream{ + protected InputStream in; + abstract public void setInputStream(InputStream inputStream); +} diff --git a/protocol/src/main/java/com/vocabimate/protocol/VocabimateHttpUrlConnection.java b/protocol/src/main/java/com/vocabimate/protocol/VocabimateHttpUrlConnection.java new file mode 100644 index 00000000000..1cd350ca3ed --- /dev/null +++ b/protocol/src/main/java/com/vocabimate/protocol/VocabimateHttpUrlConnection.java @@ -0,0 +1,107 @@ +package com.vocabimate.protocol; + +import com.google.gson.Gson; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * Created by Hisham on 15/Oct/2018 - 16:58 + */ +class VocabimateHttpUrlConnection extends HttpURLConnection { + + protected VocAbsInputStream vocAbsInputStream; + + public VocabimateHttpUrlConnection(URL url) throws IOException { + super(url); + try { + String classname = "com.vocabimate.protocol." + "VocabimateInputStream"; + vocAbsInputStream = (VocAbsInputStream) Class.forName(classname).newInstance(); + } catch (Exception e) { + throw new IOException("Class Not Found: " + e); + } + } + + @Override + public void connect() throws IOException { + + // todo Need to fix things here, not getting token and licence url on older phones. + String licence_url = getRequestProperty("licence_url"); + String token = getRequestProperty("access_token"); + if (licence_url == null) { + throw new Error("Licence url is not provided in header, please set 'licence_url' just like access_token"); + } + if (token == null) { + throw new Error("Access Token is null, please set access_token in header"); + } + URL url = new URL(licence_url); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + String result = readStream(connection.getInputStream()); + LicenceModel licenceModel = new Gson().fromJson(result, LicenceModel.class); + InputStream stream = null; + if (licenceModel != null && licenceModel.getLicenseFile() != null && licenceModel.getLicenseFile().getDecryptionKey() != null) { + TokenDecryptionHelper tokenDecryptionHelper = new TokenDecryptionHelper(token, licenceModel.getLicenseFile().getDecryptionKey()); + byte[] decrypt = tokenDecryptionHelper.decrypt(); + stream = new ByteArrayInputStream(decrypt); +// for (int i = 0; i < decrypt.length; i++) { +// buffer[i] = decrypt[i]; +// } + } + + vocAbsInputStream.setInputStream(stream); + connected = true; + responseCode = 200; + } + + private String readStream(InputStream in) { + BufferedReader reader = null; + StringBuffer response = new StringBuffer(); + try { + reader = new BufferedReader(new InputStreamReader(in)); + String line = ""; + while ((line = reader.readLine()) != null) { + response.append(line); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return response.toString(); + } + + @Override + public String getContentType() { + return super.getContentType(); // application/pgp-keys + } + + @Override + public void disconnect() { + vocAbsInputStream = null; + } + + @Override + public boolean usingProxy() { + return false; + } + + @Override + public InputStream getInputStream() throws IOException { + if (!connected) { + connect(); + } + return vocAbsInputStream; + } +} diff --git a/protocol/src/main/java/com/vocabimate/protocol/VocabimateInputStream.java b/protocol/src/main/java/com/vocabimate/protocol/VocabimateInputStream.java new file mode 100644 index 00000000000..1c639df003a --- /dev/null +++ b/protocol/src/main/java/com/vocabimate/protocol/VocabimateInputStream.java @@ -0,0 +1,19 @@ +package com.vocabimate.protocol; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Created by Hisham on 15/Oct/2018 - 17:55 + */ +public class VocabimateInputStream extends VocAbsInputStream { + @Override + public int read() throws IOException { + return in.read(); + } + + @Override + public void setInputStream(InputStream inputStream) { + in = inputStream; + } +} diff --git a/protocol/src/main/java/com/vocabimate/protocol/VocabimateStreamHandlerFactory.java b/protocol/src/main/java/com/vocabimate/protocol/VocabimateStreamHandlerFactory.java new file mode 100644 index 00000000000..fa38f6644f3 --- /dev/null +++ b/protocol/src/main/java/com/vocabimate/protocol/VocabimateStreamHandlerFactory.java @@ -0,0 +1,17 @@ +package com.vocabimate.protocol; + +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; + +/** + * Created by Hisham on 15/Oct/2018 - 16:44 + */ +public class VocabimateStreamHandlerFactory implements URLStreamHandlerFactory { + @Override + public URLStreamHandler createURLStreamHandler(String protocol) { + if("vcb".equals(protocol)) { + return new VocabimateURLStreamHandler(protocol); + } + return null; + } +} diff --git a/protocol/src/main/java/com/vocabimate/protocol/VocabimateURLStreamHandler.java b/protocol/src/main/java/com/vocabimate/protocol/VocabimateURLStreamHandler.java new file mode 100644 index 00000000000..c344b29d3aa --- /dev/null +++ b/protocol/src/main/java/com/vocabimate/protocol/VocabimateURLStreamHandler.java @@ -0,0 +1,28 @@ +package com.vocabimate.protocol; + +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +/** + * Created by Hisham on 15/Oct/2018 - 16:50 + */ +class VocabimateURLStreamHandler extends URLStreamHandler { + + private final String protocol; + + public VocabimateURLStreamHandler(String protocol) { + super(); + this.protocol = protocol; + } + + @Override + protected URLConnection openConnection(URL url) throws IOException { + if ("vcb".equals(protocol)) { + return new VocabimateHttpUrlConnection(url); + } else { + return new VocabimateUrlConnection(url); + } + } +} diff --git a/protocol/src/main/java/com/vocabimate/protocol/VocabimateUrlConnection.java b/protocol/src/main/java/com/vocabimate/protocol/VocabimateUrlConnection.java new file mode 100644 index 00000000000..d2412891009 --- /dev/null +++ b/protocol/src/main/java/com/vocabimate/protocol/VocabimateUrlConnection.java @@ -0,0 +1,37 @@ +package com.vocabimate.protocol; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; + +/** + * Created by Hisham on 15/Oct/2018 - 16:58 + */ +class VocabimateUrlConnection extends URLConnection { + + protected VocAbsInputStream vocAbsInputStream; + + @Override + public void connect() throws IOException { + + + InputStream is = null; // todo + vocAbsInputStream.setInputStream(is); + + // todo need to hit server here in real, maybe + connected = true; + } + + public VocabimateUrlConnection(URL url) { + super(url); + } + + @Override + public InputStream getInputStream() throws IOException { + if(!connected){ + connect(); + } + return vocAbsInputStream; + } +} diff --git a/settings.gradle b/settings.gradle index d4530d67b74..eb74bfb2e24 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,7 +17,7 @@ def modulePrefix = ':' if (gradle.ext.has('exoplayerModulePrefix')) { modulePrefix += gradle.ext.exoplayerModulePrefix } - +include ':protocol' include modulePrefix + 'demo' include modulePrefix + 'demo-cast' include modulePrefix + 'demo-ima'