react-native-theoplayer 2.10.0 → 2.12.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 (75) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/README.md +12 -9
  3. package/android/src/main/java/com/theoplayer/PlayerEventEmitter.kt +1 -1
  4. package/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt +14 -0
  5. package/android/src/main/java/com/theoplayer/ReactTHEOplayerView.kt +1 -3
  6. package/android/src/main/java/com/theoplayer/audio/AudioFocusManager.kt +145 -0
  7. package/android/src/main/java/com/theoplayer/drm/ContentProtectionModule.kt +1 -1
  8. package/android/src/main/java/com/theoplayer/player/PlayerModule.kt +10 -0
  9. package/android/src/main/java/com/theoplayer/presentation/PipUtils.kt +16 -9
  10. package/android/src/main/java/com/theoplayer/track/TextTrackStyleAdapter.kt +79 -0
  11. package/android/src/main/java/com/theoplayer/util/ViewResolver.kt +6 -0
  12. package/ios/THEOplayerRCTBridge.m +3 -0
  13. package/ios/THEOplayerRCTPlayerAPI.swift +66 -2
  14. package/ios/THEOplayerRCTSourceDescriptionBuilder.swift +2 -0
  15. package/ios/THEOplayerRCTTextTrackEventHandler.swift +32 -4
  16. package/ios/THEOplayerRCTTypeUtils.swift +15 -0
  17. package/ios/ads/THEOplayerRCTAdsAPI+DAI.swift +1 -1
  18. package/ios/ads/THEOplayerRCTSourceDescriptionBuilder+Ads.swift +1 -1
  19. package/ios/ads/THEOplayerRCTView+AdsConfig.swift +1 -1
  20. package/ios/backgroundAudio/THEOplayerRCTNowPlayingManager.swift +3 -3
  21. package/ios/backgroundAudio/THEOplayerRCTRemoteCommandsManager.swift +51 -36
  22. package/lib/commonjs/api/source/SourceDescription.js.map +1 -1
  23. package/lib/commonjs/api/source/analytics/AnalyticsDescription.js +2 -0
  24. package/lib/commonjs/api/source/analytics/AnalyticsDescription.js.map +1 -0
  25. package/lib/commonjs/api/source/analytics/barrel.js +17 -0
  26. package/lib/commonjs/api/source/analytics/barrel.js.map +1 -0
  27. package/lib/commonjs/api/source/barrel.js +15 -4
  28. package/lib/commonjs/api/source/barrel.js.map +1 -1
  29. package/lib/commonjs/api/track/TextTrack.js +1 -1
  30. package/lib/commonjs/api/track/TextTrack.js.map +1 -1
  31. package/lib/commonjs/api/track/TextTrackStyle.js +29 -0
  32. package/lib/commonjs/api/track/TextTrackStyle.js.map +1 -1
  33. package/lib/commonjs/internal/adapter/THEOplayerWebAdapter.js.map +1 -1
  34. package/lib/commonjs/internal/adapter/track/NamedColors.json +143 -0
  35. package/lib/commonjs/internal/adapter/track/TextTrackStyleAdapter.js +118 -22
  36. package/lib/commonjs/internal/adapter/track/TextTrackStyleAdapter.js.map +1 -1
  37. package/lib/commonjs/internal/adapter/web/WebMediaSession.js +1 -1
  38. package/lib/commonjs/internal/adapter/web/WebMediaSession.js.map +1 -1
  39. package/lib/module/api/source/SourceDescription.js.map +1 -1
  40. package/lib/module/api/source/analytics/AnalyticsDescription.js +2 -0
  41. package/lib/module/api/source/analytics/AnalyticsDescription.js.map +1 -0
  42. package/lib/module/api/source/analytics/barrel.js +2 -0
  43. package/lib/module/api/source/analytics/barrel.js.map +1 -0
  44. package/lib/module/api/source/barrel.js +1 -0
  45. package/lib/module/api/source/barrel.js.map +1 -1
  46. package/lib/module/api/track/TextTrack.js +1 -1
  47. package/lib/module/api/track/TextTrack.js.map +1 -1
  48. package/lib/module/api/track/TextTrackStyle.js +23 -0
  49. package/lib/module/api/track/TextTrackStyle.js.map +1 -1
  50. package/lib/module/internal/adapter/THEOplayerWebAdapter.js.map +1 -1
  51. package/lib/module/internal/adapter/track/NamedColors.json +143 -0
  52. package/lib/module/internal/adapter/track/TextTrackStyleAdapter.js +117 -22
  53. package/lib/module/internal/adapter/track/TextTrackStyleAdapter.js.map +1 -1
  54. package/lib/module/internal/adapter/web/WebMediaSession.js +1 -1
  55. package/lib/module/internal/adapter/web/WebMediaSession.js.map +1 -1
  56. package/lib/typescript/api/source/SourceDescription.d.ts +5 -0
  57. package/lib/typescript/api/source/analytics/AnalyticsDescription.d.ts +15 -0
  58. package/lib/typescript/api/source/analytics/barrel.d.ts +1 -0
  59. package/lib/typescript/api/source/barrel.d.ts +1 -0
  60. package/lib/typescript/api/track/TextTrack.d.ts +1 -1
  61. package/lib/typescript/api/track/TextTrackStyle.d.ts +43 -5
  62. package/lib/typescript/internal/adapter/track/TextTrackStyleAdapter.d.ts +26 -8
  63. package/package.json +1 -1
  64. package/react-native-theoplayer.json +2 -1
  65. package/react-native-theoplayer.podspec +5 -1
  66. package/src/api/source/SourceDescription.ts +6 -0
  67. package/src/api/source/analytics/AnalyticsDescription.ts +16 -0
  68. package/src/api/source/analytics/barrel.ts +1 -0
  69. package/src/api/source/barrel.ts +1 -0
  70. package/src/api/track/TextTrack.ts +2 -2
  71. package/src/api/track/TextTrackStyle.ts +45 -5
  72. package/src/internal/adapter/THEOplayerWebAdapter.ts +1 -1
  73. package/src/internal/adapter/track/NamedColors.json +143 -0
  74. package/src/internal/adapter/track/TextTrackStyleAdapter.ts +132 -21
  75. package/src/internal/adapter/web/WebMediaSession.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -2,9 +2,41 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
+ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.1.0/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.12.0] - 23-09-04
9
+
10
+ ### Added
11
+
12
+ - Added an `AnalyticsDescription` property to `SourceDescription` to configure additional source-specific properties for analytics connectors.
13
+ - Added support for sideloaded webVTT and SRT texttracks on iOS.
14
+ - Added Audio Focus for Android, pausing play-out on audio focus loss and resuming play-out once focus has been regained.
15
+ - Added Audio Focus and Audio Becoming Noisy manager for Android.
16
+
17
+ ### Fixed
18
+
19
+ - Fixed an issue on Android that would cause the player not to reset when setting an empty source.
20
+ - Fixed an issue where a text track cue was not properly removed from the cue list on a TextTrackEventType.REMOVE_CUE event.
21
+ - Fixed an issue on tvOS that allowed the user to pause a CSAI ad using the apple remote control.
22
+ - Fixed an issue on iOS where the cue event listeners were not cleanup up when destroying the player instance, resulting in memory build-up.
23
+
24
+ ## [2.11.0] - 23-08-10
25
+
26
+ ### Added
27
+
28
+ - Added DAI support through iOS Native pipeline, using new THEOplayerGoogleIMAIntegration functionality
29
+ - Added `TextTrackStyle` API for iOS and Android.
30
+
31
+ ### Fixed
32
+
33
+ - Fixed an issue on Android where the player would sometimes crash when requesting the current active video track.
34
+
35
+ ### Changed
36
+
37
+ - Switched to 'displayIconUri' in sourceDescription.metadata as primary field for artwork selection in NowplayingInfo/MediaSession, 'poster' in sourceDescription is now the fallback.
38
+ - Removed the play/pause icon in the PiP window on Android while playing an ad.
39
+
8
40
  ## [2.10.0] - 23-07-25
9
41
 
10
42
  ### Fixed
package/README.md CHANGED
@@ -55,14 +55,15 @@ React Native works to speed up the way of working with THEOplayer React Native S
55
55
  The `react-native-theoplayer` package can be combined with any number of connectors to provide extra
56
56
  functionality. Currently, the following connectors are available:
57
57
 
58
- | Package name | Purpose | Registry |
59
- |------------------------------------------------------------------------------------------------------------------|-------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
60
- | [`@theoplayer/react-native-analytics-adobe`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Adobe analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-adobe)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-adobe) |
61
- | [`@theoplayer/react-native-analytics-comscore`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Comscore analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-comscore)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-comscore) |
62
- | [`@theoplayer/react-native-analytics-conviva`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Conviva analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-conviva)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-conviva) |
63
- | [`@theoplayer/react-native-analytics-nielsen`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Nielsen analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-nielsen)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-nielsen) |
64
- | [`@theoplayer/react-native-drm`](https://github.com/THEOplayer/react-native-theoplayer-drm) | Content protection (DRM) connectors | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-drm)](https://www.npmjs.com/package/@theoplayer/react-native-drm) |
65
- | [`@theoplayer/react-native-ui`](https://github.com/THEOplayer/react-native-theoplayer-ui) | React Native user interface | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-ui)](https://www.npmjs.com/package/@theoplayer/react-native-ui) |
58
+ | Package name | Purpose | Registry |
59
+ |---------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
60
+ | [`@theoplayer/react-native-analytics-adobe`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Adobe analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-adobe)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-adobe) |
61
+ | [`@theoplayer/react-native-analytics-comscore`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Comscore analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-comscore)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-comscore) |
62
+ | [`@theoplayer/react-native-analytics-conviva`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Conviva analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-conviva)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-conviva) |
63
+ | [`@theoplayer/react-native-analytics-nielsen`](https://github.com/THEOplayer/react-native-theoplayer-analytics) | Nielsen analytics connector | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-analytics-nielsen)](https://www.npmjs.com/package/@theoplayer/react-native-analytics-nielsen) |
64
+ | [`@theoplayer/react-native-drm`](https://github.com/THEOplayer/react-native-theoplayer-drm) | Content protection (DRM) connectors | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-drm)](https://www.npmjs.com/package/@theoplayer/react-native-drm) |
65
+ | [`@theoplayer/react-native-ui`](https://github.com/THEOplayer/react-native-theoplayer-ui) | React Native user interface | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-ui)](https://www.npmjs.com/package/@theoplayer/react-native-ui) |
66
+ | [`@theoplayer/react-native-connector-template`](https://github.com/THEOplayer/react-native-theoplayer-connector-template) | A template for `react-native-theoplayer` connectors. | [![npm](https://img.shields.io/npm/v/@theoplayer/react-native-connector-template)](https://www.npmjs.com/package/@theoplayer/react-native-connector-template) |
66
67
 
67
68
  ## Getting Started
68
69
 
@@ -82,10 +83,12 @@ and discussed in the next section. Finally, an overview of features, limitations
82
83
  - Knowledge Base
83
84
  - [Adaptive Bitrate (ABR)](./doc/abr.md)
84
85
  - [Advertisements](./doc/ads.md)
86
+ - [Audio Control Management](./doc/audio-control.md)
85
87
  - [Background playback and notifications](./doc/background.md)
86
88
  - [Casting with Chromecast and Airplay](./doc/cast.md)
87
89
  - [Custom iOS framework](./doc/custom-ios-framework.md)
88
90
  - [Digital Rights Management (DRM)](./doc/drm.md)
89
- - [Picture-in-Picture (PiP)](./doc/pip.md)
90
91
  - [Migrating to `react-native-theoplayer` v2.x](./doc/migrating_v2.md)
92
+ - [Picture-in-Picture (PiP)](./doc/pip.md)
93
+ - [Styling subtitles and closed captions](./doc/texttrackstyles.md)
91
94
  - [Limitations and known issues](./doc/limitations.md)
@@ -494,7 +494,7 @@ class PlayerEventEmitter internal constructor(
494
494
  }
495
495
 
496
496
  private fun <T : Quality?> activeTrack(tracks: MediaTrackList<T>?): MediaTrack<T>? {
497
- return tracks?.first { track ->
497
+ return tracks?.firstOrNull { track ->
498
498
  track.isEnabled
499
499
  }
500
500
  }
@@ -27,6 +27,8 @@ import com.theoplayer.android.api.event.EventListener
27
27
  import com.theoplayer.android.api.event.player.*
28
28
  import com.theoplayer.android.api.player.Player
29
29
  import com.theoplayer.android.connector.mediasession.MediaSessionConnector
30
+ import com.theoplayer.audio.AudioBecomingNoisyManager
31
+ import com.theoplayer.audio.AudioFocusManager
30
32
  import com.theoplayer.audio.BackgroundAudioConfig
31
33
  import com.theoplayer.media.MediaPlaybackService
32
34
  import java.util.concurrent.atomic.AtomicBoolean
@@ -49,6 +51,11 @@ class ReactTHEOplayerContext private constructor(
49
51
  private var isBound = AtomicBoolean()
50
52
  private var binder: MediaPlaybackService.MediaPlaybackBinder? = null
51
53
  private var mediaSessionConnector: MediaSessionConnector? = null
54
+ private var audioBecomingNoisyManager = AudioBecomingNoisyManager(reactContext) {
55
+ // Audio is about to become 'noisy' due to a change in audio outputs: pause the player
56
+ player.pause()
57
+ }
58
+ private var audioFocusManager: AudioFocusManager? = null
52
59
 
53
60
  var backgroundAudioConfig: BackgroundAudioConfig = BackgroundAudioConfig(enabled = false)
54
61
  set(value) {
@@ -207,6 +214,8 @@ class ReactTHEOplayerContext private constructor(
207
214
  addIntegrations(playerConfig)
208
215
  addListeners()
209
216
 
217
+ audioFocusManager = AudioFocusManager(reactContext, player)
218
+
210
219
  if (!BuildConfig.USE_PLAYBACK_SERVICE || !isBackgroundAudioEnabled) {
211
220
  initDefaultMediaSession()
212
221
  }
@@ -282,11 +291,14 @@ class ReactTHEOplayerContext private constructor(
282
291
  }
283
292
  binder?.updateNotification(PlaybackStateCompat.STATE_PLAYING)
284
293
  applyAllowedMediaControls()
294
+ audioBecomingNoisyManager.setEnabled(true)
295
+ audioFocusManager?.retrieveAudioFocus()
285
296
  }
286
297
 
287
298
  private val onPause = EventListener<PauseEvent> {
288
299
  binder?.updateNotification(PlaybackStateCompat.STATE_PAUSED)
289
300
  applyAllowedMediaControls()
301
+ audioBecomingNoisyManager.setEnabled(false)
290
302
  }
291
303
 
292
304
  private fun addListeners() {
@@ -334,6 +346,7 @@ class ReactTHEOplayerContext private constructor(
334
346
  fun onHostResume() {
335
347
  mediaSessionConnector?.setActive(true)
336
348
  playerView.onResume()
349
+ audioFocusManager?.retrieveAudioFocus()
337
350
  }
338
351
 
339
352
  fun destroy() {
@@ -346,6 +359,7 @@ class ReactTHEOplayerContext private constructor(
346
359
  // Unbind client from background service so it can stop
347
360
  unbindMediaPlaybackService()
348
361
  }
362
+ audioFocusManager?.abandonAudioFocus()
349
363
  mediaSessionConnector?.destroy()
350
364
  playerView.onDestroy()
351
365
  }
@@ -130,9 +130,7 @@ class ReactTHEOplayerView(private val reactContext: ThemedReactContext) :
130
130
  try {
131
131
  val sourceDescription = SourceAdapter().parseSourceFromJS(source)
132
132
  adsApi.setSource(sourceDescription)
133
- if (sourceDescription != null) {
134
- player?.source = sourceDescription
135
- }
133
+ player?.source = sourceDescription
136
134
  } catch (exception: THEOplayerException) {
137
135
  Log.e(TAG, exception.message ?: "")
138
136
  eventEmitter.emitError(exception)
@@ -0,0 +1,145 @@
1
+ package com.theoplayer.audio
2
+
3
+ import android.app.UiModeManager
4
+ import android.content.Context
5
+ import android.content.res.Configuration
6
+ import android.media.AudioManager
7
+ import android.util.Log
8
+ import androidx.media.AudioAttributesCompat
9
+ import androidx.media.AudioFocusRequestCompat
10
+ import androidx.media.AudioManagerCompat
11
+ import com.theoplayer.BuildConfig
12
+ import com.theoplayer.android.api.player.Player
13
+
14
+ private const val TAG = "AudioFocusManager"
15
+
16
+ /**
17
+ * Manages audio focus for the application, ensuring proper handling of audio focus changes
18
+ * to control media playback behavior.
19
+ *
20
+ * @param context The context used to access system services.
21
+ * @param player The media player instance associated with this audio focus manager. It can be
22
+ * provided optionally to control playback behavior.
23
+ */
24
+ class AudioFocusManager(
25
+ context: Context,
26
+ private val player: Player? = null
27
+ ) : AudioManager.OnAudioFocusChangeListener {
28
+
29
+ private val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
30
+ private val uiModeManager = context.getSystemService(Context.UI_MODE_SERVICE) as? UiModeManager
31
+ private var resumeOnFocusGain = false
32
+ private val focusLock = Any()
33
+
34
+ private val audioFocusRequest = AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN)
35
+ .setAudioAttributes(
36
+ AudioAttributesCompat.Builder()
37
+ // Usage value to use when the usage is media, such as music, or movie soundtracks.
38
+ .setUsage(AudioAttributesCompat.USAGE_MEDIA)
39
+ // Content type value to use when the content type is a soundtrack, typically accompanying
40
+ // a movie or TV program.
41
+ .setContentType(AudioAttributesCompat.CONTENT_TYPE_MOVIE)
42
+ .build()
43
+ )
44
+ .setOnAudioFocusChangeListener(this)
45
+ .setWillPauseWhenDucked(true)
46
+ .build()
47
+
48
+ /**
49
+ * Called on the listener to notify it the audio focus for this listener has been changed.
50
+ */
51
+ override fun onAudioFocusChange(focusChange: Int) {
52
+ if (uiModeManager?.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) {
53
+ // Ignore changes in audioFocus for Connected TVs.
54
+ return
55
+ }
56
+ if (BuildConfig.DEBUG) {
57
+ Log.d(TAG, "onAudioFocusChange: ${fromAudioFocusChange(focusChange)}")
58
+ }
59
+ when (focusChange) {
60
+ // Used to indicate a gain of audio focus, or a request of audio focus, of unknown duration.
61
+ AudioManager.AUDIOFOCUS_GAIN -> {
62
+ if (resumeOnFocusGain) {
63
+ synchronized(focusLock) {
64
+ // Reset resume flag
65
+ resumeOnFocusGain = false
66
+ }
67
+ player?.play()
68
+ }
69
+ }
70
+
71
+ // Used to indicate a loss of audio focus of unknown duration.
72
+ AudioManager.AUDIOFOCUS_LOSS -> {
73
+ synchronized (focusLock) {
74
+ // This is not a transient loss, we shouldn't automatically resume for now
75
+ resumeOnFocusGain = false;
76
+ }
77
+ }
78
+
79
+ // Used to indicate a transient loss of audio focus.
80
+ AudioManager.AUDIOFOCUS_LOSS_TRANSIENT,
81
+
82
+ // Used to indicate a transient loss of audio focus where the loser of the audio focus can
83
+ // lower its output volume if it wants to continue playing (also referred to as "ducking"),
84
+ // as the new focus owner doesn't require others to be silent.
85
+ AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
86
+ synchronized (focusLock) {
87
+ // We should only resume if playback was interrupted
88
+ resumeOnFocusGain = !(player?.isPaused ?: true)
89
+ }
90
+ player?.pause()
91
+ }
92
+ }
93
+ }
94
+
95
+ private fun fromAudioFocusChange(focusChange: Int?): String {
96
+ return when (focusChange) {
97
+ AudioManager.AUDIOFOCUS_GAIN -> "AUDIOFOCUS_GAIN"
98
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT -> "AUDIOFOCUS_GAIN_TRANSIENT"
99
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE -> "AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE"
100
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK -> "AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK"
101
+ AudioManager.AUDIOFOCUS_LOSS -> "AUDIOFOCUS_LOSS"
102
+ AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> "AUDIOFOCUS_LOSS_TRANSIENT"
103
+ AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> "AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK"
104
+ else -> "AUDIOFOCUS_NONE"
105
+ }
106
+ }
107
+
108
+ private fun fromAudioFocusRequest(focusRequest: Int?): String {
109
+ return when(focusRequest) {
110
+ AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> "AUDIOFOCUS_REQUEST_GRANTED"
111
+ AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> "AUDIOFOCUS_REQUEST_DELAYED"
112
+ else -> "AUDIOFOCUS_REQUEST_FAILED"
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Send a request to obtain the audio focus
118
+ *
119
+ * @return True if audio focus is granted, false otherwise.
120
+ */
121
+ fun retrieveAudioFocus(): Boolean {
122
+ val result = audioManager?.let {
123
+ AudioManagerCompat.requestAudioFocus(it, audioFocusRequest)
124
+ }
125
+ if (BuildConfig.DEBUG) {
126
+ Log.d(TAG, "retrieveAudioFocus: ${fromAudioFocusRequest(result)}")
127
+ }
128
+ return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
129
+ }
130
+
131
+ /**
132
+ * Abandon audio focus. Causes the previous focus owner, if any, to receive focus.
133
+ */
134
+ fun abandonAudioFocus() {
135
+ synchronized (focusLock) {
136
+ resumeOnFocusGain = false
137
+ }
138
+ val result = audioManager?.let {
139
+ AudioManagerCompat.abandonAudioFocusRequest(it, audioFocusRequest)
140
+ }
141
+ if (BuildConfig.DEBUG) {
142
+ Log.d(TAG, "abandonAudioFocus: ${fromAudioFocusRequest(result)}")
143
+ }
144
+ }
145
+ }
@@ -111,7 +111,7 @@ class ContentProtectionModule(private val context: ReactApplicationContext) :
111
111
  }
112
112
  emit(
113
113
  EVENT_BUILD_INTEGRATION, payload,
114
- onResult = hashMapOf(),
114
+ onResult = hashMapOf(EVENT_BUILD_PROCESSED to { /*NoOp*/ }),
115
115
  onError = {}
116
116
  )
117
117
  }
@@ -12,6 +12,7 @@ import com.theoplayer.android.api.player.track.texttrack.TextTrackMode
12
12
  import com.theoplayer.audio.BackgroundAudioConfigAdapter
13
13
  import com.theoplayer.presentation.PipConfigAdapter
14
14
  import com.theoplayer.track.QualityListFilter
15
+ import com.theoplayer.track.TextTrackStyleAdapter
15
16
  import com.theoplayer.track.emptyQualityList
16
17
  import com.theoplayer.util.ViewResolver
17
18
 
@@ -188,4 +189,13 @@ class PlayerModule(context: ReactApplicationContext) : ReactContextBaseJavaModul
188
189
  })
189
190
  }
190
191
  }
192
+
193
+ @ReactMethod
194
+ fun setTextTrackStyle(tag: Int, style: ReadableMap?) {
195
+ viewResolver.resolveViewByTag(tag) { view: ReactTHEOplayerView? ->
196
+ view?.player?.let { player ->
197
+ TextTrackStyleAdapter.applyTextTrackStyle(player.textTrackStyle, style)
198
+ }
199
+ }
200
+ }
191
201
  }
@@ -35,6 +35,7 @@ private const val ACTION_RWD = ACTION_PLAY + 2
35
35
  private const val ACTION_FFD = ACTION_PLAY + 3
36
36
  private const val ACTION_IGNORE = ACTION_PLAY + 999
37
37
  private const val SKIP_TIME = 15
38
+ private const val NO_ICON = -1
38
39
 
39
40
  private val PIP_ASPECT_RATIO_DEFAULT = Rational(16, 9)
40
41
  private val PIP_ASPECT_RATIO_MIN = Rational(100, 239)
@@ -125,21 +126,24 @@ class PipUtils(
125
126
  }
126
127
 
127
128
  // Play/pause
128
- // Always add this button, but send an ACTION_IGNORE if disabled.
129
+ // Always add this button, but send an ACTION_IGNORE and make invisible if disabled.
130
+ // If no RemoteActions are added, MediaSession takes over the UI.
129
131
  add(
130
132
  if (paused) {
131
133
  buildRemoteAction(
132
- if (enablePlayPause) ACTION_PLAY else ACTION_IGNORE,
134
+ ACTION_PLAY,
133
135
  R.drawable.ic_play,
134
136
  R.string.play_pip,
135
- R.string.play_desc_pip
137
+ R.string.play_desc_pip,
138
+ enablePlayPause
136
139
  )
137
140
  } else {
138
141
  buildRemoteAction(
139
- if (enablePlayPause) ACTION_PAUSE else ACTION_IGNORE,
142
+ ACTION_PAUSE,
140
143
  R.drawable.ic_pause,
141
144
  R.string.pause_pip,
142
- R.string.pause_desc_pip
145
+ R.string.pause_desc_pip,
146
+ enablePlayPause
143
147
  )
144
148
  }
145
149
  )
@@ -235,12 +239,15 @@ class PipUtils(
235
239
  requestId: Int,
236
240
  iconId: Int,
237
241
  titleId: Int,
238
- descId: Int
242
+ descId: Int,
243
+ enabled: Boolean = true
239
244
  ): RemoteAction {
240
- val intent = Intent(ACTION_MEDIA_CONTROL).putExtra(EXTRA_ACTION, requestId)
245
+ // Ignore the action if it is disabled
246
+ val requestCode = if (enabled) requestId else ACTION_IGNORE
247
+ val intent = Intent(ACTION_MEDIA_CONTROL).putExtra(EXTRA_ACTION, requestCode)
241
248
  val pendingIntent =
242
- PendingIntent.getBroadcast(reactContext, requestId, intent, PendingIntent.FLAG_IMMUTABLE)
243
- val icon: Icon = Icon.createWithResource(reactContext, iconId)
249
+ PendingIntent.getBroadcast(reactContext, requestCode, intent, PendingIntent.FLAG_IMMUTABLE)
250
+ val icon: Icon = Icon.createWithResource(reactContext, if (enabled) iconId else NO_ICON)
244
251
  val title = reactContext.getString(titleId)
245
252
  val desc = reactContext.getString(descId)
246
253
  return RemoteAction(icon, title, desc, pendingIntent)
@@ -0,0 +1,79 @@
1
+ package com.theoplayer.track
2
+
3
+ import android.graphics.Color
4
+ import com.facebook.react.bridge.ReadableMap
5
+ import com.theoplayer.android.api.player.track.texttrack.TextTrackStyle
6
+
7
+ private val PROP_BACKGROUND_COLOR = "backgroundColor"
8
+ private val PROP_EDGE_STYLE = "edgeStyle"
9
+ private val PROP_FONT_COLOR = "fontColor"
10
+ private val PROP_FONT_FAMILY = "fontFamily"
11
+ private val PROP_FONT_SIZE = "fontSize"
12
+ private val PROP_WINDOW_COLOR = "windowColor"
13
+ private val PROP_MARGIN_LEFT = "marginLeft"
14
+ private val PROP_MARGIN_TOP = "marginTop"
15
+ private val PROP_COLOR_R = "r"
16
+ private val PROP_COLOR_G = "g"
17
+ private val PROP_COLOR_B = "b"
18
+ private val PROP_COLOR_A = "a"
19
+
20
+ object TextTrackStyleAdapter {
21
+
22
+ fun applyTextTrackStyle(style: TextTrackStyle, props: ReadableMap?) {
23
+ if (props == null) {
24
+ return
25
+ }
26
+ if (props.hasKey(PROP_BACKGROUND_COLOR)) {
27
+ style.backgroundColor =
28
+ colorFromBridgeColor(props.getMap(PROP_BACKGROUND_COLOR)) ?: Color.BLACK
29
+ }
30
+ if (props.hasKey(PROP_EDGE_STYLE)) {
31
+ style.edgeType = edgeStyleFromProps(props.getString(PROP_EDGE_STYLE))
32
+ }
33
+ if (props.hasKey(PROP_FONT_COLOR)) {
34
+ style.fontColor = colorFromBridgeColor(props.getMap(PROP_FONT_COLOR)) ?: Color.WHITE
35
+ }
36
+ if (props.hasKey(PROP_FONT_FAMILY)) {
37
+ val family = props.getString(PROP_FONT_FAMILY)
38
+ if (family != null) {
39
+ style.setFont(family, TextTrackStyle.FontStyle.NORMAL)
40
+ } else {
41
+ style.setFont(TextTrackStyle.FontFamily.DEFAULT, TextTrackStyle.FontStyle.NORMAL)
42
+ }
43
+ }
44
+ if (props.hasKey(PROP_FONT_SIZE)) {
45
+ style.fontSize = props.getInt(PROP_FONT_SIZE)
46
+ }
47
+ if (props.hasKey(PROP_WINDOW_COLOR)) {
48
+ style.windowColor = colorFromBridgeColor(props.getMap(PROP_WINDOW_COLOR)) ?: Color.TRANSPARENT
49
+ }
50
+ if (props.hasKey(PROP_MARGIN_TOP)) {
51
+ style.marginTop = props.getInt(PROP_MARGIN_TOP)
52
+ }
53
+ if (props.hasKey(PROP_MARGIN_LEFT)) {
54
+ style.marginLeft = props.getInt(PROP_MARGIN_LEFT)
55
+ }
56
+ }
57
+
58
+ private fun edgeStyleFromProps(style: String?): TextTrackStyle.EdgeType {
59
+ return when (style) {
60
+ "dropshadow" -> TextTrackStyle.EdgeType.EDGE_TYPE_DROP_SHADOW
61
+ "raised" -> TextTrackStyle.EdgeType.EDGE_TYPE_RAISED
62
+ "depressed" -> TextTrackStyle.EdgeType.EDGE_TYPE_DEPRESSED
63
+ "uniform" -> TextTrackStyle.EdgeType.EDGE_TYPE_OUTLINE
64
+ else -> TextTrackStyle.EdgeType.EDGE_TYPE_NONE
65
+ }
66
+ }
67
+
68
+ private fun colorFromBridgeColor(color: ReadableMap?): Int? {
69
+ if (color == null) {
70
+ return null
71
+ }
72
+ return Color.argb(
73
+ color.getInt(PROP_COLOR_A),
74
+ color.getInt(PROP_COLOR_R),
75
+ color.getInt(PROP_COLOR_G),
76
+ color.getInt(PROP_COLOR_B),
77
+ )
78
+ }
79
+ }
@@ -6,11 +6,16 @@ import com.facebook.react.uimanager.UIManagerModule
6
6
  import com.theoplayer.ReactTHEOplayerView
7
7
 
8
8
  private const val TAG = "ViewResolver"
9
+ private const val INVALID_TAG = -1
9
10
 
10
11
  class ViewResolver(private val reactContext: ReactApplicationContext) {
11
12
  private var uiManager: UIManagerModule? = null
12
13
 
13
14
  fun resolveViewByTag(tag: Int, onResolved: (view: ReactTHEOplayerView?) -> Unit) {
15
+ if (tag == INVALID_TAG) {
16
+ // Don't bother trying to resolve an invalid tag.
17
+ onResolved(null)
18
+ }
14
19
  if (uiManager == null) {
15
20
  uiManager = reactContext.getNativeModule(UIManagerModule::class.java)
16
21
  }
@@ -20,6 +25,7 @@ class ViewResolver(private val reactContext: ReactApplicationContext) {
20
25
  } catch (ignore: Exception) {
21
26
  // The ReactTHEOplayerView instance could not be resolved: log but do not forward exception.
22
27
  Log.w(TAG, "Failed to resolve ReactTHEOplayerView tag $tag")
28
+ onResolved(null)
23
29
  }
24
30
  }
25
31
  }
@@ -108,6 +108,9 @@ RCT_EXTERN_METHOD(setTargetVideoQuality:(nonnull NSNumber *)node
108
108
  RCT_EXTERN_METHOD(setPreload:(nonnull NSNumber *)node
109
109
  type:(nonnull NSString *)type)
110
110
 
111
+ RCT_EXTERN_METHOD(setTextTrackStyle:(nonnull NSNumber *)node
112
+ textTrackStyle:(NSDictionary)textTrackStyle)
113
+
111
114
  @end
112
115
 
113
116
  // ----------------------------------------------------------------------------
@@ -6,10 +6,26 @@ import Foundation
6
6
  import UIKit
7
7
  import THEOplayerSDK
8
8
 
9
+ #if canImport(THEOplayerConnectorSideloadedSubtitle)
10
+ import THEOplayerConnectorSideloadedSubtitle
11
+ #endif
12
+
9
13
  let ERROR_MESSAGE_PLAYER_ABR_UNSUPPORTED_FEATURE: String = "Setting an ABRconfig is not supported on iOS/tvOS."
10
14
  let ERROR_MESSAGE_PLAYER_QUALITY_UNSUPPORTED_FEATURE: String = "Setting a target video quality is not supported on iOS/tvOS."
11
15
  let ERROR_MESSAGE_PLAYER_FULLSCREEN_UNSUPPORTED_FEATURE: String = "Fullscreen presentationmode should be implemented on the RN level for iOS/tvOS."
12
16
 
17
+ let TTS_PROP_BACKGROUND_COLOR = "backgroundColor"
18
+ let TTS_PROP_EDGE_STYLE = "edgeStyle"
19
+ let TTS_PROP_FONT_COLOR = "fontColor"
20
+ let TTS_PROP_FONT_FAMILY = "fontFamily"
21
+ let TTS_PROP_FONT_SIZE = "fontSize"
22
+ let TTS_PROP_MARGIN_LEFT = "marginLeft"
23
+ let TTS_PROP_MARGIN_TOP = "marginTop"
24
+ let TTS_PROP_COLOR_R = "r"
25
+ let TTS_PROP_COLOR_G = "g"
26
+ let TTS_PROP_COLOR_B = "b"
27
+ let TTS_PROP_COLOR_A = "a"
28
+
13
29
  @objc(THEOplayerRCTPlayerAPI)
14
30
  class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
15
31
  @objc var bridge: RCTBridge!
@@ -44,8 +60,7 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
44
60
  if let theView = self.bridge.uiManager.view(forReactTag: node) as? THEOplayerRCTView,
45
61
  let srcDescription = THEOplayerRCTSourceDescriptionBuilder.buildSourceDescription(src) {
46
62
  if let player = theView.player {
47
- if DEBUG_PLAYER_API { PrintUtils.printLog(logText: "[NATIVE] Setting new source on TheoPlayer") }
48
- player.source = srcDescription
63
+ self.setNewSourceDescription(player: player, srcDescription: srcDescription)
49
64
  }
50
65
  } else {
51
66
  if DEBUG_PLAYER_API { PrintUtils.printLog(logText: "[NATIVE] Failed to update THEOplayer source.") }
@@ -53,6 +68,15 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
53
68
  }
54
69
  }
55
70
 
71
+ private func setNewSourceDescription(player: THEOplayer, srcDescription: SourceDescription) {
72
+ if DEBUG_PLAYER_API { PrintUtils.printLog(logText: "[NATIVE] Setting new source on TheoPlayer") }
73
+ #if canImport(THEOplayerConnectorSideloadedSubtitle)
74
+ player.setSourceWithSubtitles(source: srcDescription)
75
+ #else
76
+ player.source = srcDescription
77
+ #endif
78
+ }
79
+
56
80
  @objc(setABRConfig:abrConfig:)
57
81
  func setABRConfig(_ node: NSNumber, setABRConfig: NSDictionary) -> Void {
58
82
  if DEBUG_PLAYER_API { print(ERROR_MESSAGE_PLAYER_ABR_UNSUPPORTED_FEATURE) }
@@ -251,4 +275,44 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
251
275
  }
252
276
  }
253
277
 
278
+ @objc(setTextTrackStyle:textTrackStyle:)
279
+ func setTextTrackStyle(_ node: NSNumber, textTrackStyle: NSDictionary) -> Void {
280
+ DispatchQueue.main.async {
281
+ if let theView = self.bridge.uiManager.view(forReactTag: node) as? THEOplayerRCTView,
282
+ let player = theView.player {
283
+ if let bgColorMap = textTrackStyle[TTS_PROP_BACKGROUND_COLOR] as? [String:Any],
284
+ let r = bgColorMap[TTS_PROP_COLOR_R] as? Int,
285
+ let g = bgColorMap[TTS_PROP_COLOR_G] as? Int,
286
+ let b = bgColorMap[TTS_PROP_COLOR_B] as? Int,
287
+ let a = bgColorMap[TTS_PROP_COLOR_A] as? Int {
288
+ let bgColor = UIColor(red: CGFloat(r)/255.0, green: CGFloat(g)/255.0, blue: CGFloat(b)/255.0, alpha: CGFloat(a)/255.0)
289
+ player.textTrackStyle?.backgroundColor = [TextTrackStyleRuleColor(bgColor)]
290
+ }
291
+ if let fontColorMap = textTrackStyle[TTS_PROP_FONT_COLOR] as? [String:Any],
292
+ let r = fontColorMap[TTS_PROP_COLOR_R] as? Int,
293
+ let g = fontColorMap[TTS_PROP_COLOR_G] as? Int,
294
+ let b = fontColorMap[TTS_PROP_COLOR_B] as? Int,
295
+ let a = fontColorMap[TTS_PROP_COLOR_A] as? Int {
296
+ let fontColor = UIColor(red: CGFloat(r)/255.0, green: CGFloat(g)/255.0, blue: CGFloat(b)/255.0, alpha: CGFloat(a)/255.0)
297
+ player.textTrackStyle?.fontColor = [TextTrackStyleRuleColor(fontColor)]
298
+ }
299
+ if let edgeStyle = textTrackStyle[TTS_PROP_EDGE_STYLE] as? String {
300
+ player.textTrackStyle?.edgeStyle = [TextTrackStyleRuleString(THEOplayerRCTTypeUtils.textTrackEdgeStyleStringFromString(edgeStyle))]
301
+ }
302
+ if let fontFamily = textTrackStyle[TTS_PROP_FONT_FAMILY] as? String {
303
+ player.textTrackStyle?.fontFamily = [TextTrackStyleRuleString(fontFamily)]
304
+ }
305
+ if let fontSize = textTrackStyle[TTS_PROP_FONT_SIZE] as? Int {
306
+ player.textTrackStyle?.fontSize = [TextTrackStyleRuleNumber(fontSize)]
307
+ }
308
+ if let marginTop = textTrackStyle[TTS_PROP_MARGIN_TOP] as? Int {
309
+ player.textTrackStyle?.marginTop = [TextTrackStyleRuleNumber(marginTop)]
310
+ }
311
+ if let marginLeft = textTrackStyle[TTS_PROP_MARGIN_LEFT] as? Int {
312
+ player.textTrackStyle?.marginLeft = [TextTrackStyleRuleNumber(marginLeft)]
313
+ }
314
+ }
315
+ }
316
+ }
317
+
254
318
  }
@@ -375,6 +375,8 @@ class THEOplayerRCTSourceDescriptionBuilder {
375
375
 
376
376
  if format == "webvtt" {
377
377
  return THEOplayerSDK.TextTrackFormat.WebVTT
378
+ } else if format == "srt" {
379
+ return THEOplayerSDK.TextTrackFormat.SRT
378
380
  } else {
379
381
  return THEOplayerSDK.TextTrackFormat.none
380
382
  }