@24i/smartapps-bigscreen-rnv-build-engine 0.17.21 → 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.
|
@@ -1,31 +1,64 @@
|
|
|
1
1
|
package com.twentyfouri.tvbridge.global.utils
|
|
2
2
|
|
|
3
|
+
import android.annotation.SuppressLint
|
|
3
4
|
import android.app.Application
|
|
4
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
|
|
5
13
|
import android.view.View
|
|
6
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
|
|
7
23
|
import com.google.android.exoplayer2.C
|
|
8
24
|
import com.google.android.exoplayer2.ExoPlayerLibraryInfo
|
|
25
|
+
import com.google.android.exoplayer2.Player
|
|
9
26
|
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager
|
|
10
27
|
import com.google.android.exoplayer2.drm.DrmSessionManager
|
|
11
28
|
import com.google.android.exoplayer2.drm.FrameworkMediaDrm
|
|
12
29
|
import com.google.android.exoplayer2.drm.MediaDrmCallback
|
|
13
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
|
|
14
38
|
import com.google.android.exoplayer2.upstream.HttpDataSource
|
|
15
39
|
import com.google.android.exoplayer2.upstream.cache.CacheDataSource
|
|
40
|
+
import com.google.android.exoplayer2.video.VideoSize
|
|
16
41
|
import com.npaw.NpawPlugin
|
|
17
42
|
import com.npaw.analytics.video.VideoAdapter
|
|
18
43
|
import com.npaw.analytics.video.VideoOptions
|
|
19
44
|
import com.npaw.core.options.AnalyticsOptions
|
|
20
45
|
import com.npaw.exoplayer.ExoPlayerAdapter
|
|
21
46
|
import com.npaw.exoplayer.ExoPlayerBalancer
|
|
47
|
+
import com.twentyfouri.player.base.CastingEnded
|
|
48
|
+
import com.twentyfouri.player.base.CastingStarted
|
|
22
49
|
import com.twentyfouri.player.base.DrmPlayready
|
|
23
50
|
import com.twentyfouri.player.base.DrmWidevine
|
|
51
|
+
import com.twentyfouri.player.base.EventListenerDispose
|
|
24
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
|
|
25
57
|
import com.twentyfouri.player.base.Source
|
|
26
58
|
import com.twentyfouri.player.base.SourceAuthorization
|
|
27
59
|
import com.twentyfouri.player.base.SourceDrm
|
|
28
60
|
import com.twentyfouri.player.base.SourceMetadata
|
|
61
|
+
import com.twentyfouri.player.base.addEventListener
|
|
29
62
|
import com.twentyfouri.player.base.buildUpon
|
|
30
63
|
import com.twentyfouri.player.base.getBoolean
|
|
31
64
|
import com.twentyfouri.player.base.getString
|
|
@@ -37,7 +70,8 @@ import com.twentyfouri.player.exoplayer.DrmSessionManagerProvider
|
|
|
37
70
|
import com.twentyfouri.player.exoplayer.PlayerComponents
|
|
38
71
|
import com.twentyfouri.player.exoplayer.PlayerExoPlayer
|
|
39
72
|
import com.twentyfouri.player.exoplayer.PlayerFactoryExoPlayer
|
|
40
|
-
import com.twentyfouri.player.exoplayer.
|
|
73
|
+
import com.twentyfouri.player.exoplayer.PlayerProxy
|
|
74
|
+
import com.twentyfouri.player.exoplayer.PlayerViewAdvertisement
|
|
41
75
|
import com.twentyfouri.player.exoplayer.SharedComponents
|
|
42
76
|
import com.twentyfouri.tvbridge.BuildConfig
|
|
43
77
|
import com.twentyfouri.tvbridge.configuration.BridgeConfig
|
|
@@ -55,7 +89,7 @@ class PlayerFactoryExoPlayerNpaw(
|
|
|
55
89
|
CustomizedSharedComponents(context)
|
|
56
90
|
|
|
57
91
|
override fun createView(parent: ViewGroup): View {
|
|
58
|
-
return
|
|
92
|
+
return NpawPlayerView(parent.context).also {
|
|
59
93
|
val basePlayer = createPlayer(parent.context)
|
|
60
94
|
val npawPlugin = (
|
|
61
95
|
(basePlayer.components as CustomizedPlayerComponents)
|
|
@@ -67,6 +101,15 @@ class PlayerFactoryExoPlayerNpaw(
|
|
|
67
101
|
}
|
|
68
102
|
}
|
|
69
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
|
+
|
|
70
113
|
private inner class CustomizedSharedComponents(context: Context) : DefaultSharedComponents(context) {
|
|
71
114
|
|
|
72
115
|
val application = context.applicationContext as? Application
|
|
@@ -166,7 +209,6 @@ class PlayerFactoryExoPlayerNpaw(
|
|
|
166
209
|
val basePlayer: PlayerExoPlayer,
|
|
167
210
|
val npawPlugin: NpawPlugin
|
|
168
211
|
) : PlayerBase by basePlayer {
|
|
169
|
-
|
|
170
212
|
var exoPlayerAdapter: ExoPlayerAdapter? = null
|
|
171
213
|
|
|
172
214
|
var videoAdapter: VideoAdapter? = null
|
|
@@ -251,5 +293,229 @@ class PlayerFactoryExoPlayerNpaw(
|
|
|
251
293
|
basePlayer.destroy()
|
|
252
294
|
}
|
|
253
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
|
+
}
|
|
254
520
|
}
|
|
255
521
|
|