@24i/smartapps-bigscreen-rnv-build-engine 0.17.2 → 0.17.3-alpha.1

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.
Files changed (88) hide show
  1. package/dist/src/__tests__/resolvers/resolveBuildFileBaseName.spec.js +80 -0
  2. package/dist/src/resolvers/index.js +3 -1
  3. package/dist/src/resolvers/resolveBuildFileBaseName.js +34 -0
  4. package/dist/src/resolvers/resolveBuildFileName.js +3 -27
  5. package/dist/src/scripts/releaseAllWithScheme.js +1 -1
  6. package/dist/src/sdks/index.js +5 -1
  7. package/dist/src/sdks/sdk-androidtv/index.js +17 -0
  8. package/dist/src/sdks/sdk-androidtv/sdkAndroidtv.js +264 -0
  9. package/dist/src/sdks/sdk-utils/sdk-installer/services/unzip.js +3 -2
  10. package/dist/src/tasks/task.rnv.build.js +3 -1
  11. package/dist/src/tasks/task.rnv.configure.js +3 -1
  12. package/dist/src/tasks/task.rnv.platform.configure.js +4 -1
  13. package/dist/templates/platforms/_shared/template.js +3 -3
  14. package/dist/templates/platforms/androidtv/project/README.md +196 -0
  15. package/dist/templates/platforms/androidtv/project/amazoniap/build.gradle +37 -0
  16. package/dist/templates/platforms/androidtv/project/amazoniap/consumer-rules.pro +0 -0
  17. package/dist/templates/platforms/androidtv/project/amazoniap/libs/in-app-purchasing-2.0.76.jar +0 -0
  18. package/dist/templates/platforms/androidtv/project/amazoniap/proguard-rules.pro +21 -0
  19. package/dist/templates/platforms/androidtv/project/amazoniap/src/main/AndroidManifest.xml +4 -0
  20. package/dist/templates/platforms/androidtv/project/amazoniap/src/main/java/com/twentyfouri/bridgetv/amazoniap/AmazonIapSevice.kt +106 -0
  21. package/dist/templates/platforms/androidtv/project/app/build.gradle +112 -0
  22. package/dist/templates/platforms/androidtv/project/app/proguard-rules.pro +32 -0
  23. package/dist/templates/platforms/androidtv/project/app/src/bigscreenAppstage/java/com/twentyfouri/tvbridge/Flavor.kt +5 -0
  24. package/dist/templates/platforms/androidtv/project/app/src/bigscreenAppstage/res/raw/configuration.json +9 -0
  25. package/dist/templates/platforms/androidtv/project/app/src/main/AndroidManifest.xml +53 -0
  26. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/FlavorBase.kt +12 -0
  27. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/bridge/BridgeFromJavaScript.kt +392 -0
  28. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/bridge/BridgeToJavaScript.kt +89 -0
  29. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/configuration/BridgeConfig.kt +16 -0
  30. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/configuration/ConfigCallback.kt +6 -0
  31. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/configuration/ConfigProvider.kt +27 -0
  32. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/configuration/Drm.kt +10 -0
  33. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/configuration/UserAgent.kt +6 -0
  34. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/BaseApplication.kt +73 -0
  35. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/di/ActivityComponent.kt +13 -0
  36. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/di/ActivityModule.kt +17 -0
  37. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/di/ApplicationComponent.kt +26 -0
  38. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/di/ApplicationModule.kt +51 -0
  39. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/di/PerActivity.kt +26 -0
  40. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/utils/LocalStorage.kt +30 -0
  41. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/utils/LoggingDrmCallback.kt +38 -0
  42. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/utils/OverriddenHttpMediaDrmCallback.kt +134 -0
  43. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/utils/PlayerFactoryExoPlayerCustomized.kt +84 -0
  44. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/utils/SimpleActivityLifecycleCallback.kt +32 -0
  45. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/utils/SourceDrmOverridden.kt +49 -0
  46. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/jump/JumpAnalytics.kt +211 -0
  47. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/sound/TouchPlayer.kt +86 -0
  48. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/client/BridgeWebView.kt +82 -0
  49. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/client/BridgeWebViewClient.kt +114 -0
  50. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/model/AnalyticsPlaybackInfo.kt +10 -0
  51. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/model/ContentModel.kt +14 -0
  52. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/model/DrmModel.kt +20 -0
  53. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/model/MediaEventType.kt +20 -0
  54. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/parser/ContentModelParser.kt +16 -0
  55. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/parser/DeviceInfoParser.kt +22 -0
  56. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/parser/PlaybackInfoParser.kt +10 -0
  57. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/parser/TrackJsonModelParser.kt +58 -0
  58. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/view/Mainview.kt +20 -0
  59. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/view/PlayerCache.kt +15 -0
  60. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/view/WebViewActivity.kt +306 -0
  61. package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/viewmodel/WebViewModel.kt +609 -0
  62. package/dist/templates/platforms/androidtv/project/app/src/main/res/drawable/button_background_focus.xml +10 -0
  63. package/dist/templates/platforms/androidtv/project/app/src/main/res/drawable/button_background_normal.xml +7 -0
  64. package/dist/templates/platforms/androidtv/project/app/src/main/res/drawable/button_background_pressed.xml +7 -0
  65. package/dist/templates/platforms/androidtv/project/app/src/main/res/drawable/button_focus.xml +8 -0
  66. package/dist/templates/platforms/androidtv/project/app/src/main/res/layout/activity_web_view.xml +76 -0
  67. package/dist/templates/platforms/androidtv/project/app/src/main/res/raw/click.ogg +0 -0
  68. package/dist/templates/platforms/androidtv/project/app/src/main/res/values/styles.xml +12 -0
  69. package/dist/templates/platforms/androidtv/project/app/src/main/res/xml/network_security_config.xml +4 -0
  70. package/dist/templates/platforms/androidtv/project/build.gradle +48 -0
  71. package/dist/templates/platforms/androidtv/project/common/build.gradle +36 -0
  72. package/dist/templates/platforms/androidtv/project/common/consumer-rules.pro +0 -0
  73. package/dist/templates/platforms/androidtv/project/common/proguard-rules.pro +21 -0
  74. package/dist/templates/platforms/androidtv/project/common/src/main/AndroidManifest.xml +4 -0
  75. package/dist/templates/platforms/androidtv/project/common/src/main/java/com/twentyfouri/tvbridge/common/IapService.kt +114 -0
  76. package/dist/templates/platforms/androidtv/project/googleiap/build.gradle +38 -0
  77. package/dist/templates/platforms/androidtv/project/googleiap/consumer-rules.pro +0 -0
  78. package/dist/templates/platforms/androidtv/project/googleiap/proguard-rules.pro +21 -0
  79. package/dist/templates/platforms/androidtv/project/googleiap/src/main/AndroidManifest.xml +6 -0
  80. package/dist/templates/platforms/androidtv/project/googleiap/src/main/java/com/twentyfouri/tvbridge/googleiap/GoogleIapService.kt +29 -0
  81. package/dist/templates/platforms/androidtv/project/gradle/wrapper/gradle-wrapper.jar +0 -0
  82. package/dist/templates/platforms/androidtv/project/gradle/wrapper/gradle-wrapper.properties +6 -0
  83. package/dist/templates/platforms/androidtv/project/gradle.properties +4 -0
  84. package/dist/templates/platforms/androidtv/project/gradlew +185 -0
  85. package/dist/templates/platforms/androidtv/project/gradlew.bat +104 -0
  86. package/dist/templates/platforms/androidtv/project/proguard-rules.pro +42 -0
  87. package/dist/templates/platforms/androidtv/project/settings.gradle +4 -0
  88. package/package.json +3 -2
@@ -0,0 +1,609 @@
1
+ package com.twentyfouri.tvbridge.webview.viewmodel
2
+
3
+ import android.app.Activity
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.graphics.Bitmap
7
+ import android.graphics.Canvas
8
+ import android.net.Uri
9
+ import android.os.Build
10
+ import android.util.Log
11
+ import android.view.KeyEvent
12
+ import android.view.View
13
+ import android.webkit.CookieManager
14
+ import android.webkit.WebChromeClient
15
+ import android.webkit.WebView
16
+ import androidx.databinding.BaseObservable
17
+ import androidx.databinding.Bindable
18
+ import com.google.android.exoplayer2.C
19
+ import com.google.android.exoplayer2.SeekParameters
20
+ import com.google.gson.JsonArray
21
+ import com.google.gson.JsonObject
22
+ import com.twentyfouri.player.base.*
23
+ import com.twentyfouri.player.exoplayer.PlayerExoPlayer
24
+ import com.twentyfouri.tvbridge.BR
25
+ import com.twentyfouri.tvbridge.BuildConfig
26
+ import com.twentyfouri.tvbridge.Flavor
27
+ import com.twentyfouri.tvbridge.bridge.BridgeFromJavaScript
28
+ import com.twentyfouri.tvbridge.bridge.BridgeToJavaScript
29
+ import com.twentyfouri.tvbridge.common.*
30
+ import com.twentyfouri.tvbridge.configuration.BridgeConfig
31
+ import com.twentyfouri.tvbridge.global.utils.LocalStorage
32
+ import com.twentyfouri.tvbridge.global.utils.SourceDrmOverridden
33
+ import com.twentyfouri.tvbridge.jump.JumpAnalytics
34
+ import com.twentyfouri.tvbridge.webview.client.BridgeWebView
35
+ import com.twentyfouri.tvbridge.webview.client.BridgeWebViewClient
36
+ import com.twentyfouri.tvbridge.webview.client.BridgeWebViewClient.BridgeWebViewClientCallback
37
+ import com.twentyfouri.tvbridge.webview.model.AnalyticsPlaybackInfo
38
+ import com.twentyfouri.tvbridge.webview.model.ContentModel
39
+ import com.twentyfouri.tvbridge.webview.model.DrmModel
40
+ import com.twentyfouri.tvbridge.webview.model.MediaEventType
41
+ import com.twentyfouri.tvbridge.webview.view.Mainview
42
+ import com.twentyfouri.tvbridge.webview.view.PlayerCache
43
+ import org.json.JSONArray
44
+ import org.json.JSONException
45
+ import org.json.JSONObject
46
+ import java.net.NetworkInterface
47
+ import java.net.URI
48
+ import javax.inject.Inject
49
+
50
+ /**
51
+ * Created by destivar on 15/02/16.
52
+ */
53
+ class WebViewModel @Inject constructor(
54
+ private val context: Context,
55
+ private val localStorage: LocalStorage,
56
+ private val config: BridgeConfig,
57
+ private val iapService: IapService,
58
+ val playerFactory: PlayerFactoryBase
59
+ ) : BaseObservable(), EventListener {
60
+ var player: PlayerBase? = null
61
+ private var view: Mainview? = null
62
+ private val jsBridge = BridgeToJavaScript(config) { s: String? ->
63
+ view?.loadUrl(s)
64
+ }
65
+ private val iapCallback = InnerIapCallback()
66
+ private val playerCache = PlayerCache()
67
+ private val jumpAnalytics = JumpAnalytics()
68
+
69
+ private var contentModel: ContentModel? = null
70
+
71
+ val audioTrack: AudioTrack?
72
+ get() = player?.selectedAudioTrack
73
+ val subtitleTrack: TextTrack?
74
+ get() = player?.selectedTextTrack
75
+
76
+ var appUrl: String? = null
77
+
78
+ var urlSetterVisible: Boolean = false
79
+ set(value) {
80
+ if (field == value) return
81
+ field = value
82
+ notifyPropertyChanged(BR.urlSetterVisibility)
83
+ }
84
+
85
+ @get:Bindable
86
+ val urlSetterVisibility: Int
87
+ get() = if (urlSetterVisible) View.VISIBLE else View.GONE
88
+
89
+ fun attachView(view: Mainview?) {
90
+ this.view = view
91
+ if (view != null) {
92
+ iapService.initialize()
93
+ checkNewPurchases()
94
+ }
95
+ }
96
+
97
+ fun configureWebView(webView: BridgeWebView) {
98
+
99
+ // Enable remote debugging on devices at API level 19 (KitKat) or higher.
100
+ if (BuildConfig.DEBUG) {
101
+ WebView.setWebContentsDebuggingEnabled(true)
102
+ }
103
+
104
+ // Add the JavaScript bridge.
105
+ webView.addJavascriptInterface(BridgeFromJavaScript(this), JAVA_SCRIPT_BRIDGE)
106
+
107
+ // WebView callback methods.
108
+ webView.webViewClient = BridgeWebViewClient(object : BridgeWebViewClientCallback {
109
+ override fun onUnhandledKeyEvent(view: WebView, event: KeyEvent) {
110
+ if (event.action == KeyEvent.ACTION_DOWN) {
111
+ sendKeyCode(event.keyCode, MediaEventType.KEY_DOWN)
112
+ } else if (event.action == KeyEvent.ACTION_UP) {
113
+ sendKeyCode(event.keyCode, MediaEventType.KEY_UP)
114
+ }
115
+ }
116
+ })
117
+
118
+ webView.webChromeClient = object : WebChromeClient() {
119
+ override fun getDefaultVideoPoster(): Bitmap? {
120
+ val bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565)
121
+ val canvas = Canvas(bitmap)
122
+ canvas.drawARGB(255, 0, 0, 0)
123
+ return bitmap
124
+ }
125
+
126
+ override fun onShowCustomView(view: View, callback: CustomViewCallback) {
127
+ val intent = Intent(Intent.ACTION_SEND)
128
+ intent.type = "video/*"
129
+ context.startActivity(intent)
130
+ }
131
+
132
+ override fun onHideCustomView() {}
133
+ }
134
+
135
+ webView.previewKeyEvent = this::dispatchKeyEvent
136
+
137
+ CookieManager.getInstance().setAcceptCookie(true)
138
+ CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true)
139
+ }
140
+
141
+ fun onClickGo() {
142
+ view?.loadUrl(appUrl)
143
+ urlSetterVisible = false
144
+ if (BuildConfig.DEBUG) {
145
+ view?.hideKeyboard()
146
+ }
147
+ }
148
+
149
+ fun setVideo(contentModel: ContentModel) {
150
+ logContentModel(contentModel)
151
+ playDirectly(contentModel)
152
+ }
153
+
154
+ private fun logContentModel(contentModel: ContentModel) {
155
+ Log.i("BridgeVideo", "Url: " + contentModel.streamUrl)
156
+ val drmModel = contentModel.drmModel
157
+ if (drmModel != null) {
158
+ Log.i("BridgeVideo", "DRM: " + drmModel.drmSchemeUuid)
159
+ Log.i("BridgeVideo", "LicenseUrl: " + drmModel.drmLicenseUrl)
160
+ }
161
+ }
162
+
163
+ private fun playInExoPlayerDemoApp(contentModel: ContentModel) {
164
+ val intent = Intent("com.google.android.exoplayer.demo.action.VIEW", Uri.parse(contentModel.streamUrl))
165
+ val drmModel = contentModel.drmModel
166
+ if (drmModel != null) {
167
+ intent.putExtra("drm_scheme", drmModel.drmSchemeUuid)
168
+ intent.putExtra("drm_license_url", drmModel.drmLicenseUrl)
169
+ }
170
+ try {
171
+ context.startActivity(intent)
172
+ } catch (e: Exception) {
173
+ Log.w("WebViewModel", "Failed to start ExoPlayer demo app", e)
174
+ }
175
+ }
176
+
177
+ private fun playDirectly(contentModel: ContentModel) {
178
+ this.contentModel = contentModel
179
+ (player as? PlayerExoPlayer)?.exoPlayer?.also { exoPlayer ->
180
+ jumpAnalytics.setVideoPlayer(exoPlayer)
181
+ exoPlayer.setSeekParameters(SeekParameters.CLOSEST_SYNC)
182
+ }
183
+ player?.also { player ->
184
+ player.source = Source {
185
+ url = contentModel.streamUrl?.let { URI(it) }
186
+ mimeType = detectMimeType(contentModel)
187
+ contentModel.drmModel?.let { buildDrm(it) }?.also { drm.add(it) }
188
+ }
189
+
190
+ if (isLowEndDevice) {
191
+ player.trackRestrictions = player.trackRestrictions.buildUpon {
192
+ maxBitrate = LOW_END_DEVICE_MAX_BITRATE
193
+ }
194
+ } else if (config.startWithHighVideoQuality) {
195
+ player.trackRestrictions = player.trackRestrictions.buildUpon {
196
+ abrStrategy = AbrStrategy.QUALITY
197
+ }
198
+ }
199
+
200
+ player.addEventListener(Event.ALL, this)
201
+
202
+ player.autoPlay = true
203
+ player.load()
204
+ player.play()
205
+ }
206
+ }
207
+
208
+ private fun detectMimeType(contentModel: ContentModel) =
209
+ when (contentModel.streamType) {
210
+ null, "" -> null
211
+ ".mpd", "mpd", "dash" -> ManifestDash.mimeType
212
+ ".ism", "ism", "ss" -> ManifestSmoothStreaming.mimeType
213
+ ".m3u8", "m3u8", "hls" -> ManifestHls.mimeType
214
+ else -> null
215
+ }
216
+
217
+ private fun buildDrm(drmModel: DrmModel): SourceDrm? {
218
+ val drmScheme = drmModel.drmSchemeUuid
219
+ val useOverride = drmModel.overrideDrmCallback ?: config.drm.shouldOverrideDrmCallback()
220
+ val useWidevine = drmScheme == "widevine" || drmScheme == C.WIDEVINE_UUID.toString()
221
+ val usePlayReady = drmScheme == "playready" || drmScheme == C.PLAYREADY_UUID.toString()
222
+ return when {
223
+ drmScheme == null -> null
224
+ useOverride -> SourceDrmOverridden {
225
+ packageName = when {
226
+ useWidevine -> DrmWidevine.packageName
227
+ usePlayReady -> DrmPlayready.packageName
228
+ else -> ""
229
+ }
230
+ licenseServer = drmModel.drmLicenseUrl?.let { URI(it) }
231
+ forceLicenseServer = true
232
+ }
233
+ useWidevine -> SourceDrmWidevine {
234
+ licenseServer = drmModel.drmLicenseUrl?.let { URI(it) }
235
+ forceLicenseServer = true
236
+ }
237
+ usePlayReady -> SourceDrmWidevine {
238
+ licenseServer = drmModel.drmLicenseUrl?.let { URI(it) }
239
+ forceLicenseServer = true
240
+ }
241
+ else -> SourceDrmUnrecognized(drmScheme)
242
+ }
243
+ }
244
+
245
+ private val isLowEndDevice: Boolean
246
+ get() = when (Build.MODEL) {
247
+ "AFTB" -> true
248
+ "AFTM" -> true
249
+ else -> false
250
+ }
251
+
252
+ fun startPlayer() {
253
+ player?.play()
254
+ }
255
+
256
+ fun resumePlayer() {
257
+ val savedContentModel = playerCache.savedContentModel
258
+ if (playerCache.loadFromCache && savedContentModel != null && playerCache.savedPlayerState !== PlayerCache.PlayerState.PAUSED) {
259
+ setVideo(savedContentModel)
260
+ var position = playerCache.savedPlayerPosition
261
+ val player = this.player ?: return
262
+ if (player.live) {
263
+ position = 0.0
264
+ }
265
+ player.seek(position)
266
+ view?.requestAudioFocus()
267
+ if (playerCache.savedPlayerState === PlayerCache.PlayerState.PLAYING) {
268
+ player.play()
269
+ }
270
+ } else {
271
+ if (player?.live == true) {
272
+ player?.seek(0.0)
273
+ }
274
+ player?.play()
275
+ }
276
+ }
277
+
278
+ fun pausePlayer() {
279
+ player?.pause()
280
+ }
281
+
282
+ fun stopPlayer() {
283
+ view?.setBlackCurtainVisibility(View.VISIBLE)
284
+ contentModel = null
285
+
286
+ player?.also { player ->
287
+ player.removeEventListener(Event.ALL, this)
288
+ player.unload()
289
+ jumpAnalytics.setVideoPlayer(null)
290
+ }
291
+ }
292
+
293
+ fun releasePlayer() {
294
+ val player = this.player ?: return
295
+ if (contentModel != null) {
296
+ playerCache.loadFromCache = true
297
+ playerCache.savedContentModel = contentModel
298
+ playerCache.savedPlayerDuration = player.duration
299
+ playerCache.savedPlayerPosition = player.currentTime
300
+ playerCache.savedPlayerState = if (player.playing) PlayerCache.PlayerState.PLAYING else PlayerCache.PlayerState.PAUSED
301
+ }
302
+ player.unload()
303
+ jumpAnalytics.setVideoPlayer(null)
304
+ }
305
+
306
+ val currentPosition: Double
307
+ get() = player?.currentTime ?: playerCache.savedPlayerPosition
308
+ val duration: Double
309
+ get() = player?.duration ?: playerCache.savedPlayerDuration
310
+
311
+ fun seekToPosition(position: Double) {
312
+ player?.seek(position)
313
+ }
314
+
315
+ val isPlayerPlaying: Boolean
316
+ get() = player?.playing == true
317
+ val isInPlayer: Boolean
318
+ get() = player != null && contentModel != null
319
+
320
+ fun loadUrl(url: String?) {
321
+ view?.loadUrl(url)
322
+ }
323
+
324
+ fun setSubtitlesVisibility(visibility: Boolean) {
325
+ val player = this.player ?: return
326
+ if (visibility) {
327
+ player.trackRestrictions = player.trackRestrictions.buildUpon {
328
+ enableCaptions = true
329
+ }
330
+ if (player.textTrack?.kind == TrackKind.EMPTY) {
331
+ player.textTrack = null
332
+ }
333
+ } else {
334
+ player.trackRestrictions = player.trackRestrictions.buildUpon {
335
+ enableCaptions = false
336
+ }
337
+ player.textTrack = player.textTracks.firstOrNull { it.kind == TrackKind.EMPTY }
338
+ ?: DISABLED_TEXT_TRACK
339
+ }
340
+ }
341
+
342
+ fun enableFullScreen() {
343
+ view?.goFullscreen()
344
+ }
345
+
346
+ fun disableFullScreen(width: Int, height: Int, left: Int, top: Int) {
347
+ view?.exitFullscreen(width, height, left, top)
348
+ }
349
+
350
+ override fun onEvent(event: Event) {
351
+ val player = this.player ?: return
352
+ when (event) {
353
+ is ErrorEvent -> {
354
+ Log.e("AndroidPlayer", "Player error", event.error)
355
+ jsBridge.bridgeAndroidToJavaScript_ErrorEvent(MediaEventType.ERROR, "${event.error.message} - ${event.error.nativeCode}")
356
+ stopPlayer()
357
+ }
358
+ is EndedEvent -> {
359
+ jsBridge.bridgeAndroidToJavaScript_MediaEvent(MediaEventType.ENDED)
360
+ }
361
+ is WaitingEvent -> {
362
+ jsBridge.bridgeAndroidToJavaScript_MediaEvent(MediaEventType.WAITING)
363
+ }
364
+ is PlayingEvent -> {
365
+ view?.setBlackCurtainVisibility(View.GONE)
366
+ jsBridge.bridgeAndroidToJavaScript_MediaEvent(MediaEventType.PLAYING)
367
+ }
368
+ is SeekedEvent -> {
369
+ jsBridge.bridgeAndroidToJavaScript_MediaEvent(MediaEventType.SEEKED)
370
+ }
371
+ is TimeUpdateEvent -> {
372
+ if (player.playing) {
373
+ jsBridge.bridgeAndroidToJavaScript_MediaEvent(MediaEventType.TIME_UPDATE)
374
+ }
375
+ }
376
+ is PauseEvent -> {
377
+ if (!player.playing) {
378
+ jsBridge.bridgeAndroidToJavaScript_MediaEvent(MediaEventType.PAUSE)
379
+ }
380
+ }
381
+ is LoadedMetadataEvent,
382
+ is AddAudioTrackEvent, is AddTextTrackEvent,
383
+ is RemoveAudioTrackEvent, is RemoveTextTrackEvent,
384
+ is ChangeAudioTrackEvent, is ChangeTextTrackEvent -> {
385
+ Log.d("WebViewModel", "onTracksChanged: audio=$audioTrack subtitle=$subtitleTrack")
386
+ jsBridge.bridgeAndroidToJavaScript_MediaTrackEvent(MediaEventType.TRACKS_CHANGED, player.audioTracks, player.textTracks)
387
+ }
388
+ }
389
+ }
390
+
391
+ fun setSubtitleTrack(track: TextTrack?) {
392
+ player?.textTrack = track
393
+ }
394
+
395
+ fun setAudioTrack(track: AudioTrack?) {
396
+ player?.audioTrack = track
397
+ }
398
+
399
+ fun saveToLocalStorage(key: String?, value: String?) {
400
+ localStorage.saveCookieJson(key, value)
401
+ }
402
+
403
+ fun getFromLocalStorage(key: String?): String? {
404
+ return localStorage.getFromLocalStorage(key)
405
+ }
406
+
407
+ fun removeFromLocalStorage(key: String?) {
408
+ localStorage.removeFromLocalStorage(key)
409
+ }
410
+
411
+ fun sendKeyCode(keyCode: Int, mediaEventType: String) {
412
+ jsBridge.bridgeAndroidToJavaScript_KeyEvent(keyCode, mediaEventType)
413
+ }
414
+
415
+ fun setPlaybackInfo(playbackInfo: AnalyticsPlaybackInfo) {
416
+ if ("JUMP" == playbackInfo.name) {
417
+ jumpAnalytics.setPlaybackInfo(playbackInfo)
418
+ }
419
+ }
420
+
421
+ fun showMessage(message: String?) {
422
+ view?.showMessage(message)
423
+ }
424
+
425
+ fun requestAudioFocus(): Int? {
426
+ return view?.requestAudioFocus()
427
+ }
428
+
429
+ fun abandonAudioFocus() {
430
+ view?.abandonAudioFocus()
431
+ }
432
+
433
+ fun onBackPressed() {
434
+ stopPlayer()
435
+ view?.onBackPressed()
436
+ loadUrl(appUrl)
437
+ }
438
+
439
+ fun signData(dataJson: String?): String? {
440
+ return if (dataJson == null || dataJson.isEmpty()) "" else Flavor().signData(context, dataJson)
441
+ }
442
+
443
+ fun launchIapPurchase(sku: String?) {
444
+ if (sku == null || sku.isEmpty()) return
445
+ view?.launchIapPurchase(sku)
446
+ }
447
+
448
+ fun checkNewPurchases() {
449
+ iapService.setCallback(iapCallback)
450
+ iapService.checkNewPurchases()
451
+ }
452
+
453
+ fun launchIapPurchase(activity: Activity, sku: String) {
454
+ iapService.setCallback(iapCallback)
455
+ iapService.purchase(activity, sku)
456
+ }
457
+
458
+ fun getIapProductDetails(skus: List<String>) {
459
+ iapService.setCallback(iapCallback)
460
+ iapService.getProducts(skus)
461
+ }
462
+
463
+ fun acknowledgeIapReceipt(receiptId: String) {
464
+ iapService.setCallback(iapCallback)
465
+ iapService.acknowledgeReceipt(receiptId)
466
+ }
467
+
468
+ private fun dispatchKeyEvent(event: KeyEvent): Boolean {
469
+ // pause and stop events should be caught only when we are in player
470
+ // -> because we want to let user have option to pause/stop some background playback
471
+ if (isInPlayer) {
472
+ // we have to catch all media events, otherwise they will be unhandled and reported in onUnhandledKeyEvent
473
+ // -> and later sent to broadcast receivers of other media apps, which can resume stream/audio from background app
474
+ when (event.keyCode) {
475
+ KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
476
+ KeyEvent.KEYCODE_MEDIA_STOP,
477
+ KeyEvent.KEYCODE_MEDIA_PAUSE -> {
478
+ if (event.action == KeyEvent.ACTION_DOWN) {
479
+ jsBridge.bridgeAndroidToJavaScript_KeyEvent(event.keyCode, MediaEventType.KEY_DOWN)
480
+ } else if (event.action == KeyEvent.ACTION_UP) {
481
+ jsBridge.bridgeAndroidToJavaScript_KeyEvent(event.keyCode, MediaEventType.KEY_UP)
482
+ }
483
+ return true
484
+ }
485
+ }
486
+ }
487
+ when (event.keyCode) {
488
+ KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK,
489
+ KeyEvent.KEYCODE_MEDIA_CLOSE,
490
+ KeyEvent.KEYCODE_MEDIA_EJECT,
491
+ KeyEvent.KEYCODE_MEDIA_FAST_FORWARD,
492
+ KeyEvent.KEYCODE_MEDIA_NEXT,
493
+ KeyEvent.KEYCODE_MEDIA_PREVIOUS,
494
+ KeyEvent.KEYCODE_MEDIA_RECORD,
495
+ KeyEvent.KEYCODE_MEDIA_REWIND,
496
+ KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD,
497
+ KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD,
498
+ KeyEvent.KEYCODE_MEDIA_STEP_BACKWARD,
499
+ KeyEvent.KEYCODE_MEDIA_STEP_FORWARD,
500
+ KeyEvent.KEYCODE_MEDIA_TOP_MENU,
501
+ KeyEvent.KEYCODE_MEDIA_PLAY -> {
502
+ if (event.action == KeyEvent.ACTION_DOWN) {
503
+ jsBridge.bridgeAndroidToJavaScript_KeyEvent(event.keyCode, MediaEventType.KEY_DOWN)
504
+ } else if (event.action == KeyEvent.ACTION_UP) {
505
+ jsBridge.bridgeAndroidToJavaScript_KeyEvent(event.keyCode, MediaEventType.KEY_UP)
506
+ }
507
+ return true
508
+ }
509
+ }
510
+ return false
511
+ }
512
+
513
+ fun detectDeviceInfo(): JSONObject {
514
+ val deviceInfo = JSONObject()
515
+ try {
516
+ deviceInfo.put("deviceBrand", Build.MANUFACTURER)
517
+ deviceInfo.put("deviceModel", Build.MODEL)
518
+ deviceInfo.put("osVersion", Build.VERSION.RELEASE)
519
+ deviceInfo.put("appFlavor", BuildConfig.FLAVOR)
520
+ deviceInfo.put("appVersion", BuildConfig.VERSION_NAME)
521
+
522
+ val drmKeySystems = mutableSetOf<String>()
523
+ playerFactory.getSupportedFormats(context).manifest.forEach { manifest ->
524
+ manifest.drm.forEach { drm ->
525
+ drmKeySystems.addAll(drm.keySystems)
526
+ }
527
+ }
528
+ deviceInfo.put("drmKeySystems", JSONArray(drmKeySystems))
529
+
530
+ val ipAddresses = mutableSetOf<String>()
531
+ for (networkInterface in NetworkInterface.getNetworkInterfaces()) {
532
+ for (inetAddress in networkInterface.inetAddresses) {
533
+ if (inetAddress.isLoopbackAddress) continue
534
+ ipAddresses.add(inetAddress.hostAddress)
535
+ }
536
+ }
537
+ deviceInfo.put("ipLocal", JSONArray(ipAddresses))
538
+
539
+ } catch (e: JSONException) {
540
+ deviceInfo.put("errorType", e.javaClass.simpleName)
541
+ deviceInfo.put("errorMessage", e.message)
542
+ }
543
+ return deviceInfo
544
+ }
545
+
546
+
547
+ private inner class InnerIapCallback : IapCallback {
548
+ override fun checkNewPurchasesFinished(result: List<IapPurchase>) {
549
+ // not actually used at the moment
550
+ }
551
+
552
+ override fun checkNewPurchasesFinished(exception: Exception) {
553
+ // not actually used at the moment
554
+ }
555
+
556
+ override fun purchaseFinished(result: IapPurchase) {
557
+ jsBridge.bridgeAndroidToJavaScript_IAPValidationSuccess(result.userId, result.receipt, result.sku)
558
+ }
559
+
560
+ override fun purchaseFinished(exception: Exception) {
561
+ if (exception is IapException) {
562
+ jsBridge.bridgeAndroidToJavaScript_IAPValidationError(exception.status)
563
+ } else {
564
+ jsBridge.bridgeAndroidToJavaScript_IAPValidationError(exception.message
565
+ ?: "Purchase failed")
566
+ }
567
+ }
568
+
569
+ override fun getProductsFinished(result: List<IapProduct>, missing: List<String>) {
570
+ val okSkus = JsonArray()
571
+ for (iapProduct in result) {
572
+ val jsonProduct = JsonObject()
573
+ jsonProduct.addProperty("title", iapProduct.title)
574
+ jsonProduct.addProperty("description", iapProduct.description)
575
+ jsonProduct.addProperty("price", iapProduct.price)
576
+ jsonProduct.addProperty("icon", iapProduct.icon)
577
+ jsonProduct.addProperty("type", iapProduct.type)
578
+ jsonProduct.addProperty("sku", iapProduct.sku)
579
+ okSkus.add(jsonProduct)
580
+ }
581
+ val badSkus = JsonArray()
582
+ for (sku in missing) {
583
+ badSkus.add(sku)
584
+ }
585
+ jsBridge.bridgeIAPGetProductDataSuccess(okSkus.toString(), badSkus.toString())
586
+ }
587
+
588
+ override fun getProductsFinished(exception: Exception) {
589
+ if (exception is IapException) {
590
+ jsBridge.bridgeIAPGetProductDataFail(exception.status)
591
+ } else {
592
+ jsBridge.bridgeIAPGetProductDataFail(exception.message ?: "Purchase failed")
593
+ }
594
+ }
595
+
596
+ override fun acknowledgeReceiptFinished(exception: Exception?) {
597
+ // JS does not care
598
+ }
599
+ }
600
+
601
+ companion object {
602
+ const val JAVA_SCRIPT_BRIDGE = "JavaScriptBridge"
603
+ const val LOW_END_DEVICE_MAX_BITRATE = 1500000
604
+ val DISABLED_TEXT_TRACK = TextTrack {
605
+ id = "text_off"
606
+ kind = TrackKind.EMPTY
607
+ }
608
+ }
609
+ }
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <shape xmlns:android="http://schemas.android.com/apk/res/android">
3
+ <solid
4
+ android:color="#E5F0FD"/>
5
+ <stroke
6
+ android:color="#33D7FF"
7
+ android:width="3dp"/>
8
+ <corners
9
+ android:radius="5dp" />
10
+ </shape>
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <shape xmlns:android="http://schemas.android.com/apk/res/android">
3
+ <solid
4
+ android:color="#E5F0FD"/>
5
+ <corners
6
+ android:radius="5dp" />
7
+ </shape>
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <shape xmlns:android="http://schemas.android.com/apk/res/android">
3
+ <solid
4
+ android:color="#1FD3FF"/>
5
+ <corners
6
+ android:radius="5dp" />
7
+ </shape>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <selector xmlns:android="http://schemas.android.com/apk/res/android">
3
+ <item android:drawable="@drawable/button_background_pressed"
4
+ android:state_pressed="true" />
5
+ <item android:drawable="@drawable/button_background_focus"
6
+ android:state_focused="true" />
7
+ <item android:drawable="@drawable/button_background_normal"/>
8
+ </selector>