@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.
- package/dist/src/__tests__/resolvers/resolveBuildFileBaseName.spec.js +80 -0
- package/dist/src/resolvers/index.js +3 -1
- package/dist/src/resolvers/resolveBuildFileBaseName.js +34 -0
- package/dist/src/resolvers/resolveBuildFileName.js +3 -27
- package/dist/src/scripts/releaseAllWithScheme.js +1 -1
- package/dist/src/sdks/index.js +5 -1
- package/dist/src/sdks/sdk-androidtv/index.js +17 -0
- package/dist/src/sdks/sdk-androidtv/sdkAndroidtv.js +264 -0
- package/dist/src/sdks/sdk-utils/sdk-installer/services/unzip.js +3 -2
- package/dist/src/tasks/task.rnv.build.js +3 -1
- package/dist/src/tasks/task.rnv.configure.js +3 -1
- package/dist/src/tasks/task.rnv.platform.configure.js +4 -1
- package/dist/templates/platforms/_shared/template.js +3 -3
- package/dist/templates/platforms/androidtv/project/README.md +196 -0
- package/dist/templates/platforms/androidtv/project/amazoniap/build.gradle +37 -0
- package/dist/templates/platforms/androidtv/project/amazoniap/consumer-rules.pro +0 -0
- package/dist/templates/platforms/androidtv/project/amazoniap/libs/in-app-purchasing-2.0.76.jar +0 -0
- package/dist/templates/platforms/androidtv/project/amazoniap/proguard-rules.pro +21 -0
- package/dist/templates/platforms/androidtv/project/amazoniap/src/main/AndroidManifest.xml +4 -0
- package/dist/templates/platforms/androidtv/project/amazoniap/src/main/java/com/twentyfouri/bridgetv/amazoniap/AmazonIapSevice.kt +106 -0
- package/dist/templates/platforms/androidtv/project/app/build.gradle +112 -0
- package/dist/templates/platforms/androidtv/project/app/proguard-rules.pro +32 -0
- package/dist/templates/platforms/androidtv/project/app/src/bigscreenAppstage/java/com/twentyfouri/tvbridge/Flavor.kt +5 -0
- package/dist/templates/platforms/androidtv/project/app/src/bigscreenAppstage/res/raw/configuration.json +9 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/AndroidManifest.xml +53 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/FlavorBase.kt +12 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/bridge/BridgeFromJavaScript.kt +392 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/bridge/BridgeToJavaScript.kt +89 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/configuration/BridgeConfig.kt +16 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/configuration/ConfigCallback.kt +6 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/configuration/ConfigProvider.kt +27 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/configuration/Drm.kt +10 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/configuration/UserAgent.kt +6 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/BaseApplication.kt +73 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/di/ActivityComponent.kt +13 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/di/ActivityModule.kt +17 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/di/ApplicationComponent.kt +26 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/di/ApplicationModule.kt +51 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/di/PerActivity.kt +26 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/utils/LocalStorage.kt +30 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/global/utils/LoggingDrmCallback.kt +38 -0
- 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/SimpleActivityLifecycleCallback.kt +32 -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 +211 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/sound/TouchPlayer.kt +86 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/client/BridgeWebView.kt +82 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/client/BridgeWebViewClient.kt +114 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/model/AnalyticsPlaybackInfo.kt +10 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/model/ContentModel.kt +14 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/model/DrmModel.kt +20 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/model/MediaEventType.kt +20 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/parser/ContentModelParser.kt +16 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/parser/DeviceInfoParser.kt +22 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/parser/PlaybackInfoParser.kt +10 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/parser/TrackJsonModelParser.kt +58 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/view/Mainview.kt +20 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/view/PlayerCache.kt +15 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/view/WebViewActivity.kt +306 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/java/com/twentyfouri/tvbridge/webview/viewmodel/WebViewModel.kt +609 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/res/drawable/button_background_focus.xml +10 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/res/drawable/button_background_normal.xml +7 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/res/drawable/button_background_pressed.xml +7 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/res/drawable/button_focus.xml +8 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/res/layout/activity_web_view.xml +76 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/res/raw/click.ogg +0 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/res/values/styles.xml +12 -0
- package/dist/templates/platforms/androidtv/project/app/src/main/res/xml/network_security_config.xml +4 -0
- package/dist/templates/platforms/androidtv/project/build.gradle +48 -0
- package/dist/templates/platforms/androidtv/project/common/build.gradle +36 -0
- package/dist/templates/platforms/androidtv/project/common/consumer-rules.pro +0 -0
- package/dist/templates/platforms/androidtv/project/common/proguard-rules.pro +21 -0
- package/dist/templates/platforms/androidtv/project/common/src/main/AndroidManifest.xml +4 -0
- package/dist/templates/platforms/androidtv/project/common/src/main/java/com/twentyfouri/tvbridge/common/IapService.kt +114 -0
- package/dist/templates/platforms/androidtv/project/googleiap/build.gradle +38 -0
- package/dist/templates/platforms/androidtv/project/googleiap/consumer-rules.pro +0 -0
- package/dist/templates/platforms/androidtv/project/googleiap/proguard-rules.pro +21 -0
- package/dist/templates/platforms/androidtv/project/googleiap/src/main/AndroidManifest.xml +6 -0
- package/dist/templates/platforms/androidtv/project/googleiap/src/main/java/com/twentyfouri/tvbridge/googleiap/GoogleIapService.kt +29 -0
- package/dist/templates/platforms/androidtv/project/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/dist/templates/platforms/androidtv/project/gradle/wrapper/gradle-wrapper.properties +6 -0
- package/dist/templates/platforms/androidtv/project/gradle.properties +4 -0
- package/dist/templates/platforms/androidtv/project/gradlew +185 -0
- package/dist/templates/platforms/androidtv/project/gradlew.bat +104 -0
- package/dist/templates/platforms/androidtv/project/proguard-rules.pro +42 -0
- package/dist/templates/platforms/androidtv/project/settings.gradle +4 -0
- 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,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>
|