@24i/smartapps-bigscreen-rnv-build-engine 0.17.2-alpha.2162 → 0.17.2-alpha.2164
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/resolvers/resolveBuildFileExtension.js +1 -0
- package/dist/src/sdks/sdk-androidtv/sdkAndroidtv.js +46 -7
- package/dist/src/tasks/task.rnv.build.js +1 -0
- package/dist/templates/platforms/androidtv/project/amazoniap/build.gradle +1 -3
- package/dist/templates/platforms/androidtv/project/app/build.gradle +270 -37
- package/dist/templates/platforms/androidtv/project/app/src/bigscreenAppstage/res/drawable/banner.png +0 -0
- package/dist/templates/platforms/androidtv/project/app/src/bigscreenAppstage/res/raw/configuration.json +4 -1
- package/dist/templates/platforms/androidtv/project/app/src/kan/java/com/twentyfouri/tvbridge/Flavor.kt +5 -0
- package/dist/templates/platforms/androidtv/project/app/src/kan/res/drawable/banner.png +0 -0
- package/dist/templates/platforms/androidtv/project/app/src/kan/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/dist/templates/platforms/androidtv/project/app/src/kan/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/dist/templates/platforms/androidtv/project/app/src/kan/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/dist/templates/platforms/androidtv/project/app/src/kan/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/dist/templates/platforms/androidtv/project/app/src/kan/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/dist/templates/platforms/androidtv/project/app/src/kan/res/raw/configuration.json +9 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/AndroidManifest.xml +8 -2
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/bridge/BridgeFromJavaScript.kt +19 -13
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/bridge/BridgeToJavaScript.kt +32 -14
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/configuration/BridgeConfig.kt +7 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/di/ActivityModule.kt +5 -3
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/utils/LoggingDrmCallback.kt +15 -23
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/utils/OverriddenHttpMediaDrmCallback.kt +134 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/utils/PlayerFactoryExoPlayerCustomized.kt +84 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/utils/SourceDrmOverridden.kt +49 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/jump/JumpAnalytics.kt +4 -2
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/client/BridgeWebView.kt +2 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/model/MediaEventType.kt +3 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/parser/TrackJsonModelParser.kt +37 -55
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/view/Mainview.kt +1 -3
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/view/PlayerCache.kt +2 -2
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/view/WebViewActivity.kt +40 -18
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/viewmodel/WebViewModel.kt +173 -171
- package/dist/templates/platforms/androidtv/project/app/src/main/res/layout/activity_web_view.xml +2 -4
- package/dist/templates/platforms/androidtv/project/app/src/main/res/values/styles.xml +1 -0
- package/dist/templates/platforms/androidtv/project/build.gradle +6 -10
- package/dist/templates/platforms/androidtv/project/common/build.gradle +1 -3
- package/dist/templates/platforms/androidtv/project/googleiap/build.gradle +1 -3
- package/dist/templates/platforms/androidtv/project/gradle/wrapper/gradle-wrapper.properties +3 -3
- package/dist/templates/platforms/androidtv/project/gradle.properties +1 -2
- package/dist/templates/platforms/androidtv/project/keystore/kan11_applicaster.keystore +0 -0
- package/dist/templates/platforms/androidtv/project/local.properties +8 -0
- package/package.json +1 -1
|
@@ -6,18 +6,20 @@ import android.os.Looper
|
|
|
6
6
|
import android.util.Log
|
|
7
7
|
import android.webkit.JavascriptInterface
|
|
8
8
|
import com.google.gson.JsonObject
|
|
9
|
-
import com.twentyfouri.
|
|
10
|
-
import com.twentyfouri.
|
|
9
|
+
import com.twentyfouri.player.base.TextTrack
|
|
10
|
+
import com.twentyfouri.player.base.TrackKind
|
|
11
11
|
import com.twentyfouri.tvbridge.webview.parser.ContentModelParser.stringToJson
|
|
12
12
|
import com.twentyfouri.tvbridge.webview.parser.DeviceInfoParser.deviceInfoParsed
|
|
13
13
|
import com.twentyfouri.tvbridge.webview.parser.PlaybackInfoParser.parse
|
|
14
|
-
import com.twentyfouri.tvbridge.webview.parser.TrackJsonModelParser.
|
|
14
|
+
import com.twentyfouri.tvbridge.webview.parser.TrackJsonModelParser.audioFromJson
|
|
15
|
+
import com.twentyfouri.tvbridge.webview.parser.TrackJsonModelParser.textFromJson
|
|
15
16
|
import com.twentyfouri.tvbridge.webview.parser.TrackJsonModelParser.toJson
|
|
16
17
|
import com.twentyfouri.tvbridge.webview.viewmodel.WebViewModel
|
|
17
18
|
import java.text.SimpleDateFormat
|
|
18
19
|
import java.util.*
|
|
19
20
|
import java.util.concurrent.CountDownLatch
|
|
20
21
|
import java.util.concurrent.TimeUnit
|
|
22
|
+
import kotlin.math.roundToInt
|
|
21
23
|
|
|
22
24
|
@SuppressWarnings("unused")
|
|
23
25
|
class BridgeFromJavaScript(
|
|
@@ -67,6 +69,7 @@ class BridgeFromJavaScript(
|
|
|
67
69
|
fun bridgeJavaScriptToAndroid_SetVideoPath(jsonString: String, mimeType: String?) = launch {
|
|
68
70
|
Log.d(LOG_TAG, "bridgeJavaScriptToAndroid_SetVideoPath = $jsonString")
|
|
69
71
|
try {
|
|
72
|
+
viewModel.stopPlayer()
|
|
70
73
|
val contentModel = stringToJson(jsonString)
|
|
71
74
|
contentModel?.streamType = mimeType?.let {
|
|
72
75
|
when {
|
|
@@ -136,7 +139,7 @@ class BridgeFromJavaScript(
|
|
|
136
139
|
*/
|
|
137
140
|
@JavascriptInterface
|
|
138
141
|
fun bridgeJavaScriptToAndroid_GetCurrentTime(): Int = runBlocking("bridgeJavaScriptToAndroid_GetCurrentTime") {
|
|
139
|
-
viewModel.currentPosition / 1000
|
|
142
|
+
viewModel.currentPosition.roundToInt() / 1000
|
|
140
143
|
} ?: 0
|
|
141
144
|
|
|
142
145
|
/*
|
|
@@ -144,7 +147,7 @@ class BridgeFromJavaScript(
|
|
|
144
147
|
*/
|
|
145
148
|
@JavascriptInterface
|
|
146
149
|
fun bridgeJavaScriptToAndroid_GetCurrentTimeFloat(): Float = runBlocking("bridgeJavaScriptToAndroid_GetCurrentTimeFloat") {
|
|
147
|
-
viewModel.currentPosition.toFloat() /
|
|
150
|
+
viewModel.currentPosition.toFloat() / 1000
|
|
148
151
|
} ?: 0f
|
|
149
152
|
|
|
150
153
|
/*
|
|
@@ -163,7 +166,7 @@ class BridgeFromJavaScript(
|
|
|
163
166
|
*/
|
|
164
167
|
@JavascriptInterface
|
|
165
168
|
fun bridgeJavaScriptToAndroid_GetTotalTime(): Int = runBlocking("bridgeJavaScriptToAndroid_GetTotalTime") {
|
|
166
|
-
viewModel.duration / 1000
|
|
169
|
+
viewModel.duration.roundToInt() / 1000
|
|
167
170
|
} ?: 0
|
|
168
171
|
|
|
169
172
|
/*
|
|
@@ -173,7 +176,7 @@ class BridgeFromJavaScript(
|
|
|
173
176
|
@JavascriptInterface
|
|
174
177
|
fun bridgeJavaScriptToAndroid_SeekTo(seconds: Int) = launch {
|
|
175
178
|
Log.d(LOG_TAG, "bridgeJavaScriptToAndroid_SeekTo($seconds)")
|
|
176
|
-
viewModel.seekToPosition(
|
|
179
|
+
viewModel.seekToPosition(1000 * seconds.toDouble())
|
|
177
180
|
}
|
|
178
181
|
|
|
179
182
|
/*
|
|
@@ -234,13 +237,16 @@ class BridgeFromJavaScript(
|
|
|
234
237
|
fun bridgeJavaScriptToAndroid_SetTrack(trackJson: String) = launch {
|
|
235
238
|
if (trackJson == "") {
|
|
236
239
|
Log.d(LOG_TAG, "bridgeJavaScriptToAndroid_SetTrack empty => DEFAULT")
|
|
237
|
-
viewModel.setSubtitleTrack(
|
|
240
|
+
viewModel.setSubtitleTrack(null)
|
|
238
241
|
} else if (trackJson == "false") {
|
|
239
242
|
Log.d(LOG_TAG, "bridgeJavaScriptToAndroid_SetTrack false => DISABLED")
|
|
240
|
-
viewModel.setSubtitleTrack(
|
|
243
|
+
viewModel.setSubtitleTrack(TextTrack {
|
|
244
|
+
id = "text_off"
|
|
245
|
+
kind = TrackKind.EMPTY
|
|
246
|
+
})
|
|
241
247
|
} else {
|
|
242
248
|
Log.d(LOG_TAG, "bridgeJavaScriptToAndroid_SetTrack $trackJson")
|
|
243
|
-
viewModel.setSubtitleTrack(
|
|
249
|
+
viewModel.setSubtitleTrack(textFromJson(trackJson))
|
|
244
250
|
}
|
|
245
251
|
}
|
|
246
252
|
|
|
@@ -250,7 +256,7 @@ class BridgeFromJavaScript(
|
|
|
250
256
|
@JavascriptInterface
|
|
251
257
|
fun bridgeJavaScriptToAndroid_SetAudioTrack(trackJson: String?) = launch {
|
|
252
258
|
Log.d(LOG_TAG, "bridgeJavaScriptToAndroid_SetAudioTrack ${trackJson ?: "(none)"}")
|
|
253
|
-
viewModel.setAudioTrack(
|
|
259
|
+
viewModel.setAudioTrack(audioFromJson(trackJson))
|
|
254
260
|
}
|
|
255
261
|
|
|
256
262
|
/*
|
|
@@ -370,10 +376,10 @@ class BridgeFromJavaScript(
|
|
|
370
376
|
companion object {
|
|
371
377
|
const val LOG_TAG = "JavaScriptBridge"
|
|
372
378
|
val DISABLED_AUDIO_TRACK = JsonObject().apply {
|
|
373
|
-
addProperty("type",
|
|
379
|
+
addProperty("type", "audio")
|
|
374
380
|
}.toString()
|
|
375
381
|
val DISABLED_SUBTITLE_TRACK = JsonObject().apply {
|
|
376
|
-
addProperty("type",
|
|
382
|
+
addProperty("type", "subtitles")
|
|
377
383
|
}.toString()
|
|
378
384
|
}
|
|
379
385
|
}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
package com.twentyfouri.tvbridge.bridge
|
|
2
2
|
|
|
3
3
|
import android.util.Log
|
|
4
|
-
import com.twentyfouri.
|
|
4
|
+
import com.twentyfouri.player.base.AudioTrack
|
|
5
|
+
import com.twentyfouri.player.base.TextTrack
|
|
5
6
|
import com.twentyfouri.tvbridge.bridge.BridgeFromJavaScript.Companion.LOG_TAG
|
|
7
|
+
import com.twentyfouri.tvbridge.configuration.BridgeConfig
|
|
6
8
|
import com.twentyfouri.tvbridge.webview.parser.TrackJsonModelParser.toJson
|
|
7
9
|
|
|
8
10
|
class BridgeToJavaScript(
|
|
9
|
-
|
|
11
|
+
config: BridgeConfig,
|
|
12
|
+
private val executeJavaScript: (String) -> Unit
|
|
10
13
|
) {
|
|
14
|
+
private val keyEventPrefix: String = config.keyEventPrefix
|
|
15
|
+
private val mediaEventPrefix: String = config.mediaEventPrefix
|
|
16
|
+
private val iapEventPrefix: String = config.iapEventPrefix
|
|
17
|
+
|
|
11
18
|
/**
|
|
12
19
|
* Android 2 JavaScript keyevent Event. Dispatch key custom event to the page document element
|
|
13
20
|
*
|
|
@@ -16,7 +23,7 @@ class BridgeToJavaScript(
|
|
|
16
23
|
*/
|
|
17
24
|
fun bridgeAndroidToJavaScript_KeyEvent(keyCode: Int, keyEvent: String) {
|
|
18
25
|
Log.d(LOG_TAG, "bridgeAndroidToJavaScript_KeyEvent keyCode = $keyCode, keyEvent = $keyEvent")
|
|
19
|
-
executeJavaScript("javascript:((function dispatchEvent(){var evt = document.createEvent('Events');evt.initEvent('$keyEvent',true,true);evt.keyCode=$keyCode;evt.which=$keyCode;document.dispatchEvent(evt);})())")
|
|
26
|
+
executeJavaScript("javascript:((function dispatchEvent(){var evt = document.createEvent('Events');evt.initEvent('$keyEventPrefix$keyEvent',true,true);evt.keyCode=$keyCode;evt.which=$keyCode;(document.activeElement?document.activeElement:document).dispatchEvent(evt);})())")
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
/**
|
|
@@ -25,8 +32,8 @@ class BridgeToJavaScript(
|
|
|
25
32
|
* @param mediaEvent the mediaEvent which is being sent from the Android code.
|
|
26
33
|
*/
|
|
27
34
|
fun bridgeAndroidToJavaScript_MediaEvent(mediaEvent: String) {
|
|
28
|
-
Log.d(LOG_TAG, "bridgeAndroidToJavaScript_MediaEvent $mediaEvent")
|
|
29
|
-
executeJavaScript("javascript:((function dispatchEvent(){var evt = document.createEvent('Events');evt.initEvent('$mediaEvent',true,true);document.dispatchEvent(evt);})())")
|
|
35
|
+
Log.d(LOG_TAG, "bridgeAndroidToJavaScript_MediaEvent $mediaEventPrefix$mediaEvent")
|
|
36
|
+
executeJavaScript("javascript:((function dispatchEvent(){var evt = document.createEvent('Events');evt.initEvent('$mediaEventPrefix$mediaEvent',true,true);document.dispatchEvent(evt);})())")
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
/**
|
|
@@ -35,31 +42,42 @@ class BridgeToJavaScript(
|
|
|
35
42
|
* @param mediaEvent the mediaEvent
|
|
36
43
|
* @param tracks tracks from the players stream.
|
|
37
44
|
*/
|
|
38
|
-
fun bridgeAndroidToJavaScript_MediaTrackEvent(
|
|
39
|
-
|
|
45
|
+
fun bridgeAndroidToJavaScript_MediaTrackEvent(
|
|
46
|
+
mediaEvent: String,
|
|
47
|
+
audioTracks: List<AudioTrack>,
|
|
48
|
+
textTracks: List<TextTrack>
|
|
49
|
+
) {
|
|
50
|
+
val tracksJson = toJson(audioTracks, textTracks)
|
|
40
51
|
Log.d(LOG_TAG, tracksJson)
|
|
41
|
-
val mediaEventString = "javascript:((function dispatchEvent(){var evt = document.createEvent('Events');evt.initEvent('$mediaEvent',true,true);evt.tracks='$tracksJson';document.dispatchEvent(evt);})())"
|
|
52
|
+
val mediaEventString = "javascript:((function dispatchEvent(){var evt = document.createEvent('Events');evt.initEvent('$mediaEventPrefix$mediaEvent',true,true);evt.tracks='$tracksJson';document.dispatchEvent(evt);})())"
|
|
42
53
|
Log.v(LOG_TAG, mediaEventString)
|
|
43
54
|
executeJavaScript(mediaEventString)
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
fun bridgeAndroidToJavaScript_IAPValidationError(message: String) {
|
|
47
58
|
Log.d(LOG_TAG, "bridgeAndroidToJavaScript_IAPValidationError")
|
|
48
|
-
executeJavaScript("javascript:((function dispatchEvent(){var evt = document.createEvent('Events');evt.initEvent('iapError',true,true);evt.message='$message';document.dispatchEvent(evt);})())")
|
|
59
|
+
executeJavaScript("javascript:((function dispatchEvent(){var evt = document.createEvent('Events');evt.initEvent('${iapEventPrefix}iapError',true,true);evt.message='$message';document.dispatchEvent(evt);})())")
|
|
49
60
|
}
|
|
50
61
|
|
|
51
|
-
fun bridgeAndroidToJavaScript_IAPValidationSuccess(
|
|
52
|
-
|
|
53
|
-
|
|
62
|
+
fun bridgeAndroidToJavaScript_IAPValidationSuccess(
|
|
63
|
+
userId: String,
|
|
64
|
+
receiptId: String,
|
|
65
|
+
sku: String
|
|
66
|
+
) {
|
|
67
|
+
Log.d(
|
|
68
|
+
LOG_TAG,
|
|
69
|
+
"bridgeAndroidToJavaScript_IAPValidationSuccess sku=$sku receiptId=$receiptId"
|
|
70
|
+
)
|
|
71
|
+
executeJavaScript("javascript:((function dispatchEvent(){var evt = document.createEvent('Events');evt.initEvent('${iapEventPrefix}iapSuccess',true,true);evt.userId='$userId';evt.receiptId='$receiptId';evt.sku='$sku';document.dispatchEvent(evt);})())")
|
|
54
72
|
}
|
|
55
73
|
|
|
56
74
|
fun bridgeIAPGetProductDataSuccess(okSkus: String, badSkus: String) {
|
|
57
75
|
Log.d(LOG_TAG, "bridgeIAPGetProductDataSuccess ok=$okSkus bad=$badSkus")
|
|
58
|
-
executeJavaScript("javascript:((function dispatchEvent(){var evt = document.createEvent('Events');evt.initEvent('onInAppProductDetailSuccess',true,true);evt.okSkus=$okSkus;evt.badSkus=$badSkus;document.dispatchEvent(evt);})())")
|
|
76
|
+
executeJavaScript("javascript:((function dispatchEvent(){var evt = document.createEvent('Events');evt.initEvent('${iapEventPrefix}onInAppProductDetailSuccess',true,true);evt.okSkus=$okSkus;evt.badSkus=$badSkus;document.dispatchEvent(evt);})())")
|
|
59
77
|
}
|
|
60
78
|
|
|
61
79
|
fun bridgeIAPGetProductDataFail(reason: String) {
|
|
62
80
|
Log.d(LOG_TAG, "bridgeIAPGetProductDataFail")
|
|
63
|
-
executeJavaScript("javascript:((function dispatchEvent(){var evt = document.createEvent('Events');evt.initEvent('onInAppProductDetailError',true,true);evt.message='$reason';document.dispatchEvent(evt);})())")
|
|
81
|
+
executeJavaScript("javascript:((function dispatchEvent(){var evt = document.createEvent('Events');evt.initEvent('${iapEventPrefix}onInAppProductDetailError',true,true);evt.message='$reason';document.dispatchEvent(evt);})())")
|
|
64
82
|
}
|
|
65
83
|
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
package com.twentyfouri.tvbridge.configuration
|
|
2
2
|
|
|
3
|
+
import com.twentyfouri.tvbridge.BuildConfig
|
|
4
|
+
|
|
3
5
|
class BridgeConfig {
|
|
4
6
|
var baseUrl: String = "http://portal.24imedia.tv/"
|
|
5
7
|
var userAgent: UserAgent = UserAgent()
|
|
6
8
|
var drm: Drm = Drm()
|
|
7
9
|
var isEnabledTouchSound: Boolean = false
|
|
8
10
|
var playerAppName: String? = null
|
|
11
|
+
var keyEventPrefix: String = ""
|
|
12
|
+
var mediaEventPrefix: String = ""
|
|
13
|
+
var iapEventPrefix: String = ""
|
|
14
|
+
var customizableUrl: Boolean = BuildConfig.DEBUG
|
|
15
|
+
var startWithHighVideoQuality: Boolean = false
|
|
9
16
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
package com.twentyfouri.tvbridge.global.di
|
|
2
2
|
|
|
3
3
|
import android.app.Activity
|
|
4
|
-
import com.twentyfouri.
|
|
4
|
+
import com.twentyfouri.player.base.PlayerFactoryBase
|
|
5
|
+
import com.twentyfouri.tvbridge.configuration.BridgeConfig
|
|
6
|
+
import com.twentyfouri.tvbridge.global.utils.PlayerFactoryExoPlayerCustomized
|
|
5
7
|
import dagger.Module
|
|
6
8
|
import dagger.Provides
|
|
7
9
|
|
|
@@ -14,6 +16,6 @@ class ActivityModule(
|
|
|
14
16
|
activity
|
|
15
17
|
|
|
16
18
|
@Provides
|
|
17
|
-
fun providePlayerFactory() =
|
|
18
|
-
|
|
19
|
+
fun providePlayerFactory(config: BridgeConfig): PlayerFactoryBase =
|
|
20
|
+
PlayerFactoryExoPlayerCustomized(config)
|
|
19
21
|
}
|
|
@@ -3,28 +3,28 @@ package com.twentyfouri.tvbridge.global.utils
|
|
|
3
3
|
import android.util.Base64
|
|
4
4
|
import android.util.Log
|
|
5
5
|
import com.google.android.exoplayer2.drm.ExoMediaDrm
|
|
6
|
-
import com.google.android.exoplayer2.
|
|
7
|
-
import com.twentyfouri.smartexoplayer.drm.SmartHttpMediaDrmCallback
|
|
8
|
-
import com.twentyfouri.smartexoplayer.drm.SmartMediaDrmCallback
|
|
9
|
-
import com.twentyfouri.smartexoplayer.model.DrmConfiguration
|
|
10
|
-
import com.twentyfouri.tvbridge.BuildConfig
|
|
6
|
+
import com.google.android.exoplayer2.drm.MediaDrmCallback
|
|
11
7
|
import java.util.*
|
|
12
8
|
|
|
13
9
|
class LoggingDrmCallback(
|
|
14
|
-
private val
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
private val base: MediaDrmCallback,
|
|
11
|
+
private val licenseUrl: String? = null
|
|
12
|
+
) : MediaDrmCallback {
|
|
13
|
+
override fun executeProvisionRequest(
|
|
14
|
+
uuid: UUID,
|
|
15
|
+
request: ExoMediaDrm.ProvisionRequest
|
|
16
|
+
): ByteArray {
|
|
17
|
+
return base.executeProvisionRequest(uuid, request)
|
|
18
|
+
}
|
|
18
19
|
|
|
19
|
-
@Throws(Exception::class)
|
|
20
20
|
override fun executeKeyRequest(uuid: UUID, request: ExoMediaDrm.KeyRequest): ByteArray {
|
|
21
|
-
if (BuildConfig.DEBUG) {
|
|
21
|
+
if (com.twentyfouri.player.exoplayer.BuildConfig.DEBUG) {
|
|
22
22
|
Log.v(TAG, "DRM Embedded URL: ${request.licenseServerUrl}")
|
|
23
|
-
Log.v(TAG, "DRM Default URL: $
|
|
23
|
+
Log.v(TAG, "DRM Default URL: $licenseUrl")
|
|
24
24
|
Log.v(TAG, "DRM request:\n${request.data.toBase64()}")
|
|
25
25
|
}
|
|
26
|
-
val response =
|
|
27
|
-
if (BuildConfig.DEBUG) {
|
|
26
|
+
val response = base.executeKeyRequest(uuid, request)
|
|
27
|
+
if (com.twentyfouri.player.exoplayer.BuildConfig.DEBUG) {
|
|
28
28
|
Log.v(TAG, "DRM response:\n" + response.toBase64())
|
|
29
29
|
}
|
|
30
30
|
return response
|
|
@@ -32,15 +32,7 @@ class LoggingDrmCallback(
|
|
|
32
32
|
|
|
33
33
|
private fun ByteArray.toBase64(): String = Base64.encodeToString(this, Base64.DEFAULT)
|
|
34
34
|
|
|
35
|
-
class Factory(
|
|
36
|
-
private val licenseUrl: String?,
|
|
37
|
-
private val forceDefaultUrl: Boolean
|
|
38
|
-
) : SmartMediaDrmCallback.Factory {
|
|
39
|
-
override fun build(drm: DrmConfiguration, dataSourceFactory: HttpDataSource.Factory): SmartMediaDrmCallback =
|
|
40
|
-
LoggingDrmCallback(licenseUrl, forceDefaultUrl, dataSourceFactory)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
35
|
companion object {
|
|
44
|
-
|
|
36
|
+
const val TAG = "DrmCallback"
|
|
45
37
|
}
|
|
46
38
|
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
package com.twentyfouri.tvbridge.global.utils
|
|
2
|
+
|
|
3
|
+
import android.net.Uri
|
|
4
|
+
import android.util.Base64
|
|
5
|
+
import android.util.Log
|
|
6
|
+
import com.google.android.exoplayer2.C
|
|
7
|
+
import com.google.android.exoplayer2.drm.ExoMediaDrm
|
|
8
|
+
import com.google.android.exoplayer2.drm.MediaDrmCallback
|
|
9
|
+
import com.google.android.exoplayer2.upstream.DataSourceInputStream
|
|
10
|
+
import com.google.android.exoplayer2.upstream.DataSpec
|
|
11
|
+
import com.google.android.exoplayer2.upstream.HttpDataSource
|
|
12
|
+
import com.google.android.exoplayer2.util.Util
|
|
13
|
+
import com.twentyfouri.tvbridge.BuildConfig
|
|
14
|
+
import org.json.JSONException
|
|
15
|
+
import org.json.JSONObject
|
|
16
|
+
import java.io.BufferedReader
|
|
17
|
+
import java.io.IOException
|
|
18
|
+
import java.io.InputStreamReader
|
|
19
|
+
import java.net.MalformedURLException
|
|
20
|
+
import java.net.URL
|
|
21
|
+
import java.net.URLEncoder
|
|
22
|
+
import java.util.HashMap
|
|
23
|
+
import java.util.UUID
|
|
24
|
+
|
|
25
|
+
class OverriddenHttpMediaDrmCallback(
|
|
26
|
+
private val defaultLicenseUrl: String?,
|
|
27
|
+
private val forceDefaultLicenseUrl: Boolean,
|
|
28
|
+
private val dataSourceFactory: HttpDataSource.Factory
|
|
29
|
+
) : MediaDrmCallback {
|
|
30
|
+
|
|
31
|
+
@Throws(IOException::class)
|
|
32
|
+
override fun executeProvisionRequest(uuid: UUID, request: ExoMediaDrm.ProvisionRequest): ByteArray {
|
|
33
|
+
Log.d(TAG, "executeProvisionRequest")
|
|
34
|
+
val url = request.defaultUrl + "&signedRequest=" + String(request.data)
|
|
35
|
+
return executePost(dataSourceFactory, url, ByteArray(0), null)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@Throws(Exception::class)
|
|
39
|
+
override fun executeKeyRequest(uuid: UUID, request: ExoMediaDrm.KeyRequest): ByteArray {
|
|
40
|
+
Log.d(TAG, "executeKeyRequest")
|
|
41
|
+
var url: String? = request.licenseServerUrl
|
|
42
|
+
if (forceDefaultLicenseUrl || url.isNullOrEmpty()) {
|
|
43
|
+
url = defaultLicenseUrl
|
|
44
|
+
}
|
|
45
|
+
val requestProperties: MutableMap<String, String> = HashMap()
|
|
46
|
+
// Add standard request properties for supported schemes.
|
|
47
|
+
val contentType = "text/plain"
|
|
48
|
+
requestProperties["Content-Type"] = contentType
|
|
49
|
+
if (C.PLAYREADY_UUID == uuid) {
|
|
50
|
+
requestProperties["SOAPAction"] = "http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//adding data to url
|
|
54
|
+
val challenge = request.data
|
|
55
|
+
val encoded = URLEncoder.encode(Base64.encodeToString(challenge, Base64.NO_WRAP), "UTF-8")
|
|
56
|
+
url = appendQuery(url, "_widevineChallenge=$encoded")
|
|
57
|
+
if (BuildConfig.DEBUG) {
|
|
58
|
+
Log.d(TAG, "Executing post on url $url with content type $contentType")
|
|
59
|
+
}
|
|
60
|
+
return executeGet(url)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
companion object {
|
|
64
|
+
private const val TAG = "DrmCallback"
|
|
65
|
+
|
|
66
|
+
private fun executeGet(urlString: String): ByteArray {
|
|
67
|
+
try {
|
|
68
|
+
val licenserRequest = URL(urlString)
|
|
69
|
+
val yc = licenserRequest.openConnection()
|
|
70
|
+
val input = BufferedReader(InputStreamReader(yc.getInputStream()))
|
|
71
|
+
val builder = StringBuilder()
|
|
72
|
+
var inputLine: String?
|
|
73
|
+
while (input.readLine().also { inputLine = it } != null) {
|
|
74
|
+
builder.append(inputLine)
|
|
75
|
+
}
|
|
76
|
+
input.close()
|
|
77
|
+
val jsonObject = JSONObject(builder.toString())
|
|
78
|
+
val container = jsonObject.getJSONObject("getWidevineLicenseResponse")
|
|
79
|
+
val license = container.getString("license")
|
|
80
|
+
if (BuildConfig.DEBUG) {
|
|
81
|
+
Log.d(TAG, "Finished post: $license")
|
|
82
|
+
}
|
|
83
|
+
return Base64.decode(license, Base64.NO_WRAP)
|
|
84
|
+
} catch (e: MalformedURLException) {
|
|
85
|
+
e.printStackTrace()
|
|
86
|
+
} catch (e: IOException) {
|
|
87
|
+
e.printStackTrace()
|
|
88
|
+
} catch (e: JSONException) {
|
|
89
|
+
e.printStackTrace()
|
|
90
|
+
}
|
|
91
|
+
return byteArrayOf()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@Throws(IOException::class)
|
|
95
|
+
private fun executePost(
|
|
96
|
+
dataSourceFactory: HttpDataSource.Factory,
|
|
97
|
+
url: String,
|
|
98
|
+
data: ByteArray,
|
|
99
|
+
requestProperties: Map<String, String>?
|
|
100
|
+
): ByteArray {
|
|
101
|
+
Log.d(TAG, "executePost")
|
|
102
|
+
val dataSource = dataSourceFactory.createDataSource()
|
|
103
|
+
if (requestProperties != null) {
|
|
104
|
+
for ((key, value) in requestProperties) {
|
|
105
|
+
dataSource.setRequestProperty(key, value)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
val dataSpec = DataSpec.Builder()
|
|
109
|
+
.setUri(Uri.parse(url))
|
|
110
|
+
.setFlags(DataSpec.FLAG_ALLOW_GZIP)
|
|
111
|
+
.setHttpMethod(DataSpec.HTTP_METHOD_POST)
|
|
112
|
+
.setHttpBody(data)
|
|
113
|
+
.setHttpRequestHeaders(requestProperties ?: emptyMap())
|
|
114
|
+
.build()
|
|
115
|
+
val inputStream = DataSourceInputStream(dataSource, dataSpec)
|
|
116
|
+
return try {
|
|
117
|
+
Util.toByteArray(inputStream)
|
|
118
|
+
} finally {
|
|
119
|
+
Util.closeQuietly(inputStream)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fun appendQuery(url: String?, appendQuery: String): String {
|
|
124
|
+
val oldUri = Uri.parse(url)
|
|
125
|
+
var newQuery = oldUri.query
|
|
126
|
+
if (newQuery == null) {
|
|
127
|
+
newQuery = appendQuery
|
|
128
|
+
} else {
|
|
129
|
+
newQuery += "&$appendQuery"
|
|
130
|
+
}
|
|
131
|
+
return oldUri.scheme + "://" + oldUri.authority + oldUri.path + "?" + newQuery
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
package com.twentyfouri.tvbridge.global.utils
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import com.google.android.exoplayer2.C
|
|
5
|
+
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager
|
|
6
|
+
import com.google.android.exoplayer2.drm.DrmSessionManager
|
|
7
|
+
import com.google.android.exoplayer2.drm.FrameworkMediaDrm
|
|
8
|
+
import com.google.android.exoplayer2.drm.MediaDrmCallback
|
|
9
|
+
import com.google.android.exoplayer2.upstream.HttpDataSource
|
|
10
|
+
import com.google.android.exoplayer2.util.Util
|
|
11
|
+
import com.twentyfouri.player.base.DrmPlayready
|
|
12
|
+
import com.twentyfouri.player.base.DrmWidevine
|
|
13
|
+
import com.twentyfouri.player.base.Source
|
|
14
|
+
import com.twentyfouri.player.base.SourceDrm
|
|
15
|
+
import com.twentyfouri.player.exoplayer.*
|
|
16
|
+
import com.twentyfouri.tvbridge.configuration.BridgeConfig
|
|
17
|
+
|
|
18
|
+
class PlayerFactoryExoPlayerCustomized(
|
|
19
|
+
private val config: BridgeConfig
|
|
20
|
+
) : PlayerFactoryExoPlayer() {
|
|
21
|
+
override fun createSharedComponents(context: Context): SharedComponents =
|
|
22
|
+
CustomizedSharedComponents(context)
|
|
23
|
+
|
|
24
|
+
private inner class CustomizedSharedComponents(context: Context) : DefaultSharedComponents(context) {
|
|
25
|
+
|
|
26
|
+
override val drmProvider: DrmSessionManagerProvider by lazy { CustomizedDrmSessionManagerProvider() }
|
|
27
|
+
|
|
28
|
+
override val applicationName: String
|
|
29
|
+
get() = config.playerAppName ?: super.applicationName
|
|
30
|
+
|
|
31
|
+
override fun buildIsolatedComponents(): PlayerComponents {
|
|
32
|
+
return CustomizedPlayerComponents(this)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private inner class CustomizedPlayerComponents(shared: SharedComponents) : DefaultPlayerComponents(shared) {
|
|
37
|
+
override val handleAudioFocus: Boolean
|
|
38
|
+
get() = false
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private class CustomizedDrmSessionManagerProvider : DefaultDrmSessionManagerProvider() {
|
|
42
|
+
override fun buildDrmSessionManager(
|
|
43
|
+
dataSourceFactory: AuthorizedDataSourceProvider,
|
|
44
|
+
source: Source,
|
|
45
|
+
sourceDrm: SourceDrm
|
|
46
|
+
): DrmSessionManager? {
|
|
47
|
+
if (sourceDrm is SourceDrmOverridden) {
|
|
48
|
+
return buildDrmOverridden(dataSourceFactory, sourceDrm)
|
|
49
|
+
} else {
|
|
50
|
+
return super.buildDrmSessionManager(dataSourceFactory, source, sourceDrm)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private fun buildDrmOverridden(dataSourceFactory: AuthorizedDataSourceProvider, sourceDrm: SourceDrmOverridden): DrmSessionManager? {
|
|
55
|
+
val drmUuid = when (sourceDrm.packageName) {
|
|
56
|
+
DrmWidevine.packageName -> C.WIDEVINE_UUID
|
|
57
|
+
DrmPlayready.packageName -> C.PLAYREADY_UUID
|
|
58
|
+
else -> C.UUID_NIL
|
|
59
|
+
}
|
|
60
|
+
val licenseUrl = sourceDrm.licenseServer?.toString() ?: ""
|
|
61
|
+
val forceLicenseUrl = sourceDrm.forceLicenseServer
|
|
62
|
+
val httpDataSourceFactory = dataSourceFactory.buildHttpDataSourceFactory(sourceDrm.authorization)
|
|
63
|
+
|
|
64
|
+
val drmCallback = LoggingDrmCallback(
|
|
65
|
+
OverriddenHttpMediaDrmCallback(
|
|
66
|
+
licenseUrl,
|
|
67
|
+
forceLicenseUrl,
|
|
68
|
+
httpDataSourceFactory
|
|
69
|
+
),
|
|
70
|
+
licenseUrl
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return DefaultDrmSessionManager.Builder()
|
|
74
|
+
.setUuidAndExoMediaDrmProvider(drmUuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
|
|
75
|
+
.setMultiSession(false)
|
|
76
|
+
.build(drmCallback)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override fun buildDrmCallback(licenseUrl: String, forceLicenseUrl: Boolean, httpDataSourceFactory: HttpDataSource.Factory): MediaDrmCallback {
|
|
80
|
+
return LoggingDrmCallback(super.buildDrmCallback(licenseUrl, forceLicenseUrl, httpDataSourceFactory), licenseUrl)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
package com.twentyfouri.tvbridge.global.utils
|
|
2
|
+
|
|
3
|
+
import com.twentyfouri.player.base.SourceAuthorization
|
|
4
|
+
import com.twentyfouri.player.base.SourceDrm
|
|
5
|
+
import java.net.URI
|
|
6
|
+
|
|
7
|
+
open class SourceDrmOverridden protected constructor(
|
|
8
|
+
builder: Builder
|
|
9
|
+
) : SourceDrm() {
|
|
10
|
+
|
|
11
|
+
override val packageName: String = builder.packageName
|
|
12
|
+
?: throw IllegalStateException("Missing SourceDrmOverridden.packageName")
|
|
13
|
+
val licenseServer: URI? = builder.licenseServer
|
|
14
|
+
val forceLicenseServer: Boolean = builder.forceLicenseServer
|
|
15
|
+
val authorization: SourceAuthorization? = builder.authorization
|
|
16
|
+
|
|
17
|
+
override fun hashCode(): Int = licenseServer.hashCode()
|
|
18
|
+
|
|
19
|
+
override fun toString(): String = "SourceDrmOverridden($licenseServer)"
|
|
20
|
+
|
|
21
|
+
override fun equals(other: Any?): Boolean = other is SourceDrmOverridden &&
|
|
22
|
+
packageName == other.packageName &&
|
|
23
|
+
licenseServer == other.licenseServer &&
|
|
24
|
+
forceLicenseServer == other.forceLicenseServer &&
|
|
25
|
+
authorization == other.authorization
|
|
26
|
+
|
|
27
|
+
override fun buildUpon() = Builder()
|
|
28
|
+
.also { it.applyFrom(this) }
|
|
29
|
+
|
|
30
|
+
open class Builder : SourceDrm.Builder() {
|
|
31
|
+
var packageName: String? = null
|
|
32
|
+
var licenseServer: URI? = null
|
|
33
|
+
var forceLicenseServer: Boolean = false
|
|
34
|
+
var authorization: SourceAuthorization? = null
|
|
35
|
+
|
|
36
|
+
open fun applyFrom(source: SourceDrmOverridden) {
|
|
37
|
+
packageName = source.packageName
|
|
38
|
+
licenseServer = source.licenseServer
|
|
39
|
+
forceLicenseServer = source.forceLicenseServer
|
|
40
|
+
authorization = source.authorization
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override fun build() =
|
|
44
|
+
SourceDrmOverridden(this)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
inline fun SourceDrmOverridden(block: SourceDrmOverridden.Builder.() -> Unit) =
|
|
49
|
+
SourceDrmOverridden.Builder().apply(block).build()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package com.twentyfouri.tvbridge.jump
|
|
2
2
|
|
|
3
3
|
import android.util.Log
|
|
4
|
+
import com.google.android.exoplayer2.ExoPlayer
|
|
4
5
|
import com.google.android.exoplayer2.SimpleExoPlayer
|
|
5
6
|
import com.google.gson.JsonObject
|
|
6
7
|
import com.twentyfouri.tvbridge.BuildConfig
|
|
@@ -40,8 +41,9 @@ class JumpAnalytics {
|
|
|
40
41
|
hasContent = true
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
fun setVideoPlayer(player:
|
|
44
|
-
|
|
44
|
+
fun setVideoPlayer(player: ExoPlayer?) {
|
|
45
|
+
@Suppress("DEPRECATION")
|
|
46
|
+
if (player !is SimpleExoPlayer) {
|
|
45
47
|
/* NOTICE: passing null also causes insights.setContentInfo(null) */
|
|
46
48
|
jump.exoplayerextension.JumpKit.insights.setVideoPlayer(null)
|
|
47
49
|
hasContent = false
|
|
@@ -37,6 +37,8 @@ class BridgeWebView @JvmOverloads constructor(
|
|
|
37
37
|
webSettings.useWideViewPort = true
|
|
38
38
|
webSettings.loadWithOverviewMode = true
|
|
39
39
|
webSettings.mediaPlaybackRequiresUserGesture = false
|
|
40
|
+
webSettings.allowFileAccess = true
|
|
41
|
+
webSettings.allowContentAccess = true
|
|
40
42
|
webSettings.userAgentString = config?.userAgent?.value ?: userAgent
|
|
41
43
|
|
|
42
44
|
// Log the settings of the WebView.
|
|
@@ -7,8 +7,11 @@ object MediaEventType {
|
|
|
7
7
|
const val TIME_UPDATE = "timeupdate"
|
|
8
8
|
const val ERROR = "error"
|
|
9
9
|
const val PLAYING = "playing"
|
|
10
|
+
const val PAUSE = "pause"
|
|
11
|
+
const val PLAY = "pause"
|
|
10
12
|
const val ENDED = "ended"
|
|
11
13
|
const val WAITING = "waiting"
|
|
14
|
+
const val SEEKING = "seeking"
|
|
12
15
|
const val SEEKED = "seeked"
|
|
13
16
|
const val KEY_PRESS = "keypress"
|
|
14
17
|
const val KEY_UP = "keyup"
|