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.
- package/android/src/main/java/com/spallaplayer/SpallaPlayerPackage.kt +4 -1
- package/android/src/main/java/com/spallaplayer/SpallaPlayerPipModule.kt +32 -0
- package/android/src/main/java/com/spallaplayer/SpallaPlayerViewManager.kt +218 -215
- package/app.plugin.js +1 -0
- package/package.json +14 -3
- package/plugin/index.js +2 -0
- package/plugin/withPipAndroid.js +70 -0
|
@@ -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(
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
79
|
-
|
|
82
|
+
override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any> {
|
|
83
|
+
val eventMap = mutableMapOf<String, Any>()
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
eventMap["onPlayerEvent"] = mapOf(
|
|
86
|
+
"phasedRegistrationNames" to mapOf(
|
|
87
|
+
"bubbled" to "onPlayerEvent"
|
|
88
|
+
)
|
|
89
|
+
)
|
|
86
90
|
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
return eventMap
|
|
92
|
+
}
|
|
89
93
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
override fun onDropViewInstance(view: SpallaPlayerContainerView) {
|
|
95
|
+
Log.v("RNSpallaPlayerManager", "onDropViewInstance")
|
|
96
|
+
//_reactContext?.removeLifecycleEventListener(this)
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
override fun addView(parent: SpallaPlayerContainerView, child: View, index: Int) {
|
|
113
|
+
parent.addView(child, index)
|
|
114
|
+
}
|
|
112
115
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
override fun removeViewAt(parent: SpallaPlayerContainerView, index: Int) {
|
|
117
|
+
parent.removeViewAt(index)
|
|
118
|
+
}
|
|
116
119
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
override fun getChildCount(parent: SpallaPlayerContainerView): Int {
|
|
121
|
+
return parent.childCount
|
|
122
|
+
}
|
|
120
123
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
141
|
+
@ReactProp(name = "muted")
|
|
142
|
+
fun setMuted(view: SpallaPlayerContainerView, muted: Boolean) {
|
|
143
|
+
_playerView?.setMuted(muted)
|
|
144
|
+
}
|
|
144
145
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
@ReactProp(name = "startTime")
|
|
147
|
+
fun setStartTime(view: SpallaPlayerContainerView, startTime: Double) {
|
|
148
|
+
this.startTime = startTime
|
|
149
|
+
//checkAndLoadPlayer(view)
|
|
150
|
+
}
|
|
150
151
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
173
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
190
|
+
Ended -> {
|
|
191
|
+
map.putString("event", "ended")
|
|
192
|
+
isPlaying = false
|
|
193
|
+
}
|
|
253
194
|
|
|
254
|
-
|
|
255
|
-
map.putString("event", "
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
278
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
}
|
package/plugin/index.js
ADDED
|
@@ -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
|
+
};
|