diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..b13b8d4 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +TDM Interactive \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..526b4c2 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0358047 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index dedefd3..adad6a4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,165 @@ -# sigma-interactive-android-tv-sdk-example -Sigma Interactive Android TV Sdk example +# SigmaInteractive SDK + +## Requirement: Android minSdk 21, Exoplayer 2.17.+ + +### I. Cài đặt + +Thêm file [SigmaInteractiveTVSDK.aar](https://github.com/truongnguyen1804/sigma-interactive-android-tv-sdk-example/tree/main/libs) vào thư mục libs cùng cấp với thư mục app của project. + +Thêm vào app/build.gradle: + +``` +dependencies { + ... + implementation files('../libs/SigmaInteractiveTVSDK.aar') + +} +``` + +### II. Sử dụng + +1. Thêm SigmaInteractive sdk vào project (mục **I**). + +2. Thêm sự kiện lắng nghe khi id3 bắt đầu parse để gửi dữ liệu cho sdk tương tác + + [SigmaRendererFactory](https://github.com/phamngochai123/sigma-interactive-sdk-example/blob/mobile-android/app/src/main/java/com/example/sigmainteractive/SigmaRendererFactory.java) xem trong demo + + ```java + DefaultRenderersFactory renderersFactory = new SigmaRendererFactory(this, metadata -> { + if (metadata != null) { + for (int i = 0; i < metadata.length(); i++) { + Metadata.Entry entry = metadata.get(i); + if (entry instanceof TextInformationFrame) { + String des = ((TextInformationFrame) entry).description; + String value = ((TextInformationFrame) entry).value; + if (des.toUpperCase().equals("TXXX")) { + _interactiveWebview.sendID3TagInstant(value); + } + } + } + } + }); + + exoPlayer = new ExoPlayer.Builder(this, renderersFactory).build(); + + ``` + +3. Thêm sự kiện lắng nghe khi id3 trả ra đúng thời điểm hẹn giờ để gửi dữ liệu cho sdk tương tác + + ```java + exoPlayer.addAnalyticsListener(new AnalyticsListener() { + @Override + public void onMetadata(EventTime eventTime, Metadata metadata) { + Log.d("checkListener", new Gson().toJson(metadata)); + if (metadata != null) { + for (int i = 0; i < metadata.length(); i++) { + Metadata.Entry entry = metadata.get(i); + if (entry instanceof TextInformationFrame) { + String des = ((TextInformationFrame) entry).description; + String value = ((TextInformationFrame) entry).value; + if (des.toUpperCase().equals("TXXX")) { + _interactiveWebview.sendID3Tag(value); + } + } + } + } + } + }); + ``` + +4. Tạo SigmaWebViewCallback để lắng nghe các sự kiện từ sdk tương tác. + + 4.1 Trong hàm onReady gửi dữ liệu dạng json string cho sdk tương tác (bắt buộc) + + ```java + _interactiveWebview.sendOnReadyBack(new Gson().toJson(getUserOption(token, currrentChannelId))); + ``` + + 4.2 Trong hàm onKeyDown lắng nghe các sự kiện phím trả về từ webview và xử lý các sự kiện đó. + + ```java + + @Override + public void onKeyDown(int code) { + Log.d("_interactiveWebview", "onKeyDown " + code); + + KeyEvent event = null; + + switch (code) { + case SigmaWebView.KEYCODE_UP: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP); + break; + case SigmaWebView.KEYCODE_DOWN: + + btnGuest.requestFocus(); + ((WebView) _interactiveWebview).setFocusable(true); + ((WebView) _interactiveWebview).clearFocus(); + break; + case SigmaWebView.KEYCODE_LEFT: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT); + break; + case SigmaWebView.KEYCODE_RIGHT: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT); + break; + case SigmaWebView.KEYCODE_ENTER: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER); + break; + case SigmaWebView.KEYCODE_ESCAPE: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); + break; + case SigmaWebView.KEYCODE_0: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_0); + break; + case SigmaWebView.KEYCODE_1: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_1); + break; + case SigmaWebView.KEYCODE_2: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_2); + break; + case SigmaWebView.KEYCODE_3: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_3); + break; + case SigmaWebView.KEYCODE_4: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_4); + break; + case SigmaWebView.KEYCODE_5: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_5); + break; + case SigmaWebView.KEYCODE_6: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_6); + break; + case SigmaWebView.KEYCODE_7: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_7); + break; + case SigmaWebView.KEYCODE_8: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_8); + break; + case SigmaWebView.KEYCODE_9: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_9); + break; + case SigmaWebView.KEYCODE_PAGEUP: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_PAGE_UP); + break; + case SigmaWebView.KEYCODE_PAGEDOWN: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_PAGE_DOWN); + break; + } + + if (event != null) { + PlayerActivity.this.onKeyDown(event.getKeyCode(), event); + } + } + ``` + + 4.3 Hàm onReload được gọi khi token hết hạn, gửi lại dữ liệu dạng json string cho sdk tương tác + + ```java + _interactiveWebview.sendOnReadyBack(new Gson().toJson(getUserOption(token, currrentChannelId))); + ``` + + + +#### + + + diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/.idea/.gitignore b/app/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/app/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/app/.idea/gradle.xml b/app/.idea/gradle.xml new file mode 100644 index 0000000..79e6703 --- /dev/null +++ b/app/.idea/gradle.xml @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/app/.idea/misc.xml b/app/.idea/misc.xml new file mode 100644 index 0000000..46fefaa --- /dev/null +++ b/app/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/app/.idea/modules.xml b/app/.idea/modules.xml new file mode 100644 index 0000000..e1f9e1c --- /dev/null +++ b/app/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/.idea/vcs.xml b/app/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/app/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..a9018c1 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,73 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdk 31 + + defaultConfig { + applicationId "com.tdm.tdminteractive" + minSdk 21 + targetSdk 31 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.4.2' + implementation 'com.google.android.material:material:1.6.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + +// implementation project(path: ':sigma_interactive') + + implementation files('../libs/SigmaInteractiveTVSDK.aar') + + implementation 'com.google.android.exoplayer:exoplayer:2.17.1' + +// for core support in exoplayer. + + implementation 'com.google.android.exoplayer:exoplayer-core:2.17.1' + // for adding dash support in our exoplayer. + + implementation 'com.google.android.exoplayer:exoplayer-dash:2.17.1' + +// for adding hls support in exoplayer. + + implementation 'com.google.android.exoplayer:exoplayer-hls:2.17.1' + +// for smooth streaming of video in our exoplayer. + + implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.17.1' + + implementation 'com.google.code.gson:gson:2.9.0' + + implementation 'com.android.volley:volley:1.2.1' + + implementation 'com.auth0:java-jwt:3.19.2' + + + + + +// for generating default ui of exoplayer + + +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/tdm/tdminteractive/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/tdm/tdminteractive/ExampleInstrumentedTest.java new file mode 100644 index 0000000..d3a99d5 --- /dev/null +++ b/app/src/androidTest/java/com/tdm/tdminteractive/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.tdm.tdminteractive; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.tdm.tdminteractive", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ef21d99 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..2004f93 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/tdm/tdminteractive/PlayerActivity.java b/app/src/main/java/com/tdm/tdminteractive/PlayerActivity.java new file mode 100644 index 0000000..0bfa8ac --- /dev/null +++ b/app/src/main/java/com/tdm/tdminteractive/PlayerActivity.java @@ -0,0 +1,503 @@ +package com.tdm.tdminteractive; + +import androidx.appcompat.app.AppCompatActivity; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.webkit.WebView; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; + +import com.android.volley.AuthFailureError; +import com.android.volley.Request; +import com.android.volley.RequestQueue; +import com.android.volley.VolleyLog; +import com.android.volley.toolbox.StringRequest; +import com.android.volley.toolbox.Volley; +import com.google.android.exoplayer2.DefaultRenderersFactory; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.PlaybackException; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.analytics.AnalyticsListener; +import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; +import com.google.android.exoplayer2.ui.PlayerView; +import com.google.gson.Gson; +import com.tdm.sigmainteractivetvsdk.SigmaWebView; +import com.tdm.sigmainteractivetvsdk.SigmaWebViewCallback; +import com.tdm.sigmainteractivetvsdk.SigmaWebviewInterface; + + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Map; + +public class PlayerActivity extends AppCompatActivity { + PlayerView playerView; + + ExoPlayer exoPlayer; + private static final String KEY_AUTH = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWQiOiJhZG1pbiIsImlzQWRtaW4iOmZhbHNlLCJpYXQiOjE2NDEzNTYyNTl9.WBRTuqhjBvzHTWCSorkVWeGeRcDFZUHzkGekDGtuZqg"; + + private MediaItem mediaItem; + + private String[][] urlChannels = { + {"c9c2ebfb-2887-4de6-aec4-0a30aa848915", "https://dev-livestream.gviet.vn/manifest/VTV1-PACKAGE/master.m3u8"} + , {"32a55ed3-4ee1-42f8-819a-407b54a39923", "https://dev-livestream.gviet.vn/manifest/VTV2-PACKAGE/master.m3u8"} + , {"60346597-8ed9-48de-bd4d-8546d0070c7c", "https://dev-livestream.gviet.vn/manifest/VTV3-PACKAGE/master.m3u8"} + , {"22e1fdb6-8d10-4193-8411-562c7104aa2b", "https://dev-livestream.gviet.vn/manifest/VTV4/master.m3u8"}}; + + private BodyTest[] bodyTests = { + new BodyTest("user", "0352153688", "default-app", new BodyTest.UserData("0352153688", "user", "android")), + new BodyTest("user", "0352153666", "default-app", new BodyTest.UserData("0352153666", "user", "android")), + new BodyTest("user", "0352153677", "default-app", new BodyTest.UserData("0352153677", "user", "android")), + new BodyTest("user", "0352153688", "default-app", new BodyTest.UserData("0352153688", "user", "android")) + }; + + private String[] idUser = {"", "0352153666", "0352153677", "0352153688"}; + + private SigmaWebviewInterface _interactiveWebview; + private RelativeLayout interactiveLayout; + private int currentIndexChannel = 0; + private int currentIndexInteractive = 0; + private String currrentChannelId = ""; + + private Button btnGuest, btn1, btn2, btn3, btnChannel1, btnChannel2, btnChannel3, btnChannel4; + + private LinearLayout layoutViewGroup; + + SharedPreferences sharedPreferences; + + private String token = ""; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + sharedPreferences = getSharedPreferences("shaTest", MODE_PRIVATE); + config(); + } + + public void config() { + + btnGuest = findViewById(R.id.btn_guest); + btn1 = findViewById(R.id.btn_u_1); + btn2 = findViewById(R.id.btn_u_2); + btn3 = findViewById(R.id.btn_u3); + + btnChannel1 = findViewById(R.id.btn_channel_1); + btnChannel2 = findViewById(R.id.btn_channel_2); + btnChannel3 = findViewById(R.id.btn_channel_3); + btnChannel4 = findViewById(R.id.btn_channel_4); + + interactiveLayout = findViewById(R.id.interactive_layout); + layoutViewGroup = findViewById(R.id.layout_group); + playerView = findViewById(R.id.player); + + currentIndexChannel = 0; + currentIndexInteractive = 0; + initPlayer(currentIndexChannel); + action(); + } + + private void action() { + btnGuest.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + initInteractive(0); + } + }); + + btn1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + initInteractive(1); + } + }); + btn2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + initInteractive(2); + } + }); + btn3.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + initInteractive(3); + } + }); + btnChannel1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + initPlayer(0); + } + }); + btnChannel2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + initPlayer(1); + } + }); + btnChannel3.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + initPlayer(2); + } + }); + btnChannel4.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + initPlayer(3); + } + }); + + btnGuest.setNextFocusUpId(R.id.sigma_webview); + btn1.setNextFocusUpId(R.id.sigma_webview); + btn2.setNextFocusUpId(R.id.sigma_webview); + btn3.setNextFocusUpId(R.id.sigma_webview); + btnChannel1.setNextFocusUpId(R.id.sigma_webview); + btnChannel2.setNextFocusUpId(R.id.sigma_webview); + btnChannel3.setNextFocusUpId(R.id.sigma_webview); + btnChannel4.setNextFocusUpId(R.id.sigma_webview); + ((WebView) _interactiveWebview).setNextFocusDownId(R.id.btn_guest); + + } + + private void setVisibleViewGroup() { + if (layoutViewGroup.getVisibility() == View.VISIBLE) { + layoutViewGroup.setVisibility(View.GONE); + if (_interactiveWebview != null) { + ((WebView) _interactiveWebview).setFocusable(true); + ((WebView) _interactiveWebview).requestFocus(); + } + } else { + layoutViewGroup.setVisibility(View.VISIBLE); + } + } + + private void initPlayer(int index) { + + currentIndexChannel = index; + + String channelId = urlChannels[currentIndexChannel][0]; + String url = urlChannels[currentIndexChannel][1]; + + currrentChannelId = channelId; + + initInteractive(currentIndexInteractive); + + DefaultRenderersFactory renderersFactory = new SigmaRendererFactory(this, metadata -> { + if (metadata != null) { + for (int i = 0; i < metadata.length(); i++) { + Metadata.Entry entry = metadata.get(i); + if (entry instanceof TextInformationFrame) { + String des = ((TextInformationFrame) entry).description; + String value = ((TextInformationFrame) entry).value; + if (des.toUpperCase().equals("TXXX")) { + _interactiveWebview.sendID3TagInstant(value); + } + } + } + } + }); + + exoPlayer = new ExoPlayer.Builder(this, renderersFactory).build(); + + + exoPlayer.addAnalyticsListener(new AnalyticsListener() { + @Override + public void onMetadata(EventTime eventTime, Metadata metadata) { + Log.d("checkListener", new Gson().toJson(metadata)); + if (metadata != null) { + for (int i = 0; i < metadata.length(); i++) { + Metadata.Entry entry = metadata.get(i); + if (entry instanceof TextInformationFrame) { + String des = ((TextInformationFrame) entry).description; + String value = ((TextInformationFrame) entry).value; + if (des.toUpperCase().equals("TXXX")) { + _interactiveWebview.sendID3Tag(value); + } + } + } + } + } + }); + + + mediaItem = new MediaItem.Builder() + .setUri(url) + .setClippingConfiguration(new MediaItem.ClippingConfiguration.Builder().build()).build(); + + playerView.setUseController(false); + + exoPlayer.setMediaItem(mediaItem); + exoPlayer.prepare(); + exoPlayer.setPlayWhenReady(true); + exoPlayer.addListener(new Player.Listener() { + + @Override + public void onPlayerError(PlaybackException error) { + if (error.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW) { + Log.d("onPlayerError", "errorCode: " + "ERROR_CODE_BEHIND_LIVE_WINDOW"); + exoPlayer.seekToDefaultPosition(); + exoPlayer.prepare(); + } else { + // Handle other errors. + } + } + + @Override + public void onIsPlayingChanged(boolean isPlaying) { + Log.d("onIsPlayingChanged", isPlaying + ""); + } + + @Override + public void onPlaybackStateChanged(int playbackState) { + + } + + @Override + public void onTimelineChanged(Timeline timeline, int reason) { + + } + + @Override + public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) { + Log.d("exoPlayer", "onPlayWhenReadyChanged" + reason); + + } + }); + playerView.setPlayer(exoPlayer); + + } + + private void initInteractive(int index) { + currentIndexInteractive = index; + ((RelativeLayout) interactiveLayout).removeAllViews(); + _interactiveWebview = new SigmaWebView(this); + + ((RelativeLayout) interactiveLayout) + .addView(((WebView) _interactiveWebview), RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); + ((WebView) _interactiveWebview).setId(R.id.sigma_webview); + ((WebView) _interactiveWebview).bringToFront(); + _interactiveWebview.setActivity(PlayerActivity.this); + + token = ""; + if (currentIndexInteractive != 0) { + if (sharedPreferences.getString(idUser[currentIndexInteractive], "").length() > 0) { + token = sharedPreferences.getString(idUser[currentIndexInteractive], ""); + + } else { + RequestQueue queue = Volley.newRequestQueue(PlayerActivity.this); + + StringRequest request = new StringRequest(Request.Method.POST, "https://dev-livestream.gviet.vn/api/interactive/v1/users/gen-token", + response -> { + Log.d("StringRequest", "success: " + response); + try { + JSONObject jsonObject = new JSONObject(response); + sharedPreferences.edit().putString(idUser[currentIndexInteractive], jsonObject.optString("token", "")).apply(); + initInteractive(currentIndexInteractive); + return; + } catch (JSONException e) { + e.printStackTrace(); + } + }, error -> { + Log.d("StringRequest", "error: " + error.networkResponse.statusCode + "\n" + new Gson().toJson(error)); + }) { + @Override + public Map getHeaders() throws AuthFailureError { + HashMap header = new HashMap<>(); + header.put("authorization", KEY_AUTH); + return header; + } + + @Override + public byte[] getBody() throws AuthFailureError { + String body = new Gson().toJson(bodyTests[currentIndexInteractive]); + Log.d("checkBody", body); + + try { + return body == null ? null : body.getBytes("utf-8"); + } catch (UnsupportedEncodingException uee) { + VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", body, "utf-8"); + return null; + } + } + + @Override + public String getBodyContentType() { + return "application/json; charset=utf-8"; + } + + }; + + queue.add(request); + } + + } + + _interactiveWebview.setCallback(new SigmaWebViewCallback() { + @Override + public void onReady() { + Log.d("_interactiveWebview", "onReady"); + ((WebView) _interactiveWebview).setFocusable(true); + ((WebView) _interactiveWebview).requestFocus(); + _interactiveWebview.sendOnReadyBack(new Gson().toJson(getUserOption(token, currrentChannelId))); + } + + @Override + public void onReload() { + _interactiveWebview.sendOnReadyBack(new Gson().toJson(getUserOption(token, currrentChannelId))); + } + + @Override + public void onKeyDown(int code) { + Log.d("_interactiveWebview", "onKeyDown " + code); + + KeyEvent event = null; + + switch (code) { + case SigmaWebView.KEYCODE_UP: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP); + break; + case SigmaWebView.KEYCODE_DOWN: + btnGuest.requestFocus(); + ((WebView) _interactiveWebview).setFocusable(true); + ((WebView) _interactiveWebview).clearFocus(); + break; + case SigmaWebView.KEYCODE_LEFT: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT); + break; + case SigmaWebView.KEYCODE_RIGHT: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT); + break; + case SigmaWebView.KEYCODE_ENTER: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER); + break; + case SigmaWebView.KEYCODE_ESCAPE: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); + break; + case SigmaWebView.KEYCODE_0: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_0); + break; + case SigmaWebView.KEYCODE_1: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_1); + break; + case SigmaWebView.KEYCODE_2: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_2); + break; + case SigmaWebView.KEYCODE_3: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_3); + break; + case SigmaWebView.KEYCODE_4: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_4); + break; + case SigmaWebView.KEYCODE_5: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_5); + break; + case SigmaWebView.KEYCODE_6: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_6); + break; + case SigmaWebView.KEYCODE_7: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_7); + break; + case SigmaWebView.KEYCODE_8: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_8); + break; + case SigmaWebView.KEYCODE_9: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_9); + break; + case SigmaWebView.KEYCODE_PAGEUP: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_PAGE_UP); + break; + case SigmaWebView.KEYCODE_PAGEDOWN: + event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_PAGE_DOWN); + break; + } + + if (event != null) { + PlayerActivity.this.onKeyDown(event.getKeyCode(), event); + } + } + + @Override + public void onOverlayShow() { + Log.d("_interactiveWebview", "onOverlayShow"); + } + + @Override + public void onOverlayHide() { + Log.d("_interactiveWebview", "onOverlayHide"); + } + + @Override + public void onSetSession(String s) { + + Log.d("_interactiveWebview", "onSetSession " + s); + + } + }); + + } + + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + Log.d("onKeyDownMain", keyCode + ""); + + return super.onKeyDown(keyCode, event); + + } + + private SigmaWebView.UserOptions getUserOption(String token, String channelId) { + return new SigmaWebView.UserOptions(token, channelId, + "default-app", "", true, "ArrowLeft", true, ""); + } + + public static boolean isKeyBack(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + case KeyEvent.KEYCODE_ESCAPE: + return true; + } + return false; + } + + + public static class BodyTest { + String role; + String id; + String appId; + UserData userData; + + public BodyTest(String role, String id, String appId, UserData userData) { + this.role = role; + this.id = id; + this.appId = appId; + this.userData = userData; + } + + public static class UserData { + String id; + String role; + String platform; + + public UserData(String id, String role, String platform) { + this.id = id; + this.role = role; + this.platform = platform; + } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/tdm/tdminteractive/SigmaRendererFactory.java b/app/src/main/java/com/tdm/tdminteractive/SigmaRendererFactory.java new file mode 100644 index 0000000..a3a0c84 --- /dev/null +++ b/app/src/main/java/com/tdm/tdminteractive/SigmaRendererFactory.java @@ -0,0 +1,108 @@ +package com.tdm.tdminteractive; + +import android.content.Context; +import android.os.Looper; + +import androidx.annotation.Nullable; + +import com.google.android.exoplayer2.DefaultRenderersFactory; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.Renderer; +import com.google.android.exoplayer2.drm.DrmSessionManager; +import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.metadata.MetadataDecoder; +import com.google.android.exoplayer2.metadata.MetadataDecoderFactory; +import com.google.android.exoplayer2.metadata.MetadataInputBuffer; +import com.google.android.exoplayer2.metadata.MetadataOutput; +import com.google.android.exoplayer2.metadata.MetadataRenderer; +import com.google.android.exoplayer2.metadata.emsg.EventMessageDecoder; +import com.google.android.exoplayer2.metadata.id3.Id3Decoder; +import com.google.android.exoplayer2.metadata.scte35.SpliceInfoDecoder; +import com.google.android.exoplayer2.util.MimeTypes; + +import java.util.ArrayList; + +public class SigmaRendererFactory extends DefaultRenderersFactory { + + /** + * @param context A {@link Context}. + */ + public SigmaRendererFactory(Context context) { + super(context); + } + + public interface Id3ParsedListener { + + void onId3Parsed(Metadata metadata); + } + + private Id3ParsedListener mListener; + + public SigmaRendererFactory(Context context, Id3ParsedListener listener) { + super(context); + mListener = listener; + } + + + + @Override + protected void buildMetadataRenderers( + Context context, + MetadataOutput output, + Looper outputLooper, + @ExtensionRendererMode int extensionRendererMode, + ArrayList out) { + out.add(new MetadataRenderer(output, outputLooper, new SigmaMetadataDecoderFactory(mListener))); + } + + public static class SigmaMetadataDecoderFactory implements MetadataDecoderFactory { + private Id3ParsedListener mListener; + public SigmaMetadataDecoderFactory(Id3ParsedListener listener){ + mListener = listener; + } + @Override + public boolean supportsFormat(Format format) { + @Nullable String mimeType = format.sampleMimeType; + return MimeTypes.APPLICATION_ID3.equals(mimeType) + || MimeTypes.APPLICATION_EMSG.equals(mimeType) + || MimeTypes.APPLICATION_SCTE35.equals(mimeType); + } + + @Override + public MetadataDecoder createDecoder(Format format) { + @Nullable String mimeType = format.sampleMimeType; + if (mimeType != null) { + switch (mimeType) { + case MimeTypes.APPLICATION_ID3: + return new SigmaId3Proxy(mListener); + case MimeTypes.APPLICATION_EMSG: + return new EventMessageDecoder(); + case MimeTypes.APPLICATION_SCTE35: + return new SpliceInfoDecoder(); + default: + break; + } + } + throw new IllegalArgumentException( + "Attempted to create decoder for unsupported MIME type: " + mimeType); + } + } + + public static class SigmaId3Proxy implements MetadataDecoder { + + private Id3Decoder mInternal = new Id3Decoder(); + private Id3ParsedListener mListener; + public SigmaId3Proxy(Id3ParsedListener listener){ + mListener = listener; + } + + @Override + public Metadata decode(MetadataInputBuffer inputBuffer) { + Metadata result = mInternal.decode(inputBuffer); + if (result != null && mListener != null) { + mListener.onId3Parsed(result); + } + return result; + } + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_channel_background.xml b/app/src/main/res/drawable/ic_channel_background.xml new file mode 100644 index 0000000..a1308f6 --- /dev/null +++ b/app/src/main/res/drawable/ic_channel_background.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..ca3826a --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..e4c56d5 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,113 @@ + + + + + + + + + +