react-native-spalla-player 0.16.0 → 0.16.2

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.
@@ -9,7 +9,10 @@ import com.spallaplayer.components.RNGoogleCastButtonManager
9
9
 
10
10
  class SpallaPlayerPackage : ReactPackage {
11
11
  override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
12
- return listOf(SpallaPlayerModule(reactContext))
12
+ return listOf(
13
+ SpallaPlayerModule(reactContext),
14
+ SpallaPlayerPipModule(reactContext)
15
+ )
13
16
  }
14
17
 
15
18
  override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
@@ -0,0 +1,32 @@
1
+ package com.spallaplayer
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
5
+ import com.facebook.react.bridge.ReactMethod
6
+ import com.facebook.react.module.annotations.ReactModule
7
+
8
+ @ReactModule(name = SpallaPlayerPipModule.NAME)
9
+ class SpallaPlayerPipModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
10
+
11
+ companion object {
12
+ const val NAME = "SpallaPlayerPipModule"
13
+ private var activePlayerManager: RNSpallaPlayerManager? = null
14
+
15
+ fun registerPlayerManager(manager: RNSpallaPlayerManager) {
16
+ activePlayerManager = manager
17
+ }
18
+
19
+ fun unregisterPlayerManager(manager: RNSpallaPlayerManager) {
20
+ if (activePlayerManager == manager) {
21
+ activePlayerManager = null
22
+ }
23
+ }
24
+ }
25
+
26
+ override fun getName(): String = NAME
27
+
28
+ @ReactMethod
29
+ fun onUserLeaveHint() {
30
+ activePlayerManager?.triggerPipImmediate()
31
+ }
32
+ }
@@ -3,7 +3,6 @@ package com.spallaplayer
3
3
  import android.util.Log
4
4
  import android.view.View
5
5
  import androidx.appcompat.app.AppCompatActivity
6
- import androidx.core.app.ComponentActivity
7
6
  import androidx.core.app.PictureInPictureModeChangedInfo
8
7
  import androidx.core.util.Consumer
9
8
  import com.facebook.react.bridge.Arguments
@@ -23,26 +22,30 @@ import java.util.Timer
23
22
  import java.util.TimerTask
24
23
 
25
24
  class RNSpallaPlayerManager() : ViewGroupManager<SpallaPlayerContainerView>(),
26
- SpallaPlayerListener, SpallaPlayerFullScreenListener, LifecycleEventListener {
27
- private var _playerView: SpallaPlayerView? = null
28
- private var _reactContext: ReactContext? = null
29
- private var _container: SpallaPlayerContainerView? = null
30
-
31
- private var contentId: String? = null
32
- private var startTime: Double? = null
33
- private var subtitle: String? = null
34
- private var loadTimer: Timer? = null
35
- private var playbackRate: Double = 1.0
36
- private var hideUI: Boolean? = null
37
- private var isPlaying: Boolean = false
38
-
39
- override fun getName() = "RNSpallaPlayer"
25
+ SpallaPlayerListener, SpallaPlayerFullScreenListener {
26
+ private var _playerView: SpallaPlayerView? = null
27
+ private var _reactContext: ReactContext? = null
28
+ private var _container: SpallaPlayerContainerView? = null
29
+
30
+ private var contentId: String? = null
31
+ private var startTime: Double? = null
32
+ private var subtitle: String? = null
33
+ private var loadTimer: Timer? = null
34
+ private var playbackRate: Double = 1.0
35
+ private var hideUI: Boolean? = null
36
+ private var isPlaying: Boolean = false
37
+ private var pipTriggered: Boolean = false
38
+
39
+ override fun getName() = "RNSpallaPlayer"
40
40
 
41
41
  private val pipModeListener = Consumer<PictureInPictureModeChangedInfo> { info ->
42
42
  val map: WritableMap = Arguments.createMap()
43
43
  map.putString("event", if (info.isInPictureInPictureMode) "enterPiP" else "exitPiP")
44
44
  map.putBoolean("isInPictureInPictureMode", info.isInPictureInPictureMode)
45
45
 
46
+ // Reset the flag when PiP mode changes
47
+ pipTriggered = false
48
+
46
49
  _container?.let { container ->
47
50
  _reactContext?.getJSModule(RCTEventEmitter::class.java)?.receiveEvent(
48
51
  container.id,
@@ -53,15 +56,16 @@ class RNSpallaPlayerManager() : ViewGroupManager<SpallaPlayerContainerView>(),
53
56
  _reactContext?.currentActivity?.let { activity ->
54
57
  _playerView?.onPictureInPictureModeChanged(activity, info.isInPictureInPictureMode)
55
58
  }
56
-
57
-
58
59
  }
59
60
 
60
61
  override fun createViewInstance(context: ThemedReactContext): SpallaPlayerContainerView {
61
62
  _reactContext = context
62
- context.addLifecycleEventListener(this)
63
+ //context.addLifecycleEventListener(this)
64
+
65
+ // Register this manager for direct PiP access
66
+ SpallaPlayerPipModule.registerPlayerManager(this)
63
67
 
64
- if(_reactContext?.currentActivity is AppCompatActivity) {
68
+ if (_reactContext?.currentActivity is AppCompatActivity) {
65
69
  val activity = _reactContext?.currentActivity as? AppCompatActivity
66
70
  activity?.addOnPictureInPictureModeChangedListener(pipModeListener)
67
71
  }
@@ -75,242 +79,241 @@ class RNSpallaPlayerManager() : ViewGroupManager<SpallaPlayerContainerView>(),
75
79
  return container
76
80
  }
77
81
 
78
- override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any> {
79
- val eventMap = mutableMapOf<String, Any>()
82
+ override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any> {
83
+ val eventMap = mutableMapOf<String, Any>()
80
84
 
81
- eventMap["onPlayerEvent"] = mapOf(
82
- "phasedRegistrationNames" to mapOf(
83
- "bubbled" to "onPlayerEvent"
84
- )
85
- )
85
+ eventMap["onPlayerEvent"] = mapOf(
86
+ "phasedRegistrationNames" to mapOf(
87
+ "bubbled" to "onPlayerEvent"
88
+ )
89
+ )
86
90
 
87
- return eventMap
88
- }
91
+ return eventMap
92
+ }
89
93
 
90
- override fun onDropViewInstance(view: SpallaPlayerContainerView) {
91
- Log.v("RNSpallaPlayerManager", "onDropViewInstance")
92
- _reactContext?.removeLifecycleEventListener(this)
94
+ override fun onDropViewInstance(view: SpallaPlayerContainerView) {
95
+ Log.v("RNSpallaPlayerManager", "onDropViewInstance")
96
+ //_reactContext?.removeLifecycleEventListener(this)
93
97
 
94
- view.post {
95
- try {
96
- loadTimer?.cancel()
97
- view.spallaPlayerView.onDestroy()
98
- } catch (e: Exception) {
99
- e.printStackTrace()
100
- }
101
- }
102
- super.onDropViewInstance(view)
103
- }
98
+ // Unregister this manager
99
+ SpallaPlayerPipModule.unregisterPlayerManager(this)
104
100
 
105
- override fun addView(parent: SpallaPlayerContainerView, child: View, index: Int) {
106
- parent.addView(child, index)
101
+ view.post {
102
+ try {
103
+ loadTimer?.cancel()
104
+ view.spallaPlayerView.onDestroy()
105
+ } catch (e: Exception) {
106
+ e.printStackTrace()
107
+ }
107
108
  }
109
+ super.onDropViewInstance(view)
110
+ }
108
111
 
109
- override fun removeViewAt(parent: SpallaPlayerContainerView, index: Int) {
110
- parent.removeViewAt(index)
111
- }
112
+ override fun addView(parent: SpallaPlayerContainerView, child: View, index: Int) {
113
+ parent.addView(child, index)
114
+ }
112
115
 
113
- override fun getChildCount(parent: SpallaPlayerContainerView): Int {
114
- return parent.childCount
115
- }
116
+ override fun removeViewAt(parent: SpallaPlayerContainerView, index: Int) {
117
+ parent.removeViewAt(index)
118
+ }
116
119
 
117
- override fun getChildAt(parent: SpallaPlayerContainerView, index: Int): View {
118
- return parent.getChildAt(index)
119
- }
120
+ override fun getChildCount(parent: SpallaPlayerContainerView): Int {
121
+ return parent.childCount
122
+ }
120
123
 
121
- @ReactProp(name = "contentId")
122
- fun setContentId(view: SpallaPlayerContainerView, contentId: String) {
123
- this.contentId = contentId
124
- //delay initialization for a bit
125
- loadTimer?.cancel()
126
- loadTimer = Timer()
127
- loadTimer?.schedule(object : TimerTask() {
128
- override fun run() {
129
- checkAndLoadPlayer(view)
130
- }
131
- }, 100)
132
- }
124
+ override fun getChildAt(parent: SpallaPlayerContainerView, index: Int): View {
125
+ return parent.getChildAt(index)
126
+ }
133
127
 
134
- @ReactProp(name = "muted")
135
- fun setMuted(view: SpallaPlayerContainerView, muted: Boolean) {
136
- _playerView?.setMuted(muted)
137
- }
128
+ @ReactProp(name = "contentId")
129
+ fun setContentId(view: SpallaPlayerContainerView, contentId: String) {
130
+ this.contentId = contentId
131
+ //delay initialization for a bit
132
+ loadTimer?.cancel()
133
+ loadTimer = Timer()
134
+ loadTimer?.schedule(object : TimerTask() {
135
+ override fun run() {
136
+ checkAndLoadPlayer(view)
137
+ }
138
+ }, 100)
139
+ }
138
140
 
139
- @ReactProp(name = "startTime")
140
- fun setStartTime(view: SpallaPlayerContainerView, startTime: Double) {
141
- this.startTime = startTime
142
- //checkAndLoadPlayer(view)
143
- }
141
+ @ReactProp(name = "muted")
142
+ fun setMuted(view: SpallaPlayerContainerView, muted: Boolean) {
143
+ _playerView?.setMuted(muted)
144
+ }
144
145
 
145
- @ReactProp(name = "subtitle")
146
- fun setSubtitle(view: SpallaPlayerContainerView, subtitle: String?) {
147
- this.subtitle = subtitle
148
- _playerView?.selectSubtitle(subtitle)
149
- }
146
+ @ReactProp(name = "startTime")
147
+ fun setStartTime(view: SpallaPlayerContainerView, startTime: Double) {
148
+ this.startTime = startTime
149
+ //checkAndLoadPlayer(view)
150
+ }
150
151
 
151
- @ReactProp(name = "playbackRate")
152
- fun setPlaybackRate(view: SpallaPlayerContainerView, playbackRate: Double) {
153
- if (playbackRate > 0) {
154
- this.playbackRate = playbackRate
155
- _playerView?.selectPlaybackRate(playbackRate)
156
- }
157
- }
152
+ @ReactProp(name = "subtitle")
153
+ fun setSubtitle(view: SpallaPlayerContainerView, subtitle: String?) {
154
+ this.subtitle = subtitle
155
+ _playerView?.selectSubtitle(subtitle)
156
+ }
158
157
 
159
- @ReactProp(name = "hideUI")
160
- fun setHideUI(view: SpallaPlayerContainerView, hideUI: Boolean) {
161
- this.hideUI = hideUI
158
+ @ReactProp(name = "playbackRate")
159
+ fun setPlaybackRate(view: SpallaPlayerContainerView, playbackRate: Double) {
160
+ if (playbackRate > 0) {
161
+ this.playbackRate = playbackRate
162
+ _playerView?.selectPlaybackRate(playbackRate)
162
163
  }
164
+ }
163
165
 
164
- private fun checkAndLoadPlayer(view: SpallaPlayerContainerView) {
165
- if (contentId != null && startTime != null && hideUI != null) {
166
- view.post {
167
- view.spallaPlayerView.load(contentId!!, true, startTime!!, subtitle, hideUI!!)
168
- }
169
- }
166
+ @ReactProp(name = "hideUI")
167
+ fun setHideUI(view: SpallaPlayerContainerView, hideUI: Boolean) {
168
+ this.hideUI = hideUI
169
+ }
170
+
171
+ private fun checkAndLoadPlayer(view: SpallaPlayerContainerView) {
172
+ if (contentId != null && startTime != null && hideUI != null) {
173
+ view.post {
174
+ view.spallaPlayerView.load(contentId!!, true, startTime!!, subtitle, hideUI!!)
175
+ }
170
176
  }
177
+ }
171
178
 
172
- override fun onEvent(event: SpallaPlayerEvent) {
173
- val map: WritableMap = Arguments.createMap()
174
-
175
- //Log.v("RNSpallaPlayerManager", "onEvent: $event")
176
- when (event) {
177
- is DurationUpdate -> {
178
- map.putString("event", "durationUpdate")
179
- map.putDouble("duration", event.duration)
180
- requestLayout()
181
- }
182
-
183
- Ended -> {
184
- map.putString("event", "ended")
185
- isPlaying = false
186
- }
187
- is Error -> {
188
- map.putString("event", "error")
189
- map.putString("message", event.message)
190
- isPlaying = false
191
- }
192
-
193
- Pause -> {
194
- map.putString("event", "pause")
195
- isPlaying = false
196
- }
197
- Play -> {
198
- map.putString("event", "play")
199
- isPlaying = false
200
- }
201
- Playing -> {
202
- map.putString("event", "playing")
203
- isPlaying = true
204
- }
205
- is TimeUpdate -> {
206
- map.putString("event", "timeUpdate")
207
- map.putDouble("time", event.currentTime)
208
- }
209
-
210
- Waiting -> map.putString("event", "buffering")
211
- is SubtitlesAvailable -> {
212
- map.putString("event", "subtitlesAvailable")
213
-
214
- var subs = Arguments.createArray()
215
- event.subtitles.forEach {
216
- subs.pushString(it)
217
- }
218
- map.putArray("subtitles", subs)
219
- }
220
-
221
- is SubtitleSelected -> {
222
- map.putString("event", "subtitleSelected")
223
- map.putString("subtitle", event.subtitle)
224
- }
225
-
226
- is MetadataLoaded -> {
227
- map.putString("event", "metadataLoaded")
228
- map.putBoolean("isLive", event.metadata.isLive)
229
- map.putDouble("duration", event.metadata.duration)
230
-
231
- // make sure current playback rate is applied if set initially
232
- _playerView?.selectPlaybackRate(playbackRate)
233
- requestLayout()
234
- }
235
-
236
- is PlaybackRateSelected -> {
237
- map.putString("event", "playbackRateSelected")
238
- map.putDouble("rate", event.rate)
239
- requestLayout()
240
- }
179
+ override fun onEvent(event: SpallaPlayerEvent) {
180
+ val map: WritableMap = Arguments.createMap()
241
181
 
242
- }
243
- _container?.let { container ->
244
- _reactContext?.getJSModule(RCTEventEmitter::class.java)?.receiveEvent(
245
- container.id,
246
- "onPlayerEvent",
247
- map
248
- )
249
- }
250
- }
182
+ //Log.v("RNSpallaPlayerManager", "onEvent: $event")
183
+ when (event) {
184
+ is DurationUpdate -> {
185
+ map.putString("event", "durationUpdate")
186
+ map.putDouble("duration", event.duration)
187
+ requestLayout()
188
+ }
251
189
 
252
- override fun onEnterFullScreen() {
190
+ Ended -> {
191
+ map.putString("event", "ended")
192
+ isPlaying = false
193
+ }
253
194
 
254
- val map: WritableMap = Arguments.createMap()
255
- map.putString("event", "enterFullScreen")
256
- _container?.let { container ->
257
- _reactContext?.getJSModule(RCTEventEmitter::class.java)?.receiveEvent(
258
- container.id,
259
- "onPlayerEvent",
260
- map
261
- )
262
- }
263
- }
195
+ is Error -> {
196
+ map.putString("event", "error")
197
+ map.putString("message", event.message)
198
+ isPlaying = false
199
+ }
264
200
 
265
- override fun onExitFullScreen() {
266
- val map: WritableMap = Arguments.createMap()
267
- map.putString("event", "exitFullScreen")
268
- _container?.let { container ->
269
- _reactContext?.getJSModule(RCTEventEmitter::class.java)?.receiveEvent(
270
- container.id,
271
- "onPlayerEvent",
272
- map
273
- )
201
+ Pause -> {
202
+ map.putString("event", "pause")
203
+ isPlaying = false
204
+ }
205
+
206
+ Play -> {
207
+ map.putString("event", "play")
208
+ isPlaying = false
209
+ }
210
+
211
+ Playing -> {
212
+ map.putString("event", "playing")
213
+ isPlaying = true
214
+ }
215
+
216
+ is TimeUpdate -> {
217
+ map.putString("event", "timeUpdate")
218
+ map.putDouble("time", event.currentTime)
219
+ }
220
+
221
+ Waiting -> map.putString("event", "buffering")
222
+ is SubtitlesAvailable -> {
223
+ map.putString("event", "subtitlesAvailable")
224
+
225
+ var subs = Arguments.createArray()
226
+ event.subtitles.forEach {
227
+ subs.pushString(it)
274
228
  }
229
+ map.putArray("subtitles", subs)
230
+ }
231
+
232
+ is SubtitleSelected -> {
233
+ map.putString("event", "subtitleSelected")
234
+ map.putString("subtitle", event.subtitle)
235
+ }
236
+
237
+ is MetadataLoaded -> {
238
+ map.putString("event", "metadataLoaded")
239
+ map.putBoolean("isLive", event.metadata.isLive)
240
+ map.putDouble("duration", event.metadata.duration)
241
+
242
+ // make sure current playback rate is applied if set initially
243
+ _playerView?.selectPlaybackRate(playbackRate)
244
+ requestLayout()
245
+ }
246
+
247
+ is PlaybackRateSelected -> {
248
+ map.putString("event", "playbackRateSelected")
249
+ map.putDouble("rate", event.rate)
250
+ requestLayout()
251
+ }
252
+
253
+ }
254
+ _container?.let { container ->
255
+ _reactContext?.getJSModule(RCTEventEmitter::class.java)?.receiveEvent(
256
+ container.id,
257
+ "onPlayerEvent",
258
+ map
259
+ )
275
260
  }
261
+ }
276
262
 
277
- fun requestLayout() {
278
- _playerView?.post(measureAndLayout)
263
+ override fun onEnterFullScreen() {
264
+
265
+ val map: WritableMap = Arguments.createMap()
266
+ map.putString("event", "enterFullScreen")
267
+ _container?.let { container ->
268
+ _reactContext?.getJSModule(RCTEventEmitter::class.java)?.receiveEvent(
269
+ container.id,
270
+ "onPlayerEvent",
271
+ map
272
+ )
279
273
  }
274
+ }
280
275
 
281
- private val measureAndLayout = Runnable {
282
- _playerView?.let {
283
- it.measure(
284
- View.MeasureSpec.makeMeasureSpec(it.width, View.MeasureSpec.EXACTLY),
285
- View.MeasureSpec.makeMeasureSpec(it.height, View.MeasureSpec.EXACTLY)
286
- )
287
- it.layout(
288
- it.left,
289
- it.top,
290
- it.right,
291
- it.bottom
292
- )
293
- }
276
+ override fun onExitFullScreen() {
277
+ val map: WritableMap = Arguments.createMap()
278
+ map.putString("event", "exitFullScreen")
279
+ _container?.let { container ->
280
+ _reactContext?.getJSModule(RCTEventEmitter::class.java)?.receiveEvent(
281
+ container.id,
282
+ "onPlayerEvent",
283
+ map
284
+ )
294
285
  }
286
+ }
295
287
 
296
- //lifecycle events
297
- override fun onHostResume() {
298
- _playerView?.let { player ->
288
+ fun requestLayout() {
289
+ _playerView?.post(measureAndLayout)
290
+ }
299
291
 
292
+ private val measureAndLayout = Runnable {
293
+ _playerView?.let {
294
+ it.measure(
295
+ View.MeasureSpec.makeMeasureSpec(it.width, View.MeasureSpec.EXACTLY),
296
+ View.MeasureSpec.makeMeasureSpec(it.height, View.MeasureSpec.EXACTLY)
297
+ )
298
+ it.layout(
299
+ it.left,
300
+ it.top,
301
+ it.right,
302
+ it.bottom
303
+ )
300
304
  }
301
305
  }
302
306
 
303
- override fun onHostPause() {
307
+ fun triggerPipImmediate() {
308
+ if (pipTriggered) return
309
+
304
310
  _playerView?.let { player ->
305
311
  val activity = _reactContext?.currentActivity
306
312
  if (activity != null && isPlaying) {
313
+ pipTriggered = true
307
314
  player.enterPictureInPictureMode(activity = activity)
308
315
  }
309
316
  }
310
317
  }
311
318
 
312
- override fun onHostDestroy() {
313
- _reactContext?.removeLifecycleEventListener(this)
314
- }
315
-
316
319
  }
package/app.plugin.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./plugin/withPipAndroid');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-spalla-player",
3
- "version": "0.16.0",
3
+ "version": "0.16.2",
4
4
  "description": "Spalla SDK for RN",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/commonjs/index.js",
@@ -15,7 +15,8 @@
15
15
  "types": "./lib/typescript/commonjs/src/index.d.ts",
16
16
  "default": "./lib/commonjs/index.js"
17
17
  }
18
- }
18
+ },
19
+ "./app.plugin.js": "./app.plugin.js"
19
20
  },
20
21
  "files": [
21
22
  "src",
@@ -23,6 +24,8 @@
23
24
  "android",
24
25
  "ios",
25
26
  "cpp",
27
+ "plugin",
28
+ "app.plugin.js",
26
29
  "*.podspec",
27
30
  "!ios/build",
28
31
  "!android/build",
@@ -79,6 +82,7 @@
79
82
  "react": "18.3.1",
80
83
  "react-native": "0.75.4",
81
84
  "react-native-builder-bob": "^0.30.2",
85
+ "react-native-safe-area-context": "^5.4.1",
82
86
  "release-it": "^15.0.0",
83
87
  "turbo": "^1.10.7",
84
88
  "typescript": "^5.2.2"
@@ -88,7 +92,8 @@
88
92
  },
89
93
  "peerDependencies": {
90
94
  "react": "*",
91
- "react-native": "*"
95
+ "react-native": "*",
96
+ "react-native-safe-area-context": "*"
92
97
  },
93
98
  "workspaces": [
94
99
  "example"
@@ -183,5 +188,11 @@
183
188
  "type": "view-legacy",
184
189
  "languages": "kotlin-swift",
185
190
  "version": "0.41.2"
191
+ },
192
+ "expo": {
193
+ "plugin": "./app.plugin.js"
194
+ },
195
+ "dependencies": {
196
+ "@expo/config-plugins": "^54.0.4"
186
197
  }
187
198
  }
@@ -0,0 +1,2 @@
1
+ module.exports = require('./withPipAndroid');
2
+ module.exports.default = module.exports;
@@ -0,0 +1,70 @@
1
+ const {
2
+ withAndroidManifest,
3
+ withMainActivity,
4
+ } = require('@expo/config-plugins');
5
+
6
+ /**
7
+ * Adds supportsPictureInPicture="true" to MainActivity
8
+ */
9
+ function withPipManifest(config) {
10
+ return withAndroidManifest(config, (config) => {
11
+ const manifest = config.modResults.manifest;
12
+ const app = manifest.application?.[0];
13
+
14
+ if (!app?.activity) return config;
15
+
16
+ const mainActivity = app.activity.find(
17
+ (activity) => activity.$?.['android:name'] === '.MainActivity'
18
+ );
19
+
20
+ if (!mainActivity) return config;
21
+
22
+ mainActivity.$['android:supportsPictureInPicture'] = 'true';
23
+ return config;
24
+ });
25
+ }
26
+
27
+ /**
28
+ * Injects onUserLeaveHint() into MainActivity.kt
29
+ */
30
+ function withPipMainActivity(config) {
31
+ return withMainActivity(config, (config) => {
32
+ let contents = config.modResults.contents;
33
+
34
+ if (contents.includes('override fun onUserLeaveHint()')) {
35
+ return config;
36
+ }
37
+
38
+ // Add necessary imports
39
+ if (!contents.includes('import com.spallaplayer.SpallaPlayerPipModule')) {
40
+ contents = contents.replace(
41
+ /import\s+android\.os\.Bundle/,
42
+ `import android.os.Bundle
43
+ import com.spallaplayer.SpallaPlayerPipModule`
44
+ );
45
+ }
46
+
47
+ const method = `
48
+ override fun onUserLeaveHint() {
49
+ super.onUserLeaveHint()
50
+ reactNativeHost.reactInstanceManager.currentReactContext?.let { context ->
51
+ context.getNativeModule(SpallaPlayerPipModule::class.java)?.onUserLeaveHint()
52
+ }
53
+ }
54
+ `;
55
+
56
+ contents = contents.replace(
57
+ /class MainActivity[^{]*\{/,
58
+ (match) => `${match}${method}`
59
+ );
60
+
61
+ config.modResults.contents = contents;
62
+ return config;
63
+ });
64
+ }
65
+
66
+ module.exports = function withPipAndroid(config) {
67
+ config = withPipManifest(config);
68
+ config = withPipMainActivity(config);
69
+ return config;
70
+ };