@24i/smartapps-bigscreen-rnv-build-engine 5.0.0-alpha.25 → 5.0.0

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 (86) hide show
  1. package/build-freshness.js +158 -0
  2. package/dist/.build-freshness.json +1010 -0
  3. package/dist/templates/platforms/firetv/project/README.md +196 -0
  4. package/dist/templates/platforms/firetv/project/amazoniap/build.gradle +41 -0
  5. package/dist/templates/platforms/firetv/project/amazoniap/consumer-rules.pro +0 -0
  6. package/dist/templates/platforms/firetv/project/amazoniap/proguard-rules.pro +21 -0
  7. package/dist/templates/platforms/firetv/project/amazoniap/src/main/kotlin/com/twentyfouri/bridgetv/amazoniap/AmazonIapService.kt +91 -0
  8. package/dist/templates/platforms/firetv/project/amazoniap/src/main/kotlin/com/twentyfouri/bridgetv/amazoniap/AmazonIapServiceFactory.kt +13 -0
  9. package/dist/templates/platforms/firetv/project/app/build.gradle +138 -0
  10. package/dist/templates/platforms/firetv/project/app/proguard-rules.pro +47 -0
  11. package/dist/templates/platforms/firetv/project/app/src/bigscreenAppstage/kotlin/com/twentyfouri/tvbridge/Flavor.kt +9 -0
  12. package/dist/templates/platforms/firetv/project/app/src/bigscreenAppstage/res/raw/configuration.json +8 -0
  13. package/dist/templates/platforms/firetv/project/app/src/main/AndroidManifest.xml +52 -0
  14. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/FlavorBase.kt +9 -0
  15. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/bridge/BridgeFromJavaScript.kt +421 -0
  16. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/bridge/BridgeToJavaScript.kt +121 -0
  17. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/configuration/BridgeConfig.kt +19 -0
  18. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/configuration/ConfigCallback.kt +6 -0
  19. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/configuration/ConfigProvider.kt +27 -0
  20. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/configuration/Drm.kt +10 -0
  21. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/configuration/UserAgent.kt +6 -0
  22. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/global/BaseApplication.kt +73 -0
  23. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/global/di/ActivityComponent.kt +13 -0
  24. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/global/di/ActivityModule.kt +14 -0
  25. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/global/di/ApplicationComponent.kt +23 -0
  26. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/global/di/ApplicationModule.kt +51 -0
  27. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/global/di/PerActivity.kt +26 -0
  28. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/global/utils/LocalStorage.kt +30 -0
  29. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/global/utils/LoggingDrmCallback.kt +38 -0
  30. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/global/utils/OverriddenHttpMediaDrmCallback.kt +134 -0
  31. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/global/utils/PlayerFactoryBitmovinCustomized.kt +19 -0
  32. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/global/utils/PlayerFactoryConfigured.kt +37 -0
  33. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/global/utils/PlayerFactoryExoPlayerCustomized.kt +106 -0
  34. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/global/utils/PlayerFactoryExoPlayerNpaw.kt +581 -0
  35. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/global/utils/PlayerFactoryVersion.kt +27 -0
  36. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/global/utils/SimpleActivityLifecycleCallback.kt +32 -0
  37. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/global/utils/SourceDrmOverridden.kt +91 -0
  38. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/sound/TouchPlayer.kt +86 -0
  39. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/webview/client/BridgeWebView.kt +82 -0
  40. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/webview/client/BridgeWebViewClient.kt +114 -0
  41. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/webview/model/AnalyticsPlaybackInfo.kt +10 -0
  42. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/webview/model/ContentModel.kt +14 -0
  43. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/webview/model/DrmModel.kt +20 -0
  44. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/webview/model/MediaEventType.kt +31 -0
  45. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/webview/parser/PlaybackInfoParser.kt +10 -0
  46. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/webview/parser/SourceParser.kt +89 -0
  47. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/webview/parser/TrackJsonModelParser.kt +58 -0
  48. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/webview/view/Mainview.kt +18 -0
  49. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/webview/view/PlayerCache.kt +15 -0
  50. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/webview/view/WebViewActivity.kt +252 -0
  51. package/dist/templates/platforms/firetv/project/app/src/main/kotlin/com/twentyfouri/tvbridge/webview/viewmodel/WebViewModel.kt +748 -0
  52. package/dist/templates/platforms/firetv/project/app/src/main/res/drawable/button_background_focus.xml +10 -0
  53. package/dist/templates/platforms/firetv/project/app/src/main/res/drawable/button_background_normal.xml +7 -0
  54. package/dist/templates/platforms/firetv/project/app/src/main/res/drawable/button_background_pressed.xml +7 -0
  55. package/dist/templates/platforms/firetv/project/app/src/main/res/drawable/button_focus.xml +8 -0
  56. package/dist/templates/platforms/firetv/project/app/src/main/res/layout/activity_web_view.xml +28 -0
  57. package/dist/templates/platforms/firetv/project/app/src/main/res/raw/click.ogg +0 -0
  58. package/dist/templates/platforms/firetv/project/app/src/main/res/values/styles.xml +12 -0
  59. package/dist/templates/platforms/firetv/project/app/src/main/res/xml/network_security_config.xml +4 -0
  60. package/dist/templates/platforms/firetv/project/build.gradle +45 -0
  61. package/dist/templates/platforms/firetv/project/common/build.gradle +41 -0
  62. package/dist/templates/platforms/firetv/project/common/consumer-rules.pro +0 -0
  63. package/dist/templates/platforms/firetv/project/common/proguard-rules.pro +21 -0
  64. package/dist/templates/platforms/firetv/project/common/src/main/kotlin/com/twentyfouri/tvbridge/common/Util.kt +11 -0
  65. package/dist/templates/platforms/firetv/project/common/src/main/kotlin/com/twentyfouri/tvbridge/common/iap/IapCallback.kt +11 -0
  66. package/dist/templates/platforms/firetv/project/common/src/main/kotlin/com/twentyfouri/tvbridge/common/iap/IapException.kt +14 -0
  67. package/dist/templates/platforms/firetv/project/common/src/main/kotlin/com/twentyfouri/tvbridge/common/iap/IapProduct.kt +16 -0
  68. package/dist/templates/platforms/firetv/project/common/src/main/kotlin/com/twentyfouri/tvbridge/common/iap/IapPurchase.kt +9 -0
  69. package/dist/templates/platforms/firetv/project/common/src/main/kotlin/com/twentyfouri/tvbridge/common/iap/IapService.kt +14 -0
  70. package/dist/templates/platforms/firetv/project/common/src/main/kotlin/com/twentyfouri/tvbridge/common/iap/IapServiceFactory.kt +8 -0
  71. package/dist/templates/platforms/firetv/project/common/src/main/kotlin/com/twentyfouri/tvbridge/common/iap/NullIapService.kt +32 -0
  72. package/dist/templates/platforms/firetv/project/googleiap/build.gradle +41 -0
  73. package/dist/templates/platforms/firetv/project/googleiap/consumer-rules.pro +0 -0
  74. package/dist/templates/platforms/firetv/project/googleiap/proguard-rules.pro +21 -0
  75. package/dist/templates/platforms/firetv/project/googleiap/src/main/kotlin/com/twentyfouri/tvbridge/googleiap/GoogleIapService.kt +6 -0
  76. package/dist/templates/platforms/firetv/project/googleiap/src/main/kotlin/com/twentyfouri/tvbridge/googleiap/GoogleIapServiceFactory.kt +13 -0
  77. package/dist/templates/platforms/firetv/project/gradle/wrapper/gradle-wrapper.jar +0 -0
  78. package/dist/templates/platforms/firetv/project/gradle/wrapper/gradle-wrapper.properties +7 -0
  79. package/dist/templates/platforms/firetv/project/gradle.properties +4 -0
  80. package/dist/templates/platforms/firetv/project/gradlew +251 -0
  81. package/dist/templates/platforms/firetv/project/gradlew.bat +94 -0
  82. package/dist/templates/platforms/firetv/project/proguard-rules.pro +42 -0
  83. package/dist/templates/platforms/firetv/project/settings.gradle +4 -0
  84. package/index.js +2 -31
  85. package/package.json +4 -3
  86. package/project.json +57 -11
@@ -0,0 +1,748 @@
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.os.Build
9
+ import android.provider.Settings
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 com.google.android.gms.ads.identifier.AdvertisingIdClient
17
+ import com.google.gson.JsonArray
18
+ import com.google.gson.JsonObject
19
+ import com.twentyfouri.player.base.AbrStrategy
20
+ import com.twentyfouri.player.base.AdBreakEndedEvent
21
+ import com.twentyfouri.player.base.AdBreakStartedEvent
22
+ import com.twentyfouri.player.base.AdBreaksRescheduledEvent
23
+ import com.twentyfouri.player.base.AdErrorEvent
24
+ import com.twentyfouri.player.base.AdSkippedEvent
25
+ import com.twentyfouri.player.base.AdStartedEvent
26
+ import com.twentyfouri.player.base.AddAudioTrackEvent
27
+ import com.twentyfouri.player.base.AddTextTrackEvent
28
+ import com.twentyfouri.player.base.AudioTrack
29
+ import com.twentyfouri.player.base.CanPlayEvent
30
+ import com.twentyfouri.player.base.ChangeAudioTrackEvent
31
+ import com.twentyfouri.player.base.ChangeTextTrackEvent
32
+ import com.twentyfouri.player.base.DurationChangeEvent
33
+ import com.twentyfouri.player.base.EndedEvent
34
+ import com.twentyfouri.player.base.ErrorEvent
35
+ import com.twentyfouri.player.base.Event
36
+ import com.twentyfouri.player.base.EventListener
37
+ import com.twentyfouri.player.base.LoadedDataEvent
38
+ import com.twentyfouri.player.base.LoadedMetadataEvent
39
+ import com.twentyfouri.player.base.PauseEvent
40
+ import com.twentyfouri.player.base.PlayerBase
41
+ import com.twentyfouri.player.base.PlayerFactoryBase
42
+ import com.twentyfouri.player.base.PlayingEvent
43
+ import com.twentyfouri.player.base.RemoveAudioTrackEvent
44
+ import com.twentyfouri.player.base.RemoveTextTrackEvent
45
+ import com.twentyfouri.player.base.SeekedEvent
46
+ import com.twentyfouri.player.base.SeekingEvent
47
+ import com.twentyfouri.player.base.Source
48
+ import com.twentyfouri.player.base.SourceDrmPlayready
49
+ import com.twentyfouri.player.base.SourceDrmWidevine
50
+ import com.twentyfouri.player.base.SourceImaAds
51
+ import com.twentyfouri.player.base.TextTrack
52
+ import com.twentyfouri.player.base.TimeUpdateEvent
53
+ import com.twentyfouri.player.base.TrackKind
54
+ import com.twentyfouri.player.base.WaitingEvent
55
+ import com.twentyfouri.player.base.buildUpon
56
+ import com.twentyfouri.tvbridge.BuildConfig
57
+ import com.twentyfouri.tvbridge.Flavor
58
+ import com.twentyfouri.tvbridge.bridge.BridgeFromJavaScript
59
+ import com.twentyfouri.tvbridge.bridge.BridgeToJavaScript
60
+ import com.twentyfouri.tvbridge.common.iap.IapCallback
61
+ import com.twentyfouri.tvbridge.common.iap.IapException
62
+ import com.twentyfouri.tvbridge.common.iap.IapProduct
63
+ import com.twentyfouri.tvbridge.common.iap.IapPurchase
64
+ import com.twentyfouri.tvbridge.common.iap.IapService
65
+ import com.twentyfouri.tvbridge.configuration.BridgeConfig
66
+ import com.twentyfouri.tvbridge.global.utils.LocalStorage
67
+ import com.twentyfouri.tvbridge.global.utils.PlayerFactoryVersion
68
+ import com.twentyfouri.tvbridge.global.utils.SourceDrmOverridden
69
+ import com.twentyfouri.tvbridge.webview.client.BridgeWebView
70
+ import com.twentyfouri.tvbridge.webview.client.BridgeWebViewClient
71
+ import com.twentyfouri.tvbridge.webview.client.BridgeWebViewClient.BridgeWebViewClientCallback
72
+ import com.twentyfouri.tvbridge.webview.model.AnalyticsPlaybackInfo
73
+ import com.twentyfouri.tvbridge.webview.model.MediaEventType
74
+ import com.twentyfouri.tvbridge.webview.view.Mainview
75
+ import com.twentyfouri.tvbridge.webview.view.PlayerCache
76
+ import org.json.JSONArray
77
+ import org.json.JSONException
78
+ import org.json.JSONObject
79
+ import java.io.IOException
80
+ import java.net.NetworkInterface
81
+ import java.util.Locale
82
+ import javax.inject.Inject
83
+
84
+ /**
85
+ * Created by destivar on 15/02/16.
86
+ */
87
+ class WebViewModel @Inject constructor(
88
+ private val context: Context,
89
+ private val localStorage: LocalStorage,
90
+ private val config: BridgeConfig,
91
+ private val iapService: IapService,
92
+ val playerFactory: PlayerFactoryBase
93
+ ) : EventListener {
94
+ var player: PlayerBase? = null
95
+ private var view: Mainview? = null
96
+ private val jsBridge = BridgeToJavaScript(config) { s: String? ->
97
+ view?.loadUrl(s)
98
+ }
99
+ private val iapCallback = InnerIapCallback()
100
+ private val playerCache = PlayerCache()
101
+
102
+ private var source: Source? = null
103
+
104
+ private var adStarted: Boolean = false
105
+
106
+ val audioTrack: AudioTrack?
107
+ get() = player?.selectedAudioTrack
108
+ val subtitleTrack: TextTrack?
109
+ get() = player?.selectedTextTrack
110
+
111
+ var appUrl: String? = null
112
+
113
+ fun attachView(view: Mainview?) {
114
+ this.view = view
115
+ if (view != null) {
116
+ iapService.initialize()
117
+ checkNewPurchases()
118
+ }
119
+ }
120
+
121
+ fun configureWebView(webView: BridgeWebView) {
122
+
123
+ // Enable remote debugging on devices at API level 19 (KitKat) or higher.
124
+ if (BuildConfig.DEBUG) {
125
+ WebView.setWebContentsDebuggingEnabled(true)
126
+ }
127
+
128
+ // Add the JavaScript bridge.
129
+ webView.addJavascriptInterface(BridgeFromJavaScript(this), JAVA_SCRIPT_BRIDGE)
130
+
131
+ // WebView callback methods.
132
+ webView.webViewClient = BridgeWebViewClient(object : BridgeWebViewClientCallback {
133
+ override fun onUnhandledKeyEvent(view: WebView, event: KeyEvent) {
134
+ if (event.action == KeyEvent.ACTION_DOWN) {
135
+ sendKeyCode(event.keyCode, MediaEventType.KEY_DOWN)
136
+ } else if (event.action == KeyEvent.ACTION_UP) {
137
+ sendKeyCode(event.keyCode, MediaEventType.KEY_UP)
138
+ }
139
+ }
140
+ })
141
+
142
+ webView.webChromeClient = object : WebChromeClient() {
143
+ override fun getDefaultVideoPoster(): Bitmap? {
144
+ val bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565)
145
+ val canvas = Canvas(bitmap)
146
+ canvas.drawARGB(255, 0, 0, 0)
147
+ return bitmap
148
+ }
149
+
150
+ override fun onShowCustomView(view: View, callback: CustomViewCallback) {
151
+ val intent = Intent(Intent.ACTION_SEND)
152
+ intent.type = "video/*"
153
+ context.startActivity(intent)
154
+ }
155
+
156
+ override fun onHideCustomView() {}
157
+ }
158
+
159
+ webView.previewKeyEvent = this::dispatchKeyEvent
160
+
161
+ CookieManager.getInstance().setAcceptCookie(true)
162
+ CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true)
163
+ }
164
+
165
+ fun onClickGo() {
166
+ view?.loadUrl(appUrl)
167
+ if (BuildConfig.DEBUG) {
168
+ view?.hideKeyboard()
169
+ }
170
+ }
171
+
172
+ fun setVideo(source: Source) {
173
+ var adaptedSource = source
174
+ if (config.drm.shouldOverrideDrmCallback()) {
175
+ adaptedSource = SourceDrmOverridden.replaceSource(adaptedSource)
176
+ }
177
+ logSource(adaptedSource)
178
+ playDirectly(adaptedSource)
179
+ }
180
+
181
+ private fun logSource(source: Source) {
182
+ Log.i("BridgeVideo", "Url: ${source.url}")
183
+ when (val ads = source.ads) {
184
+ is SourceImaAds -> {
185
+ Log.i("BridgeVideo", "Ads: ${ads.url}")
186
+ }
187
+ }
188
+ when (val drmModel = source.drm.firstOrNull()) {
189
+ is SourceDrmWidevine -> {
190
+ Log.i("BridgeVideo", "DRM: " + drmModel.packageName)
191
+ Log.i("BridgeVideo", "LicenseUrl: " + drmModel.licenseServer)
192
+ }
193
+ is SourceDrmPlayready -> {
194
+ Log.i("BridgeVideo", "DRM: " + drmModel.packageName)
195
+ Log.i("BridgeVideo", "LicenseUrl: " + drmModel.licenseServer)
196
+ }
197
+ is SourceDrmOverridden -> {
198
+ Log.i("BridgeVideo", "DRM: " + drmModel.packageName)
199
+ Log.i("BridgeVideo", "LicenseUrl: " + drmModel.licenseServer)
200
+ }
201
+ }
202
+ }
203
+
204
+ private fun playDirectly(source: Source) {
205
+ this.source = source
206
+ player?.also { player ->
207
+ player.source = source
208
+
209
+ if (isLowEndDevice) {
210
+ player.trackRestrictions = player.trackRestrictions.buildUpon {
211
+ maxBitrate = LOW_END_DEVICE_MAX_BITRATE
212
+ }
213
+ } else if (config.startWithHighVideoQuality) {
214
+ player.trackRestrictions = player.trackRestrictions.buildUpon {
215
+ abrStrategy = AbrStrategy.QUALITY
216
+ }
217
+ }
218
+
219
+ player.addEventListener(Event.ALL, this)
220
+
221
+ player.autoPlay = true
222
+ player.load()
223
+ player.play()
224
+ }
225
+ }
226
+
227
+ private val isLowEndDevice: Boolean
228
+ get() = when (Build.MODEL) {
229
+ "AFTB" -> true
230
+ "AFTM" -> true
231
+ else -> false
232
+ }
233
+
234
+ fun startPlayer() {
235
+ player?.play()
236
+ }
237
+
238
+ fun resumePlayer() {
239
+ val savedSource = playerCache.savedSource
240
+ if (playerCache.loadFromCache && savedSource != null && playerCache.savedPlayerState !== PlayerCache.PlayerState.PAUSED) {
241
+ setVideo(savedSource)
242
+ var position = playerCache.savedPlayerPosition
243
+ val player = this.player ?: return
244
+ if (player.live) {
245
+ position = 0.0
246
+ }
247
+ player.seek(position)
248
+ view?.requestAudioFocus()
249
+ if (playerCache.savedPlayerState === PlayerCache.PlayerState.PLAYING) {
250
+ player.play()
251
+ }
252
+ } else {
253
+ if (player?.live == true) {
254
+ player?.seek(0.0)
255
+ }
256
+ player?.play()
257
+ }
258
+ }
259
+
260
+ fun pausePlayer() {
261
+ player?.pause()
262
+ }
263
+
264
+ fun stopPlayer() {
265
+ view?.setBlackCurtainVisibility(View.VISIBLE)
266
+ source = null
267
+
268
+ player?.also { player ->
269
+ player.removeEventListener(Event.ALL, this)
270
+ player.unload()
271
+ }
272
+ }
273
+
274
+ fun releasePlayer() {
275
+ val player = this.player ?: return
276
+ if (source != null) {
277
+ playerCache.loadFromCache = true
278
+ playerCache.savedSource = source
279
+ playerCache.savedPlayerDuration = player.duration
280
+ playerCache.savedPlayerPosition = player.currentTime
281
+ playerCache.savedPlayerState = if (player.playing) PlayerCache.PlayerState.PLAYING else PlayerCache.PlayerState.PAUSED
282
+ }
283
+ player.unload()
284
+ }
285
+
286
+ fun reloadPlayer() {
287
+ view?.reloadPlayer()
288
+ }
289
+
290
+ val currentPosition: Double
291
+ get() = player?.currentTime ?: playerCache.savedPlayerPosition
292
+ val duration: Double
293
+ get() = player?.duration ?: playerCache.savedPlayerDuration
294
+
295
+ fun seekToPosition(position: Double) {
296
+ player?.seek(position)
297
+ }
298
+
299
+ val isPlayerPlaying: Boolean
300
+ get() = player?.playing == true
301
+ val isInPlayer: Boolean
302
+ get() = player != null && source != null
303
+
304
+ fun loadUrl(url: String?) {
305
+ view?.loadUrl(url)
306
+ }
307
+
308
+ fun setSubtitlesVisibility(visibility: Boolean) {
309
+ val player = this.player ?: return
310
+ if (visibility) {
311
+ player.trackRestrictions = player.trackRestrictions.buildUpon {
312
+ enableCaptions = true
313
+ }
314
+ if (player.textTrack?.kind == TrackKind.EMPTY) {
315
+ player.textTrack = null
316
+ }
317
+ } else {
318
+ player.trackRestrictions = player.trackRestrictions.buildUpon {
319
+ enableCaptions = false
320
+ }
321
+ player.textTrack = player.textTracks.firstOrNull { it.kind == TrackKind.EMPTY }
322
+ ?: DISABLED_TEXT_TRACK
323
+ }
324
+ }
325
+
326
+ fun enableFullScreen() {
327
+ view?.goFullscreen()
328
+ }
329
+
330
+ fun disableFullScreen(width: Int, height: Int, left: Int, top: Int) {
331
+ view?.exitFullscreen(width, height, left, top)
332
+ }
333
+
334
+ override fun onEvent(event: Event) {
335
+ val player = this.player ?: return
336
+ when (event) {
337
+ is ErrorEvent -> {
338
+ Log.e("AndroidPlayer", "Player error", event.error)
339
+ jsBridge.bridgeAndroidToJavaScript_ErrorEvent(
340
+ MediaEventType.ERROR,
341
+ "${event.error.message} - ${event.error.nativeCode}"
342
+ )
343
+ stopPlayer()
344
+ }
345
+
346
+ is LoadedMetadataEvent -> {
347
+ jsBridge.bridgeAndroidToJavaScript_MediaEvent(MediaEventType.LOADEDMETADATA)
348
+ jsBridge.bridgeAndroidToJavaScript_MediaTrackEvent(
349
+ MediaEventType.TRACKS_CHANGED,
350
+ player.audioTracks,
351
+ player.textTracks
352
+ )
353
+ }
354
+
355
+ is LoadedDataEvent -> {
356
+ jsBridge.bridgeAndroidToJavaScript_MediaEvent(MediaEventType.LOADEDDATA)
357
+ }
358
+
359
+ is CanPlayEvent -> {
360
+ jsBridge.bridgeAndroidToJavaScript_MediaEvent(MediaEventType.CANPLAY)
361
+ }
362
+
363
+ is EndedEvent -> {
364
+ jsBridge.bridgeAndroidToJavaScript_MediaEvent(MediaEventType.ENDED)
365
+ }
366
+
367
+ is WaitingEvent -> {
368
+ jsBridge.bridgeAndroidToJavaScript_MediaEvent(MediaEventType.WAITING)
369
+ }
370
+
371
+ is PlayingEvent -> {
372
+ view?.setBlackCurtainVisibility(View.GONE)
373
+ jsBridge.bridgeAndroidToJavaScript_MediaEvent(MediaEventType.PLAYING)
374
+ }
375
+
376
+ is DurationChangeEvent -> {
377
+ jsBridge.bridgeAndroidToJavaScript_MediaEvent(MediaEventType.DURATION_CHANGE)
378
+ }
379
+
380
+ is SeekingEvent -> {
381
+ jsBridge.bridgeAndroidToJavaScript_MediaEvent(MediaEventType.SEEKING)
382
+ }
383
+
384
+ is SeekedEvent -> {
385
+ jsBridge.bridgeAndroidToJavaScript_MediaEvent(MediaEventType.SEEKED)
386
+ }
387
+
388
+ is TimeUpdateEvent -> {
389
+ if (player.playing) {
390
+ jsBridge.bridgeAndroidToJavaScript_MediaEvent(MediaEventType.TIME_UPDATE)
391
+ }
392
+ }
393
+
394
+ is PauseEvent -> {
395
+ if (!player.playing) {
396
+ jsBridge.bridgeAndroidToJavaScript_MediaEvent(MediaEventType.PAUSE)
397
+ }
398
+ }
399
+
400
+ is AddAudioTrackEvent, is AddTextTrackEvent,
401
+ is RemoveAudioTrackEvent, is RemoveTextTrackEvent,
402
+ is ChangeAudioTrackEvent, is ChangeTextTrackEvent -> {
403
+ Log.d("WebViewModel", "onTrackschanged: audio=$audioTrack subtitle=$subtitleTrack")
404
+ jsBridge.bridgeAndroidToJavaScript_MediaTrackEvent(
405
+ MediaEventType.TRACKS_CHANGED,
406
+ player.audioTracks,
407
+ player.textTracks
408
+ )
409
+ }
410
+
411
+ is AdBreaksRescheduledEvent -> {
412
+ jsBridge.bridgeAndroidToJavaScript_MediaAdBreaksEvent(
413
+ MediaEventType.AD_BREAKS_RESCHEDULED,
414
+ player.timeline
415
+ )
416
+ }
417
+
418
+ is AdBreakStartedEvent -> {
419
+ jsBridge.bridgeAndroidToJavaScript_MediaAdEvent(
420
+ MediaEventType.AD_BREAK_START,
421
+ player.currentAdBreak,
422
+ player.currentAd
423
+ )
424
+ }
425
+
426
+ is AdBreakEndedEvent -> {
427
+ if (adStarted) {
428
+ jsBridge.bridgeAndroidToJavaScript_MediaAdEvent(
429
+ MediaEventType.AD_ENDED,
430
+ player.currentAdBreak,
431
+ null
432
+ )
433
+ }
434
+ adStarted = false
435
+ jsBridge.bridgeAndroidToJavaScript_MediaAdEvent(
436
+ MediaEventType.AD_BREAK_END,
437
+ player.currentAdBreak,
438
+ player.currentAd
439
+ )
440
+ }
441
+
442
+ is AdStartedEvent -> {
443
+ if (adStarted) {
444
+ jsBridge.bridgeAndroidToJavaScript_MediaAdEvent(
445
+ MediaEventType.AD_ENDED,
446
+ player.currentAdBreak,
447
+ null
448
+ )
449
+ } else {
450
+ adStarted = true
451
+ }
452
+ jsBridge.bridgeAndroidToJavaScript_MediaAdEvent(
453
+ MediaEventType.AD_STARTED,
454
+ player.currentAdBreak,
455
+ player.currentAd
456
+ )
457
+ }
458
+
459
+ is AdErrorEvent -> {
460
+ jsBridge.bridgeAndroidToJavaScript_MediaAdEvent(
461
+ MediaEventType.AD_ERROR,
462
+ player.currentAdBreak,
463
+ player.currentAd
464
+ )
465
+ }
466
+
467
+ is AdSkippedEvent -> {
468
+ jsBridge.bridgeAndroidToJavaScript_MediaAdEvent(
469
+ MediaEventType.AD_SKIPPED,
470
+ player.currentAdBreak,
471
+ player.currentAd
472
+ )
473
+ }
474
+ }
475
+ }
476
+
477
+ fun setSubtitleTrack(track: TextTrack?) {
478
+ player?.textTrack = track
479
+ }
480
+
481
+ fun setAudioTrack(track: AudioTrack?) {
482
+ player?.audioTrack = track
483
+ }
484
+
485
+ fun saveToLocalStorage(key: String?, value: String?) {
486
+ localStorage.saveCookieJson(key, value)
487
+ }
488
+
489
+ fun getFromLocalStorage(key: String?): String? {
490
+ return localStorage.getFromLocalStorage(key)
491
+ }
492
+
493
+ fun removeFromLocalStorage(key: String?) {
494
+ localStorage.removeFromLocalStorage(key)
495
+ }
496
+
497
+ fun sendKeyCode(keyCode: Int, mediaEventType: String) {
498
+ jsBridge.bridgeAndroidToJavaScript_KeyEvent(keyCode, mediaEventType)
499
+ }
500
+
501
+ fun setPlaybackInfo(playbackInfo: AnalyticsPlaybackInfo) {
502
+ // previously used for Jump analytics, not supported with THEO
503
+ }
504
+
505
+ fun showMessage(message: String?) {
506
+ view?.showMessage(message)
507
+ }
508
+
509
+ fun requestAudioFocus(): Int? {
510
+ return view?.requestAudioFocus()
511
+ }
512
+
513
+ fun abandonAudioFocus() {
514
+ view?.abandonAudioFocus()
515
+ }
516
+
517
+ fun onBackPressed() {
518
+ stopPlayer()
519
+ view?.onBackPressed()
520
+ loadUrl(appUrl)
521
+ }
522
+
523
+ fun signData(dataJson: String?): String? {
524
+ return if (dataJson == null || dataJson.isEmpty()) "" else Flavor.signData(context, dataJson)
525
+ }
526
+
527
+ fun launchIapPurchase(sku: String?) {
528
+ if (sku == null || sku.isEmpty()) return
529
+ view?.launchIapPurchase(sku)
530
+ }
531
+
532
+ fun checkNewPurchases() {
533
+ iapService.setCallback(iapCallback)
534
+ iapService.checkNewPurchases()
535
+ }
536
+
537
+ fun launchIapPurchase(activity: Activity, sku: String) {
538
+ iapService.setCallback(iapCallback)
539
+ iapService.purchase(activity, sku)
540
+ }
541
+
542
+ fun getIapProductDetails(skus: List<String>) {
543
+ iapService.setCallback(iapCallback)
544
+ iapService.getProducts(skus)
545
+ }
546
+
547
+ fun acknowledgeIapReceipt(receipt: JSONObject) {
548
+ iapService.setCallback(iapCallback)
549
+ iapService.acknowledgeReceipt(receipt)
550
+ }
551
+
552
+ private fun dispatchKeyEvent(event: KeyEvent): Boolean {
553
+ // pause and stop events should be caught only when we are in player
554
+ // -> because we want to let user have option to pause/stop some background playback
555
+ if (isInPlayer) {
556
+ // we have to catch all media events, otherwise they will be unhandled and reported in onUnhandledKeyEvent
557
+ // -> and later sent to broadcast receivers of other media apps, which can resume stream/audio from background app
558
+ when (event.keyCode) {
559
+ KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
560
+ KeyEvent.KEYCODE_MEDIA_STOP,
561
+ KeyEvent.KEYCODE_MEDIA_PAUSE -> {
562
+ if (event.action == KeyEvent.ACTION_DOWN) {
563
+ jsBridge.bridgeAndroidToJavaScript_KeyEvent(event.keyCode, MediaEventType.KEY_DOWN)
564
+ } else if (event.action == KeyEvent.ACTION_UP) {
565
+ jsBridge.bridgeAndroidToJavaScript_KeyEvent(event.keyCode, MediaEventType.KEY_UP)
566
+ }
567
+ return true
568
+ }
569
+ }
570
+ }
571
+ when (event.keyCode) {
572
+ KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK,
573
+ KeyEvent.KEYCODE_MEDIA_CLOSE,
574
+ KeyEvent.KEYCODE_MEDIA_EJECT,
575
+ KeyEvent.KEYCODE_MEDIA_FAST_FORWARD,
576
+ KeyEvent.KEYCODE_MEDIA_NEXT,
577
+ KeyEvent.KEYCODE_MEDIA_PREVIOUS,
578
+ KeyEvent.KEYCODE_MEDIA_RECORD,
579
+ KeyEvent.KEYCODE_MEDIA_REWIND,
580
+ KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD,
581
+ KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD,
582
+ KeyEvent.KEYCODE_MEDIA_STEP_BACKWARD,
583
+ KeyEvent.KEYCODE_MEDIA_STEP_FORWARD,
584
+ KeyEvent.KEYCODE_MEDIA_TOP_MENU,
585
+ KeyEvent.KEYCODE_MEDIA_PLAY -> {
586
+ if (event.action == KeyEvent.ACTION_DOWN) {
587
+ jsBridge.bridgeAndroidToJavaScript_KeyEvent(event.keyCode, MediaEventType.KEY_DOWN)
588
+ } else if (event.action == KeyEvent.ACTION_UP) {
589
+ jsBridge.bridgeAndroidToJavaScript_KeyEvent(event.keyCode, MediaEventType.KEY_UP)
590
+ }
591
+ return true
592
+ }
593
+ }
594
+ return false
595
+ }
596
+
597
+ fun detectDeviceInfo(): JSONObject {
598
+ val deviceInfo = JSONObject()
599
+ try {
600
+ val engineVersion = PlayerFactoryVersion.from(playerFactory)
601
+ deviceInfo.put("deviceBrand", Build.MANUFACTURER)
602
+ deviceInfo.put("deviceModel", Build.MODEL)
603
+ deviceInfo.put("osVersion", Build.VERSION.RELEASE)
604
+ deviceInfo.put("appFlavor", BuildConfig.FLAVOR)
605
+ deviceInfo.put("appVersion", BuildConfig.VERSION_NAME)
606
+ deviceInfo.put("engineName", engineVersion.engineName)
607
+ deviceInfo.put("engineVersion", engineVersion.engineVersion)
608
+
609
+ val drmKeySystems = mutableSetOf<String>()
610
+ playerFactory.getSupportedFormats(context).manifest.forEach { manifest ->
611
+ manifest.drm.forEach { drm ->
612
+ drmKeySystems.addAll(drm.keySystems)
613
+ }
614
+ }
615
+ deviceInfo.put("drmKeySystems", JSONArray(drmKeySystems))
616
+
617
+ val ipAddresses = mutableSetOf<String>()
618
+ for (networkInterface in NetworkInterface.getNetworkInterfaces()) {
619
+ for (inetAddress in networkInterface.inetAddresses) {
620
+ if (inetAddress.isLoopbackAddress) continue
621
+ ipAddresses.add(inetAddress.hostAddress)
622
+ }
623
+ }
624
+ deviceInfo.put("ipLocal", JSONArray(ipAddresses))
625
+
626
+ } catch (e: JSONException) {
627
+ deviceInfo.put("errorType", e.javaClass.simpleName)
628
+ deviceInfo.put("errorMessage", e.message)
629
+ }
630
+ return deviceInfo
631
+ }
632
+
633
+ fun detectMediaInfo(): JSONObject? {
634
+ val player = this.player ?: return null
635
+ val mediaInfo = JSONObject()
636
+ try {
637
+ mediaInfo.put("resolution", "${player.videoWidth}x${player.videoHeight}")
638
+ player.selectedVideoTrack?.also { track ->
639
+ mediaInfo.put("videoCodec", track.codec)
640
+ mediaInfo.put("bitrate", track.bitrate)
641
+ mediaInfo.put("framerate", track.frameRate)
642
+ }
643
+ player.selectedAudioTrack?.also { track ->
644
+ mediaInfo.put("audioCodec", track.codec)
645
+ }
646
+ player.metrics.also { metrics ->
647
+ mediaInfo.put("estimatedBandwidth", metrics.bandwidthEstimate)
648
+ mediaInfo.put("totalDataLoaded", metrics.totalDownloadedBytes)
649
+ if (player.live) {
650
+ mediaInfo.put("liveLatency", metrics.liveLatency)
651
+ }
652
+ }
653
+ } catch (e: JSONException) {
654
+ mediaInfo.put("errorType", e.javaClass.simpleName)
655
+ mediaInfo.put("errorMessage", e.message)
656
+ }
657
+ return mediaInfo
658
+ }
659
+
660
+ val adId get() = try {
661
+ // Let's try Google first
662
+ AdvertisingIdClient.getAdvertisingIdInfo(context).id
663
+ } catch (e: IOException) {
664
+ // Probably Amazon
665
+ try {
666
+ Settings.Secure.getString(context.contentResolver, "advertising_id")
667
+ } catch (snfe: Settings.SettingNotFoundException) {
668
+ null
669
+ }
670
+ }
671
+
672
+ val limitAdTracking get() = try {
673
+ // Let's try Google first
674
+ AdvertisingIdClient.getAdvertisingIdInfo(context).isLimitAdTrackingEnabled
675
+ } catch (e: IOException) {
676
+ // Probably Amazon
677
+ try {
678
+ Settings.Secure.getInt(context.contentResolver, "limit_ad_tracking") != 0
679
+ } catch (snfe: Settings.SettingNotFoundException) {
680
+ true
681
+ }
682
+ }
683
+
684
+ val language get() = Locale.getDefault().language
685
+
686
+ private inner class InnerIapCallback : IapCallback {
687
+ override fun checkNewPurchasesFinished(result: List<IapPurchase>) {
688
+ // not actually used at the moment
689
+ }
690
+
691
+ override fun checkNewPurchasesFinished(exception: Exception) {
692
+ // not actually used at the moment
693
+ }
694
+
695
+ override fun purchaseFinished(result: IapPurchase) {
696
+ jsBridge.bridgeAndroidToJavaScript_IAPValidationSuccess(result.userId, result.receipt, result.sku)
697
+ }
698
+
699
+ override fun purchaseFinished(exception: Exception) {
700
+ if (exception is IapException) {
701
+ jsBridge.bridgeAndroidToJavaScript_IAPValidationError(exception.status)
702
+ } else {
703
+ jsBridge.bridgeAndroidToJavaScript_IAPValidationError(exception.message
704
+ ?: "Purchase failed")
705
+ }
706
+ }
707
+
708
+ override fun getProductsFinished(result: List<IapProduct>, missing: List<String>) {
709
+ val okSkus = JsonArray()
710
+ for (iapProduct in result) {
711
+ val jsonProduct = JsonObject()
712
+ jsonProduct.addProperty("title", iapProduct.title)
713
+ jsonProduct.addProperty("description", iapProduct.description)
714
+ jsonProduct.addProperty("price", iapProduct.price)
715
+ jsonProduct.addProperty("icon", iapProduct.icon)
716
+ jsonProduct.addProperty("type", iapProduct.type)
717
+ jsonProduct.addProperty("sku", iapProduct.sku)
718
+ okSkus.add(jsonProduct)
719
+ }
720
+ val badSkus = JsonArray()
721
+ for (sku in missing) {
722
+ badSkus.add(sku)
723
+ }
724
+ jsBridge.bridgeIAPGetProductDataSuccess(okSkus.toString(), badSkus.toString())
725
+ }
726
+
727
+ override fun getProductsFinished(exception: Exception) {
728
+ if (exception is IapException) {
729
+ jsBridge.bridgeIAPGetProductDataFail(exception.status)
730
+ } else {
731
+ jsBridge.bridgeIAPGetProductDataFail(exception.message ?: "Purchase failed")
732
+ }
733
+ }
734
+
735
+ override fun acknowledgeReceiptFinished(exception: Exception?) {
736
+ // JS does not care
737
+ }
738
+ }
739
+
740
+ companion object {
741
+ const val JAVA_SCRIPT_BRIDGE = "JavaScriptBridge"
742
+ const val LOW_END_DEVICE_MAX_BITRATE = 1500000
743
+ val DISABLED_TEXT_TRACK = TextTrack {
744
+ id = "text_off"
745
+ kind = TrackKind.EMPTY
746
+ }
747
+ }
748
+ }