@24i/smartapps-bigscreen-rnv-build-engine 0.17.21-alpha.2697 → 0.17.22

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.
@@ -145,8 +145,8 @@ const configureProject = (c) => {
145
145
  }
146
146
  const appId = rnv_1.Common.getConfigProp(c, c.platform, 'id', '');
147
147
  const minSdkVersion = rnv_1.Common.getConfigProp(c, c.platform, 'minSdkVersion', 21);
148
- const targetSdkVersion = rnv_1.Common.getConfigProp(c, c.platform, 'targetSdkVersion', 33);
149
- const compileSdkVersion = rnv_1.Common.getConfigProp(c, c.platform, 'compileSdkVersion', 33);
148
+ const targetSdkVersion = rnv_1.Common.getConfigProp(c, c.platform, 'targetSdkVersion', 34);
149
+ const compileSdkVersion = rnv_1.Common.getConfigProp(c, c.platform, 'compileSdkVersion', 34);
150
150
  let versionCode = rnv_1.Common.getConfigProp(c, c.platform, 'versionCode');
151
151
  if (typeof versionCode === 'undefined') {
152
152
  const major = `${semVerObj.major}`.padStart(2, '0');
@@ -202,12 +202,17 @@ const configureEntryUrl = (c) => {
202
202
  const entryUrl = getHostedAppEntryUrl(c);
203
203
  const theoLicense = process.env['THEO_LICENSE'] ||
204
204
  rnv_1.Common.getConfigProp(c, c.platform, 'theoLicense', '');
205
+ const npawAccountCode = process.env['NPAW_ACCOUNT_CODE'] ||
206
+ rnv_1.Common.getConfigProp(c, c.platform, 'npawAccountCode', '');
205
207
  const injects = [{
206
208
  pattern: '{{APP_URL}}',
207
209
  override: entryUrl,
208
210
  }, {
209
211
  pattern: '{{THEO_LICENSE}}',
210
212
  override: theoLicense,
213
+ }, {
214
+ pattern: '{{NPAW_ACCOUNT_CODE}}',
215
+ override: npawAccountCode,
211
216
  }];
212
217
  rnv_1.Common.addSystemInjects(c, injects);
213
218
  rnv_1.FileUtils.writeCleanFile(buildGradleFilePath, buildGradleFilePath, injects, null, c);
@@ -4,6 +4,8 @@ apply plugin: 'kotlin-kapt'
4
4
 
5
5
  def theoLicense = '{{THEO_LICENSE}}'
6
6
  def useTheoPlayer = theoLicense != ''
7
+ def npawAccountCode = '{{NPAW_ACCOUNT_CODE}}'
8
+ def useNpawPlugin = npawAccountCode != ''
7
9
 
8
10
  android {
9
11
  namespace = 'com.twentyfouri.tvbridge'
@@ -37,6 +39,7 @@ android {
37
39
 
38
40
  buildConfigField "String", "APP_URL", '"{{APP_URL}}"'
39
41
  buildConfigField "String", "THEO_LICENSE", "\"$theoLicense\""
42
+ buildConfigField "String", "NPAW_ACCOUNT_CODE", "\"$npawAccountCode\""
40
43
  buildConfigField "Boolean", "startWithHighVideoQuality", "true"
41
44
  }
42
45
 
@@ -86,7 +89,7 @@ dependencies {
86
89
  api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
87
90
 
88
91
  compileOnly "javax.annotation:jsr250-api:1.0"
89
- implementation 'androidx.core:core-ktx:1.9.0'
92
+ implementation 'androidx.core:core-ktx:1.12.0'
90
93
  implementation "androidx.appcompat:appcompat:1.6.1"
91
94
  implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
92
95
 
@@ -94,17 +97,27 @@ dependencies {
94
97
  kapt "com.google.dagger:dagger-compiler:2.48.1"
95
98
 
96
99
  implementation "com.squareup.retrofit2:retrofit:2.9.0"
97
- implementation "com.squareup.retrofit2:converter-gson:2.5.0"
100
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
98
101
  implementation "com.squareup.okhttp3:logging-interceptor:4.9.2"
99
102
  implementation "com.squareup.okhttp3:okhttp:4.9.2"
100
103
  if (useTheoPlayer) {
101
104
  compileOnly "com.twentyfouri.player:exoplayer:0.7.0-alpha.3"
102
105
  implementation "com.twentyfouri.player:theoplayer:0.7.0-alpha.3"
103
- implementation "com.theoplayer.theoplayer-sdk-android:core:6.8.0"
106
+ implementation "com.theoplayer.theoplayer-sdk-android:core:6.2.1"
104
107
  } else {
105
108
  implementation "com.twentyfouri.player:exoplayer:0.7.0-alpha.3"
106
109
  compileOnly "com.twentyfouri.player:theoplayer:0.7.0-alpha.3"
107
- compileOnly "com.theoplayer.theoplayer-sdk-android:core:6.8.0"
110
+ compileOnly "com.theoplayer.theoplayer-sdk-android:core:6.2.1"
111
+ }
112
+
113
+ if (useNpawPlugin) {
114
+ implementation 'com.npaw.plugin:plugin:7.2.31'
115
+ implementation 'com.npaw.plugin:plugin-exoplayer:2.19.1-7.2.31'
116
+ implementation 'com.google.android.exoplayer:extension-okhttp:2.19.1'
117
+ } else {
118
+ compileOnly 'com.npaw.plugin:plugin:7.2.31'
119
+ compileOnly 'com.npaw.plugin:plugin-exoplayer:2.19.1-7.2.31'
120
+ compileOnly 'com.google.android.exoplayer:extension-okhttp:2.19.1'
108
121
  }
109
122
 
110
123
  implementation project(':common')
@@ -14,4 +14,5 @@ class BridgeConfig {
14
14
  var customizableUrl: Boolean = BuildConfig.DEBUG
15
15
  var startWithHighVideoQuality: Boolean = false
16
16
  var theoLicense: String = BuildConfig.THEO_LICENSE
17
+ var npawAccount: String = BuildConfig.NPAW_ACCOUNT_CODE
17
18
  }
@@ -11,6 +11,9 @@ object PlayerFactoryConfigured {
11
11
  if (bridgeConfig.theoLicense.isNotEmpty()) {
12
12
  factory = factory ?: buildTheoFactory(bridgeConfig)
13
13
  }
14
+ if (bridgeConfig.npawAccount.isNotEmpty()) {
15
+ factory = factory ?: buildNpawFactory(bridgeConfig)
16
+ }
14
17
  factory = factory ?: buildExoFactory(bridgeConfig)
15
18
  return factory ?: throw Exception("Cannot create player factory")
16
19
  }
@@ -23,6 +26,14 @@ object PlayerFactoryConfigured {
23
26
  }
24
27
  }
25
28
 
29
+ private fun buildNpawFactory(bridgeConfig: BridgeConfig): PlayerFactoryBase? {
30
+ try {
31
+ return PlayerFactoryExoPlayerNpaw(bridgeConfig)
32
+ } catch (e: NoClassDefFoundError) {
33
+ return null
34
+ }
35
+ }
36
+
26
37
  private fun buildTheoFactory(bridgeConfig: BridgeConfig): PlayerFactoryBase? {
27
38
  try {
28
39
  return PlayerFactoryTheoPlayerCustomized(bridgeConfig)
@@ -0,0 +1,521 @@
1
+ package com.twentyfouri.tvbridge.global.utils
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.app.Application
5
+ import android.content.Context
6
+ import android.content.ContextWrapper
7
+ import android.os.Bundle
8
+ import android.os.Parcelable
9
+ import android.util.AttributeSet
10
+ import android.view.Gravity
11
+ import android.view.MotionEvent
12
+ import android.view.SurfaceView
13
+ import android.view.View
14
+ import android.view.ViewGroup
15
+ import android.view.ViewGroup.LayoutParams.MATCH_PARENT
16
+ import android.widget.FrameLayout
17
+ import android.widget.ImageView
18
+ import android.widget.Space
19
+ import androidx.appcompat.widget.AppCompatImageView
20
+ import androidx.lifecycle.DefaultLifecycleObserver
21
+ import androidx.lifecycle.Lifecycle
22
+ import androidx.lifecycle.LifecycleOwner
23
+ import com.google.android.exoplayer2.C
24
+ import com.google.android.exoplayer2.ExoPlayerLibraryInfo
25
+ import com.google.android.exoplayer2.Player
26
+ import com.google.android.exoplayer2.drm.DefaultDrmSessionManager
27
+ import com.google.android.exoplayer2.drm.DrmSessionManager
28
+ import com.google.android.exoplayer2.drm.FrameworkMediaDrm
29
+ import com.google.android.exoplayer2.drm.MediaDrmCallback
30
+ import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource
31
+ import com.google.android.exoplayer2.text.Cue
32
+ import com.google.android.exoplayer2.text.CueGroup
33
+ import com.google.android.exoplayer2.text.TextOutput
34
+ import com.google.android.exoplayer2.ui.AdOverlayInfo
35
+ import com.google.android.exoplayer2.ui.AdViewProvider
36
+ import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
37
+ import com.google.android.exoplayer2.ui.SubtitleView
38
+ import com.google.android.exoplayer2.upstream.HttpDataSource
39
+ import com.google.android.exoplayer2.upstream.cache.CacheDataSource
40
+ import com.google.android.exoplayer2.video.VideoSize
41
+ import com.npaw.NpawPlugin
42
+ import com.npaw.analytics.video.VideoAdapter
43
+ import com.npaw.analytics.video.VideoOptions
44
+ import com.npaw.core.options.AnalyticsOptions
45
+ import com.npaw.exoplayer.ExoPlayerAdapter
46
+ import com.npaw.exoplayer.ExoPlayerBalancer
47
+ import com.twentyfouri.player.base.CastingEnded
48
+ import com.twentyfouri.player.base.CastingStarted
49
+ import com.twentyfouri.player.base.DrmPlayready
50
+ import com.twentyfouri.player.base.DrmWidevine
51
+ import com.twentyfouri.player.base.EventListenerDispose
52
+ import com.twentyfouri.player.base.PlayerBase
53
+ import com.twentyfouri.player.base.PlayerGesturesTarget
54
+ import com.twentyfouri.player.base.PlayerSurface
55
+ import com.twentyfouri.player.base.ResizeEvent
56
+ import com.twentyfouri.player.base.ScaleType
57
+ import com.twentyfouri.player.base.Source
58
+ import com.twentyfouri.player.base.SourceAuthorization
59
+ import com.twentyfouri.player.base.SourceDrm
60
+ import com.twentyfouri.player.base.SourceMetadata
61
+ import com.twentyfouri.player.base.addEventListener
62
+ import com.twentyfouri.player.base.buildUpon
63
+ import com.twentyfouri.player.base.getBoolean
64
+ import com.twentyfouri.player.base.getString
65
+ import com.twentyfouri.player.exoplayer.AuthorizedDataSourceProvider
66
+ import com.twentyfouri.player.exoplayer.DefaultDrmSessionManagerProvider
67
+ import com.twentyfouri.player.exoplayer.DefaultPlayerComponents
68
+ import com.twentyfouri.player.exoplayer.DefaultSharedComponents
69
+ import com.twentyfouri.player.exoplayer.DrmSessionManagerProvider
70
+ import com.twentyfouri.player.exoplayer.PlayerComponents
71
+ import com.twentyfouri.player.exoplayer.PlayerExoPlayer
72
+ import com.twentyfouri.player.exoplayer.PlayerFactoryExoPlayer
73
+ import com.twentyfouri.player.exoplayer.PlayerProxy
74
+ import com.twentyfouri.player.exoplayer.PlayerViewAdvertisement
75
+ import com.twentyfouri.player.exoplayer.SharedComponents
76
+ import com.twentyfouri.tvbridge.BuildConfig
77
+ import com.twentyfouri.tvbridge.configuration.BridgeConfig
78
+ import com.npaw.core.util.extensions.Log as NpawLog
79
+
80
+ class PlayerFactoryExoPlayerNpaw(
81
+ private val config: BridgeConfig
82
+ ) : PlayerFactoryExoPlayer(), PlayerFactoryVersion {
83
+
84
+ override val engineName: String = "Exoplayer"
85
+
86
+ override val engineVersion: String = ExoPlayerLibraryInfo.VERSION
87
+
88
+ override fun createSharedComponents(context: Context): SharedComponents =
89
+ CustomizedSharedComponents(context)
90
+
91
+ override fun createView(parent: ViewGroup): View {
92
+ return NpawPlayerView(parent.context).also {
93
+ val basePlayer = createPlayer(parent.context)
94
+ val npawPlugin = (
95
+ (basePlayer.components as CustomizedPlayerComponents)
96
+ .sharedComponents as CustomizedSharedComponents
97
+ ).npawPlugin
98
+ val npawPlayer = NpawPlayer(basePlayer, npawPlugin)
99
+ trackPlayer(npawPlayer)
100
+ it.player = npawPlayer
101
+ }
102
+ }
103
+
104
+ override fun getPlayerSurface(view: View): PlayerSurface {
105
+ return view as NpawPlayerView
106
+ }
107
+
108
+ override fun getPlayer(view: View): PlayerBase {
109
+ return (view as NpawPlayerView).player
110
+ ?: throw IllegalStateException("Player was not initialized by PlayerFactory")
111
+ }
112
+
113
+ private inner class CustomizedSharedComponents(context: Context) : DefaultSharedComponents(context) {
114
+
115
+ val application = context.applicationContext as? Application
116
+ ?: throw IllegalStateException("Missing application")
117
+
118
+ val npawPlugin: NpawPlugin = NpawPlugin
119
+ .Builder(application, config.npawAccount)
120
+ .setLogLevel(if (BuildConfig.DEBUG) NpawLog.Level.VERBOSE else NpawLog.Level.ERROR)
121
+ .build()
122
+
123
+ val npawBalancer = ExoPlayerBalancer(npawPlugin)
124
+
125
+ override fun destroy() {
126
+ super.destroy()
127
+ npawPlugin.destroy()
128
+ }
129
+
130
+ override val drmProvider: DrmSessionManagerProvider by lazy { CustomizedDrmSessionManagerProvider() }
131
+
132
+ override val applicationName: String
133
+ get() = config.playerAppName ?: super.applicationName
134
+
135
+ override fun buildIsolatedComponents(): PlayerComponents {
136
+ return CustomizedPlayerComponents(this)
137
+ }
138
+
139
+ override val baseHttpFactory: HttpDataSource.Factory by lazy {
140
+ npawBalancer.dataSourceFactory as OkHttpDataSource.Factory
141
+ }
142
+
143
+ override val dataSourceFactory: AuthorizedDataSourceProvider by lazy {
144
+ object : AuthorizedDataSourceProvider {
145
+ override fun buildDataSourceFactory(authorization: SourceAuthorization?): CacheDataSource.Factory {
146
+ return buildReadOnlyDataSourceFactory(baseHttpFactory)
147
+ }
148
+
149
+ override fun buildHttpDataSourceFactory(authorization: SourceAuthorization?): HttpDataSource.Factory {
150
+ return baseHttpFactory
151
+ }
152
+ }
153
+ }
154
+
155
+ override val cacheDataSourceFactory: AuthorizedDataSourceProvider by lazy {
156
+ dataSourceFactory
157
+ }
158
+ }
159
+
160
+ private inner class CustomizedPlayerComponents(shared: SharedComponents) : DefaultPlayerComponents(shared) {
161
+ override val handleAudioFocus: Boolean
162
+ get() = false
163
+ }
164
+
165
+ private class CustomizedDrmSessionManagerProvider : DefaultDrmSessionManagerProvider() {
166
+ override fun buildDrmSessionManager(
167
+ dataSourceFactory: AuthorizedDataSourceProvider,
168
+ source: Source,
169
+ sourceDrm: SourceDrm
170
+ ): DrmSessionManager? {
171
+ if (sourceDrm is SourceDrmOverridden) {
172
+ return buildDrmOverridden(dataSourceFactory, sourceDrm)
173
+ } else {
174
+ return super.buildDrmSessionManager(dataSourceFactory, source, sourceDrm)
175
+ }
176
+ }
177
+
178
+ private fun buildDrmOverridden(dataSourceFactory: AuthorizedDataSourceProvider, sourceDrm: SourceDrmOverridden): DrmSessionManager? {
179
+ val drmUuid = when (sourceDrm.packageName) {
180
+ DrmWidevine.packageName -> C.WIDEVINE_UUID
181
+ DrmPlayready.packageName -> C.PLAYREADY_UUID
182
+ else -> C.UUID_NIL
183
+ }
184
+ val licenseUrl = sourceDrm.licenseServer?.toString() ?: ""
185
+ val forceLicenseUrl = sourceDrm.forceLicenseServer
186
+ val httpDataSourceFactory = dataSourceFactory.buildHttpDataSourceFactory(sourceDrm.authorization)
187
+
188
+ val drmCallback = LoggingDrmCallback(
189
+ OverriddenHttpMediaDrmCallback(
190
+ licenseUrl,
191
+ forceLicenseUrl,
192
+ httpDataSourceFactory
193
+ ),
194
+ licenseUrl
195
+ )
196
+
197
+ return DefaultDrmSessionManager.Builder()
198
+ .setUuidAndExoMediaDrmProvider(drmUuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
199
+ .setMultiSession(false)
200
+ .build(drmCallback)
201
+ }
202
+
203
+ override fun buildDrmCallback(licenseUrl: String, forceLicenseUrl: Boolean, httpDataSourceFactory: HttpDataSource.Factory): MediaDrmCallback {
204
+ return LoggingDrmCallback(super.buildDrmCallback(licenseUrl, forceLicenseUrl, httpDataSourceFactory), licenseUrl)
205
+ }
206
+ }
207
+
208
+ private class NpawPlayer(
209
+ val basePlayer: PlayerExoPlayer,
210
+ val npawPlugin: NpawPlugin
211
+ ) : PlayerBase by basePlayer {
212
+ var exoPlayerAdapter: ExoPlayerAdapter? = null
213
+
214
+ var videoAdapter: VideoAdapter? = null
215
+ set(value) {
216
+ field?.destroy()
217
+ field = value
218
+ }
219
+
220
+ fun extractYouboraAnalyticsOptions(source: Source?, target: AnalyticsOptions) {
221
+ if (source == null) return
222
+ val jsonAnalyticsArray = source.extras?.optJSONArray("analytics") ?: return
223
+ for (index in 0 until jsonAnalyticsArray.length()) {
224
+ val jsonAnalytics = jsonAnalyticsArray.getJSONObject(index)
225
+ if (jsonAnalytics.getString("integration", "") != "youbora") continue
226
+ jsonAnalytics.keys().forEach { key ->
227
+ when (key) {
228
+ "app.name" -> target.appName = jsonAnalytics.getString(key, null)
229
+ "app.releaseVersion" -> target.appReleaseVersion = jsonAnalytics.getString(key, null)
230
+ "device.id" -> target.deviceId = jsonAnalytics.getString(key, null)
231
+ }
232
+ }
233
+ }
234
+ }
235
+
236
+ fun extractYouboraVideoOptions(source: Source?, target: VideoOptions) {
237
+ if (source == null) return
238
+ val jsonAnalyticsArray = source.extras?.optJSONArray("analytics") ?: return
239
+ for (index in 0 until jsonAnalyticsArray.length()) {
240
+ val jsonAnalytics = jsonAnalyticsArray.getJSONObject(index)
241
+ if (jsonAnalytics.getString("integration", "") != "youbora") continue
242
+ jsonAnalytics.keys().forEach { key ->
243
+ when (key) {
244
+ "content.id" -> target.contentId = jsonAnalytics.getString(key, null)
245
+ "content.title" -> target.contentTitle = jsonAnalytics.getString(key, null)
246
+ "content.isLive" -> target.live = jsonAnalytics.getBoolean(key, false)
247
+ "content.type" -> target.contentType = jsonAnalytics.getString(key, null)
248
+ "content.program" -> target.program = jsonAnalytics.getString(key, null)
249
+ "content.channel" -> target.contentChannel = jsonAnalytics.getString(key, null)
250
+ "content.episodeTitle" -> target.contentEpisodeTitle = jsonAnalytics.getString(key, null)
251
+ "content.season" -> target.contentSeason = jsonAnalytics.getString(key, null)
252
+ "content.tvShow" -> target.contentTvShow = jsonAnalytics.getString(key, null)
253
+ "content.customDimension.1" -> target.contentCustomDimension1 = jsonAnalytics.getString(key, null)
254
+ "content.customDimension.2" -> target.contentCustomDimension2 = jsonAnalytics.getString(key, null)
255
+ }
256
+ }
257
+ }
258
+ }
259
+
260
+ override var source: Source?
261
+ get() = basePlayer.source
262
+ set(value) {
263
+ exoPlayerAdapter = null
264
+ videoAdapter = null
265
+ if (value != null) {
266
+ val videoOptions = VideoOptions()
267
+ extractYouboraAnalyticsOptions(value, npawPlugin.analyticsOptions)
268
+ extractYouboraVideoOptions(value, videoOptions)
269
+
270
+ var adjustedSource = value
271
+ if (value.metadata?.title.isNullOrEmpty() && !videoOptions.contentTitle.isNullOrEmpty()) {
272
+ adjustedSource = value.buildUpon {
273
+ metadata = (metadata?.buildUpon() ?: SourceMetadata.Builder()).apply {
274
+ title = videoOptions.contentTitle
275
+ }.build()
276
+ }
277
+ }
278
+
279
+ basePlayer.source = adjustedSource
280
+ exoPlayerAdapter = ExoPlayerAdapter(basePlayer.components.context, basePlayer.exoPlayer)
281
+ videoAdapter = npawPlugin.videoBuilder()
282
+ .setPlayerAdapter(exoPlayerAdapter)
283
+ .setOptions(videoOptions)
284
+ .build()
285
+ } else {
286
+ basePlayer.source = null
287
+ }
288
+ }
289
+
290
+ override fun destroy() {
291
+ exoPlayerAdapter = null
292
+ videoAdapter = null
293
+ basePlayer.destroy()
294
+ }
295
+ }
296
+
297
+ class NpawPlayerView @JvmOverloads constructor(
298
+ context: Context,
299
+ attrs: AttributeSet? = null,
300
+ defStyleAttr: Int = 0
301
+ ) : FrameLayout(context, attrs, defStyleAttr), AdViewProvider, PlayerSurface {
302
+
303
+ private val componentListener = ComponentListener()
304
+ private val onDetachPlayer = EventListenerDispose()
305
+
306
+ val videoFrame: AspectRatioFrameLayout = AspectRatioFrameLayout(context)
307
+ val surfaceView: View = when {
308
+ isInEditMode -> Space(context)
309
+ else -> SurfaceView(context)
310
+ }
311
+ val posterView: ImageView = AppCompatImageView(context).also {
312
+ it.scaleType = ImageView.ScaleType.FIT_CENTER
313
+ }
314
+ val subtitleView: SubtitleView = SubtitleView(context)
315
+ val adOverlay: PlayerViewAdvertisement = PlayerViewAdvertisement(context).also {
316
+ it.fitsSystemWindows = true
317
+ }
318
+ override var adObstructions: List<View> = emptyList()
319
+ override var gesturesTarget: PlayerGesturesTarget?
320
+ get() = adOverlay.gesturesTarget
321
+ set(value) {
322
+ adOverlay.gesturesTarget = value
323
+ }
324
+
325
+ var player: PlayerBase? = null
326
+ set(value) {
327
+ if (field == value) return
328
+ field?.also { detachPlayer(it) }
329
+ field = value
330
+ field?.also { attachPlayer(it) }
331
+ }
332
+
333
+ var pausedByBackground: Boolean = false
334
+ get() = player?.asPlayerExoPlayer()?.pausedByBackground ?: field
335
+ set(value) {
336
+ field = value
337
+ player?.asPlayerExoPlayer()?.pausedByBackground = value
338
+ }
339
+ var surfaceAttached: Boolean = false
340
+
341
+ init {
342
+ if (!isInEditMode) {
343
+ subtitleView.setUserDefaultTextSize()
344
+ subtitleView.setUserDefaultStyle()
345
+ subtitleView.setApplyEmbeddedStyles(false)
346
+ }
347
+
348
+ addView(videoFrame, LayoutParams(MATCH_PARENT, MATCH_PARENT, Gravity.CENTER))
349
+ videoFrame.addView(surfaceView, MATCH_PARENT, MATCH_PARENT)
350
+ videoFrame.addView(subtitleView, MATCH_PARENT, MATCH_PARENT)
351
+ addView(posterView, MATCH_PARENT, MATCH_PARENT)
352
+ addView(adOverlay, MATCH_PARENT, MATCH_PARENT)
353
+
354
+ context.lifecycle?.addObserver(componentListener)
355
+ }
356
+
357
+ private val Context.lifecycle: Lifecycle?
358
+ get() = when (this) {
359
+ is LifecycleOwner -> lifecycle
360
+ is ContextWrapper -> baseContext.lifecycle
361
+ else -> null
362
+ }
363
+
364
+ private fun detachPlayer(player: PlayerBase) {
365
+ onDetachPlayer()
366
+ player.asPlayerExoPlayer()?.also { detachSurface(it) }
367
+ }
368
+
369
+ private fun detachSurface(player: PlayerExoPlayer) {
370
+ if (!surfaceAttached) return
371
+ surfaceAttached = false
372
+ player.exoPlayer.removeListener(componentListener)
373
+ player.adViewProvider = null
374
+ player.posterSurface = null
375
+ player.videoSurface = null
376
+ subtitleView.setCues(null)
377
+ }
378
+
379
+ private fun attachPlayer(player: PlayerBase) {
380
+ player.asPlayerExoPlayer()?.also { attachSurface(it) }
381
+ player.fullscreen = isFullscreen
382
+ onDetachPlayer += player.addEventListener<ResizeEvent> {
383
+ videoFrame.resizeMode = player.scaleType.toResizeMode()
384
+ }
385
+ onDetachPlayer += player.addEventListener<CastingStarted> {
386
+ player.asPlayerExoPlayer()?.also { detachSurface(it) }
387
+ }
388
+ onDetachPlayer += player.addEventListener<CastingEnded> {
389
+ player.asPlayerExoPlayer()?.also { attachSurface(it) }
390
+ }
391
+ videoFrame.resizeMode = player.scaleType.toResizeMode()
392
+ }
393
+
394
+ private fun attachSurface(player: PlayerExoPlayer) {
395
+ if (surfaceAttached) return
396
+ surfaceAttached = true
397
+ player.exoPlayer.addListener(componentListener)
398
+ player.adViewProvider = this
399
+ player.posterSurface = posterView
400
+ player.videoSurface = surfaceView as? SurfaceView
401
+ }
402
+
403
+ private fun PlayerBase.asPlayerExoPlayer(): PlayerExoPlayer? = when {
404
+ this is PlayerExoPlayer -> this
405
+ this is PlayerProxy -> this.base?.asPlayerExoPlayer()
406
+ this is NpawPlayer -> this.basePlayer
407
+ else -> null
408
+ }
409
+
410
+ protected fun ScaleType?.toResizeMode(): Int = when (this) {
411
+ ScaleType.STRETCH -> AspectRatioFrameLayout.RESIZE_MODE_FILL
412
+ ScaleType.ZOOM -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
413
+ else -> AspectRatioFrameLayout.RESIZE_MODE_FIT
414
+ }
415
+
416
+ private var isFullscreen = false
417
+ set(value) {
418
+ if (field == value) return
419
+ field = value
420
+ player?.fullscreen = isFullscreen
421
+ }
422
+
423
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
424
+ super.onLayout(changed, left, top, right, bottom)
425
+
426
+ val displayMetrics = context.resources.displayMetrics
427
+ val screenWidth = displayMetrics.widthPixels
428
+ val minFullWidth = screenWidth * 9 / 10
429
+ val screenHeight = displayMetrics.heightPixels
430
+ val minFullHeight = screenHeight * 9 / 10
431
+ val width = right - left
432
+ val height = bottom - top
433
+ isFullscreen = width >= minFullWidth && height >= minFullHeight
434
+ }
435
+
436
+ @SuppressLint("ClickableViewAccessibility")
437
+ override fun onTouchEvent(event: MotionEvent): Boolean {
438
+ return gesturesTarget?.onDelegatedTouchEvent(event) ?: super.onTouchEvent(event)
439
+ }
440
+
441
+ override fun getAdViewGroup(): ViewGroup =
442
+ adOverlay
443
+
444
+ override fun getAdOverlayInfos(): List<AdOverlayInfo> =
445
+ adObstructions.map { AdOverlayInfo.Builder(it, AdOverlayInfo.PURPOSE_CONTROLS).build() }
446
+
447
+ override fun onSaveInstanceState(): Parcelable {
448
+ val state = Bundle()
449
+ state.putParcelable("base", super.onSaveInstanceState())
450
+ state.putBoolean("pausedByBackground", pausedByBackground)
451
+ return state
452
+ }
453
+
454
+ override fun onRestoreInstanceState(state: Parcelable?) {
455
+ val bundle = state as? Bundle
456
+ if (bundle == null) {
457
+ super.onRestoreInstanceState(state)
458
+ } else {
459
+ super.onRestoreInstanceState(bundle.getParcelable("base"))
460
+ pausedByBackground = bundle.getBoolean("pausedByBackground")
461
+ }
462
+ }
463
+
464
+ private inner class ComponentListener : TextOutput, Player.Listener,
465
+ DefaultLifecycleObserver {
466
+ override fun onCues(cueGroup: CueGroup) {
467
+ subtitleView.setCues(cueGroup.cues)
468
+ }
469
+
470
+ @Deprecated("No longer used")
471
+ override fun onCues(cues: MutableList<Cue>) {
472
+ // only overridden because compiler is confused about multiple default implementations
473
+ }
474
+
475
+ override fun onVideoSizeChanged(videoSize: VideoSize) {
476
+ videoFrame.resizeMode = player?.scaleType.toResizeMode()
477
+ videoFrame.setAspectRatio(
478
+ when {
479
+ videoSize.height <= 0 -> 1f
480
+ else -> videoSize.width * videoSize.pixelWidthHeightRatio / videoSize.height
481
+ }
482
+ )
483
+ }
484
+
485
+ override fun onIsPlayingChanged(isPlaying: Boolean) {
486
+ this@NpawPlayerView.keepScreenOn = isPlaying
487
+ }
488
+
489
+ override fun onPause(owner: LifecycleOwner) {
490
+ val player = this@NpawPlayerView.player ?: return
491
+ player.asPlayerExoPlayer()?.also {
492
+ detachSurface(it)
493
+ }
494
+ if (!player.continueInBackground && !player.paused && player.state.source != null) {
495
+ if (player.live) {
496
+ player.stop()
497
+ } else {
498
+ player.pause()
499
+ }
500
+ pausedByBackground = true
501
+ }
502
+ }
503
+
504
+ override fun onResume(owner: LifecycleOwner) {
505
+ val player = this@NpawPlayerView.player ?: return
506
+ if (pausedByBackground) {
507
+ pausedByBackground = false
508
+ player.play()
509
+ }
510
+ player.asPlayerExoPlayer()?.also {
511
+ attachSurface(it)
512
+ }
513
+ }
514
+
515
+ override fun onDestroy(owner: LifecycleOwner) {
516
+ context.lifecycle?.removeObserver(this)
517
+ }
518
+ }
519
+ }
520
+ }
521
+
@@ -38,7 +38,9 @@ allprojects {
38
38
  password = "$mavenRepositoryPassword"
39
39
  }
40
40
  }
41
+ maven { url "https://maven.theoplayer.com/releases" }
41
42
  maven { url "https://jitpack.io" }
43
+ maven { url "https://artifact.plugin.npaw.com/artifactory/plugins/android" }
42
44
  }
43
45
  }
44
46
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@24i/smartapps-bigscreen-rnv-build-engine",
3
- "version": "0.17.21-alpha.2697",
3
+ "version": "0.17.22",
4
4
  "description": "Package with build engine for SmartApps BIGscreen projects...",
5
5
  "main": "dist/src/index.js",
6
6
  "scripts": {