@shortkitsdk/react-native 0.2.6 → 0.2.12
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/ShortKitReactNative.podspec +1 -0
- package/android/build.gradle.kts +17 -1
- package/android/src/main/java/com/shortkit/reactnative/ReactCarouselOverlayHost.kt +379 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactLoadingHost.kt +40 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +570 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +1029 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedView.kt +212 -219
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedViewManager.kt +17 -3
- package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +157 -742
- package/android/src/main/java/com/shortkit/reactnative/ShortKitPlayerNativeView.kt +11 -2
- package/android/src/main/java/com/shortkit/reactnative/ShortKitWidgetNativeView.kt +2 -2
- package/ios/ReactCarouselOverlayHost.swift +177 -0
- package/ios/ReactLoadingHost.swift +38 -0
- package/ios/ReactOverlayHost.swift +444 -0
- package/ios/SKFabricSurfaceWrapper.h +18 -0
- package/ios/SKFabricSurfaceWrapper.mm +57 -0
- package/ios/ShortKitBridge.swift +220 -63
- package/ios/ShortKitFeedView.swift +82 -228
- package/ios/ShortKitFeedViewManager.mm +3 -2
- package/ios/ShortKitModule.mm +69 -37
- package/ios/ShortKitPlayerNativeView.swift +39 -8
- package/ios/ShortKitReactNative-Bridging-Header.h +2 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +1 -1
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +3683 -1249
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +56 -15
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +56 -15
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +1 -1
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +3683 -1249
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +56 -15
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +56 -15
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework.bak/Info.plist +43 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +418 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Info.plist +16 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +28917 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +824 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +824 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/module.modulemap +4 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +418 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Info.plist +16 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +28917 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +824 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +824 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/module.modulemap +4 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitWidgetNativeView.swift +3 -3
- package/package.json +1 -1
- package/src/ShortKitCarouselOverlaySurface.tsx +55 -0
- package/src/ShortKitCommands.ts +31 -0
- package/src/ShortKitContext.ts +6 -24
- package/src/ShortKitFeed.tsx +124 -41
- package/src/ShortKitLoadingSurface.tsx +24 -0
- package/src/ShortKitOverlaySurface.tsx +313 -0
- package/src/ShortKitPlayer.tsx +30 -9
- package/src/ShortKitProvider.tsx +28 -285
- package/src/index.ts +9 -3
- package/src/serialization.ts +20 -39
- package/src/specs/NativeShortKitModule.ts +74 -45
- package/src/specs/ShortKitFeedViewNativeComponent.ts +3 -2
- package/src/types.ts +84 -16
- package/src/useShortKit.ts +1 -3
- package/src/useShortKitPlayer.ts +7 -7
- package/android/src/main/java/com/shortkit/reactnative/ShortKitCarouselOverlayBridge.kt +0 -48
- package/android/src/main/java/com/shortkit/reactnative/ShortKitOverlayBridge.kt +0 -128
- package/ios/ShortKitCarouselOverlayBridge.swift +0 -219
- package/ios/ShortKitOverlayBridge.swift +0 -111
- package/src/CarouselOverlayManager.tsx +0 -70
- package/src/OverlayManager.tsx +0 -87
- package/src/useShortKitCarousel.ts +0 -29
|
@@ -1,881 +1,296 @@
|
|
|
1
1
|
package com.shortkit.reactnative
|
|
2
2
|
|
|
3
|
+
import android.os.Handler
|
|
4
|
+
import android.os.Looper
|
|
3
5
|
import com.facebook.react.bridge.Arguments
|
|
6
|
+
import com.facebook.react.bridge.Promise
|
|
4
7
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
8
|
import com.facebook.react.bridge.ReactMethod
|
|
6
9
|
import com.facebook.react.bridge.WritableMap
|
|
7
10
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
8
|
-
import com.shortkit.CarouselImage
|
|
9
|
-
import com.shortkit.ContentItem
|
|
10
|
-
import com.shortkit.ContentSignal
|
|
11
|
-
import com.shortkit.FeedInput
|
|
12
|
-
import com.shortkit.ImageCarouselItem
|
|
13
|
-
import com.shortkit.FeedConfig
|
|
14
|
-
import com.shortkit.FeedHeight
|
|
15
|
-
import com.shortkit.FeedSource
|
|
16
|
-
import com.shortkit.FeedTransitionPhase
|
|
17
|
-
import com.shortkit.JsonValue
|
|
18
|
-
import com.shortkit.ShortKit
|
|
19
|
-
import com.shortkit.ShortKitPlayer
|
|
20
|
-
import com.shortkit.VideoOverlayMode
|
|
21
|
-
import com.shortkit.CarouselOverlayMode
|
|
22
|
-
import com.shortkit.SurveyOverlayMode
|
|
23
|
-
import com.shortkit.AdOverlayMode
|
|
24
|
-
import kotlinx.coroutines.CoroutineScope
|
|
25
|
-
import kotlinx.coroutines.Dispatchers
|
|
26
|
-
import kotlinx.coroutines.SupervisorJob
|
|
27
|
-
import kotlinx.coroutines.cancel
|
|
28
|
-
import kotlinx.coroutines.launch
|
|
29
|
-
import org.json.JSONArray
|
|
30
|
-
import org.json.JSONObject
|
|
31
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Thin TurboModule that delegates all logic to [ShortKitBridge].
|
|
14
|
+
*
|
|
15
|
+
* Responsibilities:
|
|
16
|
+
* - Forward every @ReactMethod to the bridge
|
|
17
|
+
* - Handle event emission via RCTDeviceEventEmitter
|
|
18
|
+
* - Buffer operations that arrive before initialize() creates the bridge
|
|
19
|
+
*/
|
|
32
20
|
class ShortKitModule(reactContext: ReactApplicationContext) :
|
|
33
21
|
NativeShortKitModuleSpec(reactContext) {
|
|
34
22
|
|
|
35
23
|
companion object {
|
|
36
24
|
const val NAME = "ShortKitModule"
|
|
37
|
-
|
|
38
|
-
/** Static reference for Fabric view access (mirrors iOS ShortKitBridge.shared). */
|
|
39
|
-
@Volatile
|
|
40
|
-
var shared: ShortKitModule? = null
|
|
41
|
-
private set
|
|
42
25
|
}
|
|
43
26
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// -----------------------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
private var shortKit: ShortKit? = null
|
|
49
|
-
private var scope: CoroutineScope? = null
|
|
27
|
+
private var bridge: ShortKitBridge? = null
|
|
28
|
+
private var pendingBridgeOps: MutableList<() -> Unit>? = null
|
|
50
29
|
private var listenerCount = 0
|
|
51
|
-
@Volatile
|
|
52
|
-
private var hasListeners = false
|
|
53
|
-
private var pendingFeedItems: String? = null
|
|
54
|
-
private var pendingAppendItems: String? = null
|
|
55
|
-
|
|
56
|
-
/** Expose the underlying SDK for the Fabric feed view manager. */
|
|
57
|
-
val sdk: ShortKit? get() = shortKit
|
|
30
|
+
@Volatile private var hasListeners = false
|
|
58
31
|
|
|
59
|
-
//
|
|
32
|
+
// -----------------------------------------------------------------
|
|
60
33
|
// Module boilerplate
|
|
61
|
-
//
|
|
34
|
+
// -----------------------------------------------------------------
|
|
62
35
|
|
|
63
36
|
override fun getName(): String = NAME
|
|
64
37
|
|
|
65
38
|
override fun initialize() {
|
|
66
39
|
super.initialize()
|
|
67
|
-
shared = this
|
|
68
40
|
}
|
|
69
41
|
|
|
70
42
|
override fun onCatalystInstanceDestroy() {
|
|
71
|
-
teardown()
|
|
72
|
-
|
|
43
|
+
bridge?.teardown()
|
|
44
|
+
bridge = null
|
|
73
45
|
super.onCatalystInstanceDestroy()
|
|
74
46
|
}
|
|
75
47
|
|
|
76
|
-
//
|
|
77
|
-
// Event
|
|
78
|
-
//
|
|
48
|
+
// -----------------------------------------------------------------
|
|
49
|
+
// Event listener management
|
|
50
|
+
// -----------------------------------------------------------------
|
|
79
51
|
|
|
80
|
-
|
|
52
|
+
@ReactMethod
|
|
53
|
+
fun addListener(eventType: String?) {
|
|
81
54
|
listenerCount++
|
|
82
55
|
hasListeners = true
|
|
83
56
|
}
|
|
84
57
|
|
|
85
|
-
|
|
58
|
+
@ReactMethod
|
|
59
|
+
fun removeListeners(count: Double) {
|
|
86
60
|
listenerCount = maxOf(0, listenerCount - count.toInt())
|
|
87
61
|
hasListeners = listenerCount > 0
|
|
88
62
|
}
|
|
89
63
|
|
|
90
|
-
//
|
|
91
|
-
// Lifecycle
|
|
92
|
-
//
|
|
64
|
+
// -----------------------------------------------------------------
|
|
65
|
+
// Lifecycle
|
|
66
|
+
// -----------------------------------------------------------------
|
|
93
67
|
|
|
94
68
|
@ReactMethod
|
|
95
69
|
override fun initialize(
|
|
96
70
|
apiKey: String,
|
|
97
|
-
|
|
71
|
+
hasLoadingView: Boolean,
|
|
98
72
|
clientAppName: String?,
|
|
99
73
|
clientAppVersion: String?,
|
|
100
74
|
customDimensions: String?
|
|
101
75
|
) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
val feedConfig = parseFeedConfig(config)
|
|
106
|
-
val dims = parseCustomDimensions(customDimensions)
|
|
76
|
+
bridge?.teardown()
|
|
77
|
+
bridge = null
|
|
107
78
|
|
|
108
79
|
val context = reactApplicationContext
|
|
109
80
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
this.shortKit = sdk
|
|
121
|
-
shared = this
|
|
122
|
-
|
|
123
|
-
// Replay any feed items that arrived before initialization
|
|
124
|
-
pendingFeedItems?.let { json ->
|
|
125
|
-
parseFeedInputs(json)?.let { sdk.setFeedItems(it) }
|
|
126
|
-
pendingFeedItems = null
|
|
127
|
-
}
|
|
128
|
-
pendingAppendItems?.let { json ->
|
|
129
|
-
parseFeedInputs(json)?.let { sdk.appendFeedItems(it) }
|
|
130
|
-
pendingAppendItems = null
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
subscribeToFlows(sdk.player)
|
|
81
|
+
Handler(Looper.getMainLooper()).post {
|
|
82
|
+
bridge = ShortKitBridge(
|
|
83
|
+
apiKey = apiKey,
|
|
84
|
+
context = context,
|
|
85
|
+
hasLoadingView = hasLoadingView,
|
|
86
|
+
clientAppName = clientAppName,
|
|
87
|
+
clientAppVersion = clientAppVersion,
|
|
88
|
+
customDimensionsJSON = customDimensions,
|
|
89
|
+
emitEvent = { name, body -> sendEvent(name, body) }
|
|
90
|
+
)
|
|
134
91
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
val params = Arguments.createMap().apply {
|
|
138
|
-
putString("contentId", contentId)
|
|
139
|
-
putInt("index", index)
|
|
140
|
-
}
|
|
141
|
-
sendEvent("onContentTapped", params)
|
|
142
|
-
}
|
|
92
|
+
pendingBridgeOps?.forEach { it() }
|
|
93
|
+
pendingBridgeOps = null
|
|
143
94
|
}
|
|
144
95
|
}
|
|
145
96
|
|
|
97
|
+
@ReactMethod
|
|
98
|
+
override fun destroy() {
|
|
99
|
+
bridge?.teardown()
|
|
100
|
+
bridge = null
|
|
101
|
+
}
|
|
102
|
+
|
|
146
103
|
@ReactMethod
|
|
147
104
|
override fun setUserId(userId: String) {
|
|
148
|
-
|
|
105
|
+
bridge?.setUserId(userId)
|
|
149
106
|
}
|
|
150
107
|
|
|
151
108
|
@ReactMethod
|
|
152
109
|
override fun clearUserId() {
|
|
153
|
-
|
|
110
|
+
bridge?.clearUserId()
|
|
154
111
|
}
|
|
155
112
|
|
|
156
113
|
@ReactMethod
|
|
157
114
|
override fun onPause() {
|
|
158
|
-
|
|
115
|
+
bridge?.onPause()
|
|
159
116
|
}
|
|
160
117
|
|
|
161
|
-
/// Called when the app foregrounds. We do NOT auto-resume here because:
|
|
162
|
-
/// 1. The user may have manually paused before backgrounding.
|
|
163
|
-
/// 2. The ShortKit SDK's internal lifecycle management already resumes
|
|
164
|
-
/// playback when appropriate via Activity lifecycle callbacks.
|
|
165
118
|
@ReactMethod
|
|
166
119
|
override fun onResume() {
|
|
167
|
-
|
|
120
|
+
bridge?.onResume()
|
|
168
121
|
}
|
|
169
122
|
|
|
170
|
-
|
|
171
|
-
override fun destroy() {
|
|
172
|
-
teardown()
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// -----------------------------------------------------------------------
|
|
123
|
+
// -----------------------------------------------------------------
|
|
176
124
|
// Player controls
|
|
177
|
-
//
|
|
125
|
+
// -----------------------------------------------------------------
|
|
178
126
|
|
|
179
127
|
@ReactMethod
|
|
180
|
-
override fun play() {
|
|
181
|
-
shortKit?.player?.play()
|
|
182
|
-
}
|
|
128
|
+
override fun play() { bridge?.play() }
|
|
183
129
|
|
|
184
130
|
@ReactMethod
|
|
185
|
-
override fun pause() {
|
|
186
|
-
shortKit?.player?.pause()
|
|
187
|
-
}
|
|
131
|
+
override fun pause() { bridge?.pause() }
|
|
188
132
|
|
|
189
133
|
@ReactMethod
|
|
190
|
-
override fun seek(seconds: Double) {
|
|
191
|
-
shortKit?.player?.seek(seconds)
|
|
192
|
-
}
|
|
134
|
+
override fun seek(seconds: Double) { bridge?.seek(seconds) }
|
|
193
135
|
|
|
194
136
|
@ReactMethod
|
|
195
|
-
override fun seekAndPlay(seconds: Double) {
|
|
196
|
-
shortKit?.player?.seekAndPlay(seconds)
|
|
197
|
-
}
|
|
137
|
+
override fun seekAndPlay(seconds: Double) { bridge?.seekAndPlay(seconds) }
|
|
198
138
|
|
|
199
139
|
@ReactMethod
|
|
200
|
-
override fun skipToNext() {
|
|
201
|
-
shortKit?.player?.skipToNext()
|
|
202
|
-
}
|
|
140
|
+
override fun skipToNext() { bridge?.skipToNext() }
|
|
203
141
|
|
|
204
142
|
@ReactMethod
|
|
205
|
-
override fun skipToPrevious() {
|
|
206
|
-
shortKit?.player?.skipToPrevious()
|
|
207
|
-
}
|
|
143
|
+
override fun skipToPrevious() { bridge?.skipToPrevious() }
|
|
208
144
|
|
|
209
145
|
@ReactMethod
|
|
210
|
-
override fun setMuted(muted: Boolean) {
|
|
211
|
-
shortKit?.player?.setMuted(muted)
|
|
212
|
-
}
|
|
146
|
+
override fun setMuted(muted: Boolean) { bridge?.setMuted(muted) }
|
|
213
147
|
|
|
214
148
|
@ReactMethod
|
|
215
|
-
override fun setPlaybackRate(rate: Double) {
|
|
216
|
-
shortKit?.player?.setPlaybackRate(rate.toFloat())
|
|
217
|
-
}
|
|
149
|
+
override fun setPlaybackRate(rate: Double) { bridge?.setPlaybackRate(rate) }
|
|
218
150
|
|
|
219
151
|
@ReactMethod
|
|
220
|
-
override fun setCaptionsEnabled(enabled: Boolean) {
|
|
221
|
-
shortKit?.player?.setCaptionsEnabled(enabled)
|
|
222
|
-
}
|
|
152
|
+
override fun setCaptionsEnabled(enabled: Boolean) { bridge?.setCaptionsEnabled(enabled) }
|
|
223
153
|
|
|
224
154
|
@ReactMethod
|
|
225
|
-
override fun selectCaptionTrack(language: String) {
|
|
226
|
-
shortKit?.player?.selectCaptionTrack(language)
|
|
227
|
-
}
|
|
155
|
+
override fun selectCaptionTrack(language: String) { bridge?.selectCaptionTrack(language) }
|
|
228
156
|
|
|
229
157
|
@ReactMethod
|
|
230
|
-
override fun sendContentSignal(signal: String) {
|
|
231
|
-
val contentSignal = if (signal == "positive") ContentSignal.POSITIVE else ContentSignal.NEGATIVE
|
|
232
|
-
shortKit?.player?.sendContentSignal(contentSignal)
|
|
233
|
-
}
|
|
158
|
+
override fun sendContentSignal(signal: String) { bridge?.sendContentSignal(signal) }
|
|
234
159
|
|
|
235
160
|
@ReactMethod
|
|
236
|
-
override fun setMaxBitrate(bitrate: Double) {
|
|
237
|
-
shortKit?.player?.setMaxBitrate(bitrate.toInt())
|
|
238
|
-
}
|
|
161
|
+
override fun setMaxBitrate(bitrate: Double) { bridge?.setMaxBitrate(bitrate) }
|
|
239
162
|
|
|
240
|
-
//
|
|
163
|
+
// -----------------------------------------------------------------
|
|
241
164
|
// Custom feed
|
|
242
|
-
//
|
|
165
|
+
// -----------------------------------------------------------------
|
|
243
166
|
|
|
244
167
|
@ReactMethod
|
|
245
|
-
override fun setFeedItems(items: String) {
|
|
246
|
-
val
|
|
247
|
-
if (
|
|
248
|
-
|
|
249
|
-
sdk.setFeedItems(parsed)
|
|
168
|
+
override fun setFeedItems(feedId: String, items: String) {
|
|
169
|
+
val b = bridge
|
|
170
|
+
if (b != null) {
|
|
171
|
+
b.setFeedItems(feedId, items)
|
|
250
172
|
} else {
|
|
251
|
-
|
|
173
|
+
bufferOp { bridge?.setFeedItems(feedId, items) }
|
|
252
174
|
}
|
|
253
175
|
}
|
|
254
176
|
|
|
255
177
|
@ReactMethod
|
|
256
|
-
override fun appendFeedItems(items: String) {
|
|
257
|
-
val
|
|
258
|
-
if (
|
|
259
|
-
|
|
260
|
-
sdk.appendFeedItems(parsed)
|
|
178
|
+
override fun appendFeedItems(feedId: String, items: String) {
|
|
179
|
+
val b = bridge
|
|
180
|
+
if (b != null) {
|
|
181
|
+
b.appendFeedItems(feedId, items)
|
|
261
182
|
} else {
|
|
262
|
-
|
|
183
|
+
bufferOp { bridge?.appendFeedItems(feedId, items) }
|
|
263
184
|
}
|
|
264
185
|
}
|
|
265
186
|
|
|
266
187
|
@ReactMethod
|
|
267
|
-
override fun fetchContent(limit: Double, promise:
|
|
268
|
-
val
|
|
269
|
-
if (
|
|
188
|
+
override fun fetchContent(limit: Double, filterJSON: String?, promise: Promise) {
|
|
189
|
+
val b = bridge
|
|
190
|
+
if (b == null) {
|
|
270
191
|
promise.resolve("[]")
|
|
271
192
|
return
|
|
272
193
|
}
|
|
273
|
-
|
|
274
|
-
try {
|
|
275
|
-
val items = sdk.fetchContent(limit.toInt())
|
|
276
|
-
val arr = JSONArray()
|
|
277
|
-
for (item in items) {
|
|
278
|
-
arr.put(JSONObject(serializeContentItemToJSON(item)))
|
|
279
|
-
}
|
|
280
|
-
promise.resolve(arr.toString())
|
|
281
|
-
} catch (e: Exception) {
|
|
282
|
-
promise.resolve("[]")
|
|
283
|
-
}
|
|
284
|
-
}
|
|
194
|
+
b.fetchContent(limit.toInt(), filterJSON) { result -> promise.resolve(result) }
|
|
285
195
|
}
|
|
286
196
|
|
|
287
|
-
// -----------------------------------------------------------------------
|
|
288
|
-
// Storyboard / Seek Thumbnails
|
|
289
|
-
// -----------------------------------------------------------------------
|
|
290
|
-
|
|
291
197
|
@ReactMethod
|
|
292
|
-
override fun
|
|
293
|
-
|
|
198
|
+
override fun applyFilter(feedId: String, filterJSON: String?) {
|
|
199
|
+
val b = bridge
|
|
200
|
+
if (b != null) {
|
|
201
|
+
b.applyFilter(feedId, filterJSON)
|
|
202
|
+
} else {
|
|
203
|
+
bufferOp { bridge?.applyFilter(feedId, filterJSON) }
|
|
204
|
+
}
|
|
294
205
|
}
|
|
295
206
|
|
|
296
207
|
@ReactMethod
|
|
297
|
-
override fun
|
|
298
|
-
val
|
|
299
|
-
if (
|
|
300
|
-
promise.resolve(
|
|
208
|
+
override fun preloadFeed(configJSON: String, itemsJSON: String?, promise: Promise) {
|
|
209
|
+
val b = bridge
|
|
210
|
+
if (b == null) {
|
|
211
|
+
promise.resolve("")
|
|
301
212
|
return
|
|
302
213
|
}
|
|
303
|
-
|
|
304
|
-
val json = player.getStoryboardData(playbackId)
|
|
305
|
-
if (json != null) {
|
|
306
|
-
promise.resolve(json)
|
|
307
|
-
return
|
|
308
|
-
}
|
|
309
|
-
// Trigger prefetch and retry after a short delay
|
|
310
|
-
player.prefetchStoryboard(playbackId)
|
|
311
|
-
scope?.launch {
|
|
312
|
-
// Wait for prefetch to complete (poll with timeout)
|
|
313
|
-
var retries = 0
|
|
314
|
-
while (retries < 30) { // 3 seconds max
|
|
315
|
-
kotlinx.coroutines.delay(100)
|
|
316
|
-
val data = player.getStoryboardData(playbackId)
|
|
317
|
-
if (data != null) {
|
|
318
|
-
promise.resolve(data)
|
|
319
|
-
return@launch
|
|
320
|
-
}
|
|
321
|
-
retries++
|
|
322
|
-
}
|
|
323
|
-
promise.resolve(null)
|
|
324
|
-
}
|
|
214
|
+
b.preloadFeed(configJSON, itemsJSON) { result -> promise.resolve(result) }
|
|
325
215
|
}
|
|
326
216
|
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
|
|
331
|
-
fun emitOverlayEvent(name: String, item: ContentItem) {
|
|
332
|
-
val params = Arguments.createMap().apply {
|
|
333
|
-
putString("item", serializeContentItemToJSON(item))
|
|
334
|
-
}
|
|
335
|
-
sendEvent(name, params)
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
fun emitOverlayEvent(name: String, params: WritableMap) {
|
|
339
|
-
sendEvent(name, params)
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// -----------------------------------------------------------------------
|
|
343
|
-
// Carousel overlay lifecycle events
|
|
344
|
-
// -----------------------------------------------------------------------
|
|
217
|
+
// -----------------------------------------------------------------
|
|
218
|
+
// Storyboard / Seek Thumbnails
|
|
219
|
+
// -----------------------------------------------------------------
|
|
345
220
|
|
|
346
|
-
|
|
347
|
-
|
|
221
|
+
@ReactMethod
|
|
222
|
+
override fun prefetchStoryboard(playbackId: String) {
|
|
223
|
+
bridge?.prefetchStoryboard(playbackId)
|
|
348
224
|
}
|
|
349
225
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
val newScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
|
357
|
-
scope = newScope
|
|
358
|
-
|
|
359
|
-
// Player state
|
|
360
|
-
newScope.launch {
|
|
361
|
-
player.playerState.collect { state ->
|
|
362
|
-
val params = Arguments.createMap().apply {
|
|
363
|
-
putString("state", playerStateString(state))
|
|
364
|
-
if (state is com.shortkit.PlayerState.Error) {
|
|
365
|
-
putString("errorMessage", state.message)
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
sendEvent("onPlayerStateChanged", params)
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Current item
|
|
373
|
-
newScope.launch {
|
|
374
|
-
player.currentItem.collect { item ->
|
|
375
|
-
if (item != null) {
|
|
376
|
-
sendEvent("onCurrentItemChanged", contentItemMap(item))
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Time updates
|
|
382
|
-
newScope.launch {
|
|
383
|
-
player.time.collect { time ->
|
|
384
|
-
val params = Arguments.createMap().apply {
|
|
385
|
-
putDouble("current", time.currentMs / 1000.0)
|
|
386
|
-
putDouble("duration", time.durationMs / 1000.0)
|
|
387
|
-
putDouble("buffered", time.bufferedMs / 1000.0)
|
|
388
|
-
}
|
|
389
|
-
sendEvent("onTimeUpdate", params)
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// Muted state
|
|
394
|
-
newScope.launch {
|
|
395
|
-
player.isMuted.collect { muted ->
|
|
396
|
-
val params = Arguments.createMap().apply {
|
|
397
|
-
putBoolean("isMuted", muted)
|
|
398
|
-
}
|
|
399
|
-
sendEvent("onMutedChanged", params)
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Playback rate
|
|
404
|
-
newScope.launch {
|
|
405
|
-
player.playbackRate.collect { rate ->
|
|
406
|
-
val params = Arguments.createMap().apply {
|
|
407
|
-
putDouble("rate", rate.toDouble())
|
|
408
|
-
}
|
|
409
|
-
sendEvent("onPlaybackRateChanged", params)
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Captions enabled
|
|
414
|
-
newScope.launch {
|
|
415
|
-
player.captionsEnabled.collect { enabled ->
|
|
416
|
-
val params = Arguments.createMap().apply {
|
|
417
|
-
putBoolean("enabled", enabled)
|
|
418
|
-
}
|
|
419
|
-
sendEvent("onCaptionsEnabledChanged", params)
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// Active caption track
|
|
424
|
-
newScope.launch {
|
|
425
|
-
player.activeCaptionTrack.collect { track ->
|
|
426
|
-
if (track != null) {
|
|
427
|
-
val params = Arguments.createMap().apply {
|
|
428
|
-
putString("language", track.language)
|
|
429
|
-
putString("label", track.label)
|
|
430
|
-
putString("sourceUrl", track.url ?: "")
|
|
431
|
-
}
|
|
432
|
-
sendEvent("onActiveCaptionTrackChanged", params)
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// Active cue (ms -> seconds)
|
|
438
|
-
newScope.launch {
|
|
439
|
-
player.activeCue.collect { cue ->
|
|
440
|
-
if (cue != null) {
|
|
441
|
-
val params = Arguments.createMap().apply {
|
|
442
|
-
putString("text", cue.text)
|
|
443
|
-
putDouble("startTime", cue.startMs / 1000.0)
|
|
444
|
-
putDouble("endTime", cue.endMs / 1000.0)
|
|
445
|
-
}
|
|
446
|
-
sendEvent("onActiveCueChanged", params)
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// Did loop
|
|
452
|
-
newScope.launch {
|
|
453
|
-
player.didLoop.collect { event ->
|
|
454
|
-
val params = Arguments.createMap().apply {
|
|
455
|
-
putString("contentId", event.contentId)
|
|
456
|
-
putInt("loopCount", event.loopCount)
|
|
457
|
-
}
|
|
458
|
-
sendEvent("onDidLoop", params)
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Feed transition
|
|
463
|
-
newScope.launch {
|
|
464
|
-
player.feedTransition.collect { event ->
|
|
465
|
-
val params = Arguments.createMap().apply {
|
|
466
|
-
putString("phase", when (event.phase) {
|
|
467
|
-
FeedTransitionPhase.BEGAN -> "began"
|
|
468
|
-
FeedTransitionPhase.ENDED -> "ended"
|
|
469
|
-
})
|
|
470
|
-
putString("direction", when (event.direction) {
|
|
471
|
-
com.shortkit.FeedDirection.FORWARD -> "forward"
|
|
472
|
-
com.shortkit.FeedDirection.BACKWARD -> "backward"
|
|
473
|
-
else -> "forward"
|
|
474
|
-
})
|
|
475
|
-
if (event.from != null) {
|
|
476
|
-
putString("fromItem", serializeContentItemToJSON(event.from!!))
|
|
477
|
-
}
|
|
478
|
-
if (event.to != null) {
|
|
479
|
-
putString("toItem", serializeContentItemToJSON(event.to!!))
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
sendEvent("onFeedTransition", params)
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// Format change (Long -> Double for bitrate)
|
|
487
|
-
newScope.launch {
|
|
488
|
-
player.formatChange.collect { event ->
|
|
489
|
-
val params = Arguments.createMap().apply {
|
|
490
|
-
putString("contentId", event.contentId)
|
|
491
|
-
putDouble("fromBitrate", event.fromBitrate.toDouble())
|
|
492
|
-
putDouble("toBitrate", event.toBitrate.toDouble())
|
|
493
|
-
putString("fromResolution", event.fromResolution ?: "")
|
|
494
|
-
putString("toResolution", event.toResolution ?: "")
|
|
495
|
-
}
|
|
496
|
-
sendEvent("onFormatChange", params)
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// Prefetched ahead count
|
|
501
|
-
newScope.launch {
|
|
502
|
-
player.prefetchedAheadCount.collect { count ->
|
|
503
|
-
val params = Arguments.createMap().apply {
|
|
504
|
-
putInt("count", count)
|
|
505
|
-
}
|
|
506
|
-
sendEvent("onPrefetchedAheadCountChanged", params)
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// Remaining content count
|
|
511
|
-
newScope.launch {
|
|
512
|
-
player.remainingContentCount.collect { count ->
|
|
513
|
-
val params = Arguments.createMap().apply {
|
|
514
|
-
putInt("count", count)
|
|
515
|
-
}
|
|
516
|
-
sendEvent("onRemainingContentCountChanged", params)
|
|
517
|
-
}
|
|
226
|
+
@ReactMethod
|
|
227
|
+
override fun getStoryboardData(playbackId: String, promise: Promise) {
|
|
228
|
+
val b = bridge
|
|
229
|
+
if (b == null) {
|
|
230
|
+
promise.resolve(null)
|
|
231
|
+
return
|
|
518
232
|
}
|
|
233
|
+
b.getStoryboardData(playbackId) { result -> promise.resolve(result) }
|
|
519
234
|
}
|
|
520
235
|
|
|
521
|
-
//
|
|
236
|
+
// -----------------------------------------------------------------
|
|
522
237
|
// Event emission
|
|
523
|
-
//
|
|
238
|
+
// -----------------------------------------------------------------
|
|
524
239
|
|
|
525
240
|
private fun sendEvent(name: String, params: WritableMap) {
|
|
526
|
-
|
|
241
|
+
// Use the codegen-generated emitOn* methods which route through
|
|
242
|
+
// mEventEmitterCallback (JSI direct channel). The legacy
|
|
243
|
+
// DeviceEventManagerModule.RCTDeviceEventEmitter path does NOT
|
|
244
|
+
// reach codegen EventEmitter<T> subscribers in RN 0.78 new arch.
|
|
527
245
|
try {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
putString("streamingUrl", item.streamingUrl)
|
|
564
|
-
putString("thumbnailUrl", item.thumbnailUrl)
|
|
565
|
-
|
|
566
|
-
// Caption tracks as JSON string
|
|
567
|
-
putString("captionTracks", serializeCaptionTracks(item.captionTracks))
|
|
568
|
-
|
|
569
|
-
// Custom metadata as JSON string
|
|
570
|
-
item.customMetadata?.let { meta ->
|
|
571
|
-
putString("customMetadata", serializeCustomMetadata(meta))
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
item.author?.let { putString("author", it) }
|
|
575
|
-
item.articleUrl?.let { putString("articleUrl", it) }
|
|
576
|
-
item.commentCount?.let { putInt("commentCount", it) }
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
/**
|
|
581
|
-
* Serialize a full ContentItem to a JSON string for delegate/overlay events.
|
|
582
|
-
*/
|
|
583
|
-
private fun serializeContentItemToJSON(item: ContentItem): String {
|
|
584
|
-
return try {
|
|
585
|
-
val obj = JSONObject().apply {
|
|
586
|
-
put("id", item.id)
|
|
587
|
-
put("title", item.title)
|
|
588
|
-
item.description?.let { put("description", it) }
|
|
589
|
-
put("duration", item.duration)
|
|
590
|
-
put("streamingUrl", item.streamingUrl)
|
|
591
|
-
put("thumbnailUrl", item.thumbnailUrl)
|
|
592
|
-
put("captionTracks", buildCaptionTracksJSONArray(item.captionTracks))
|
|
593
|
-
item.customMetadata?.let { put("customMetadata", buildCustomMetadataJSONObject(it)) }
|
|
594
|
-
item.author?.let { put("author", it) }
|
|
595
|
-
item.articleUrl?.let { put("articleUrl", it) }
|
|
596
|
-
item.commentCount?.let { put("commentCount", it) }
|
|
597
|
-
}
|
|
598
|
-
obj.toString()
|
|
599
|
-
} catch (_: Exception) {
|
|
600
|
-
"{}"
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
/**
|
|
605
|
-
* Serialize caption tracks list to a JSON string: `[{"language":"en","label":"English","sourceUrl":"..."}]`
|
|
606
|
-
*/
|
|
607
|
-
private fun serializeCaptionTracks(tracks: List<com.shortkit.CaptionTrack>): String {
|
|
608
|
-
return try {
|
|
609
|
-
buildCaptionTracksJSONArray(tracks).toString()
|
|
610
|
-
} catch (_: Exception) {
|
|
611
|
-
"[]"
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
private fun buildCaptionTracksJSONArray(tracks: List<com.shortkit.CaptionTrack>): JSONArray {
|
|
616
|
-
val arr = JSONArray()
|
|
617
|
-
for (track in tracks) {
|
|
618
|
-
val obj = JSONObject().apply {
|
|
619
|
-
put("language", track.language)
|
|
620
|
-
put("label", track.label)
|
|
621
|
-
put("sourceUrl", track.url ?: "")
|
|
622
|
-
}
|
|
623
|
-
arr.put(obj)
|
|
624
|
-
}
|
|
625
|
-
return arr
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
/**
|
|
629
|
-
* Serialize custom metadata map to a JSON string.
|
|
630
|
-
*/
|
|
631
|
-
private fun serializeCustomMetadata(meta: Map<String, JsonValue>): String {
|
|
632
|
-
return try {
|
|
633
|
-
buildCustomMetadataJSONObject(meta).toString()
|
|
634
|
-
} catch (_: Exception) {
|
|
635
|
-
"{}"
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
private fun buildCustomMetadataJSONObject(meta: Map<String, JsonValue>): JSONObject {
|
|
640
|
-
val obj = JSONObject()
|
|
641
|
-
for ((key, value) in meta) {
|
|
642
|
-
obj.put(key, jsonValueToAny(value))
|
|
643
|
-
}
|
|
644
|
-
return obj
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
/**
|
|
648
|
-
* Convert a ShortKit JsonValue sealed class to a native type suitable for JSONObject.
|
|
649
|
-
*/
|
|
650
|
-
private fun jsonValueToAny(value: JsonValue): Any {
|
|
651
|
-
return when (value) {
|
|
652
|
-
is JsonValue.StringValue -> value.value
|
|
653
|
-
is JsonValue.NumberValue -> value.value
|
|
654
|
-
is JsonValue.BoolValue -> value.value
|
|
655
|
-
is JsonValue.ObjectValue -> {
|
|
656
|
-
val obj = JSONObject()
|
|
657
|
-
for ((k, v) in value.value) {
|
|
658
|
-
obj.put(k, jsonValueToAny(v))
|
|
659
|
-
}
|
|
660
|
-
obj
|
|
661
|
-
}
|
|
662
|
-
is JsonValue.NullValue -> JSONObject.NULL
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
// -----------------------------------------------------------------------
|
|
667
|
-
// Player state serialization
|
|
668
|
-
// -----------------------------------------------------------------------
|
|
669
|
-
|
|
670
|
-
private fun playerStateString(state: com.shortkit.PlayerState): String {
|
|
671
|
-
return when (state) {
|
|
672
|
-
is com.shortkit.PlayerState.Idle -> "idle"
|
|
673
|
-
is com.shortkit.PlayerState.Loading -> "loading"
|
|
674
|
-
is com.shortkit.PlayerState.Ready -> "ready"
|
|
675
|
-
is com.shortkit.PlayerState.Playing -> "playing"
|
|
676
|
-
is com.shortkit.PlayerState.Paused -> "paused"
|
|
677
|
-
is com.shortkit.PlayerState.Seeking -> "seeking"
|
|
678
|
-
is com.shortkit.PlayerState.Buffering -> "buffering"
|
|
679
|
-
is com.shortkit.PlayerState.Ended -> "ended"
|
|
680
|
-
is com.shortkit.PlayerState.Error -> "error"
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// -----------------------------------------------------------------------
|
|
685
|
-
// Config parsing
|
|
686
|
-
// -----------------------------------------------------------------------
|
|
687
|
-
|
|
688
|
-
/**
|
|
689
|
-
* Parse the JSON config string from JS into a FeedConfig.
|
|
690
|
-
*
|
|
691
|
-
* Expected JSON shape:
|
|
692
|
-
* ```json
|
|
693
|
-
* {
|
|
694
|
-
* "feedHeight": "{\"type\":\"fullscreen\"}",
|
|
695
|
-
* "overlay": "\"none\"",
|
|
696
|
-
* "carouselMode": "\"none\"",
|
|
697
|
-
* "surveyMode": "\"none\"",
|
|
698
|
-
* "muteOnStart": true
|
|
699
|
-
* }
|
|
700
|
-
* ```
|
|
701
|
-
*/
|
|
702
|
-
private fun parseFeedConfig(json: String): FeedConfig {
|
|
703
|
-
return try {
|
|
704
|
-
val obj = JSONObject(json)
|
|
705
|
-
|
|
706
|
-
val feedHeight = parseFeedHeight(obj.optString("feedHeight", null))
|
|
707
|
-
val muteOnStart = obj.optBoolean("muteOnStart", true)
|
|
708
|
-
val videoOverlay = parseVideoOverlay(obj.optString("overlay", null))
|
|
709
|
-
|
|
710
|
-
val feedSourceStr = obj.optString("feedSource", "algorithmic")
|
|
711
|
-
val feedSource = if (feedSourceStr == "custom") FeedSource.CUSTOM else FeedSource.ALGORITHMIC
|
|
712
|
-
|
|
713
|
-
val carouselOverlay = parseCarouselOverlay(obj.optString("carouselOverlay", null))
|
|
714
|
-
|
|
715
|
-
FeedConfig(
|
|
716
|
-
feedHeight = feedHeight,
|
|
717
|
-
videoOverlay = videoOverlay,
|
|
718
|
-
carouselOverlay = carouselOverlay,
|
|
719
|
-
surveyOverlay = SurveyOverlayMode.None,
|
|
720
|
-
adOverlay = AdOverlayMode.None,
|
|
721
|
-
muteOnStart = muteOnStart,
|
|
722
|
-
feedSource = feedSource
|
|
723
|
-
)
|
|
724
|
-
} catch (_: Exception) {
|
|
725
|
-
FeedConfig()
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
/**
|
|
730
|
-
* Parse a double-stringified feedHeight JSON.
|
|
731
|
-
* e.g. `"{\"type\":\"fullscreen\"}"` or `"{\"type\":\"percentage\",\"value\":0.8}"`
|
|
732
|
-
*/
|
|
733
|
-
private fun parseFeedHeight(json: String?): FeedHeight {
|
|
734
|
-
if (json.isNullOrEmpty()) return FeedHeight.Fullscreen
|
|
735
|
-
return try {
|
|
736
|
-
val obj = JSONObject(json)
|
|
737
|
-
when (obj.optString("type")) {
|
|
738
|
-
"percentage" -> {
|
|
739
|
-
val value = obj.optDouble("value", 1.0)
|
|
740
|
-
FeedHeight.Percentage(value.toFloat())
|
|
246
|
+
when (name) {
|
|
247
|
+
"onPlayerStateChanged" -> emitOnPlayerStateChanged(params)
|
|
248
|
+
"onCurrentItemChanged" -> emitOnCurrentItemChanged(params)
|
|
249
|
+
"onTimeUpdate" -> emitOnTimeUpdate(params)
|
|
250
|
+
"onMutedChanged" -> emitOnMutedChanged(params)
|
|
251
|
+
"onPlaybackRateChanged" -> emitOnPlaybackRateChanged(params)
|
|
252
|
+
"onCaptionsEnabledChanged" -> emitOnCaptionsEnabledChanged(params)
|
|
253
|
+
"onActiveCaptionTrackChanged" -> emitOnActiveCaptionTrackChanged(params)
|
|
254
|
+
"onActiveCueChanged" -> emitOnActiveCueChanged(params)
|
|
255
|
+
"onDidLoop" -> emitOnDidLoop(params)
|
|
256
|
+
"onFeedTransition" -> emitOnFeedTransition(params)
|
|
257
|
+
"onFeedScrollPhase" -> emitOnFeedScrollPhase(params)
|
|
258
|
+
"onFormatChange" -> emitOnFormatChange(params)
|
|
259
|
+
"onPrefetchedAheadCountChanged" -> emitOnPrefetchedAheadCountChanged(params)
|
|
260
|
+
"onRemainingContentCountChanged" -> emitOnRemainingContentCountChanged(params)
|
|
261
|
+
"onSurveyResponse" -> emitOnSurveyResponse(params)
|
|
262
|
+
"onContentTapped" -> emitOnContentTapped(params)
|
|
263
|
+
"onDismiss" -> emitOnDismiss(params)
|
|
264
|
+
"onRefreshRequested" -> emitOnRefreshRequested(params)
|
|
265
|
+
"onDidFetchContentItems" -> emitOnDidFetchContentItems(params)
|
|
266
|
+
"onFeedReady" -> emitOnFeedReady(params)
|
|
267
|
+
"onOverlayActiveChanged" -> emitOnOverlayActiveChanged(params)
|
|
268
|
+
"onOverlayPlayerStateChanged" -> emitOnOverlayPlayerStateChanged(params)
|
|
269
|
+
"onOverlayMutedChanged" -> emitOnOverlayMutedChanged(params)
|
|
270
|
+
"onOverlayPlaybackRateChanged" -> emitOnOverlayPlaybackRateChanged(params)
|
|
271
|
+
"onOverlayCaptionsEnabledChanged" -> emitOnOverlayCaptionsEnabledChanged(params)
|
|
272
|
+
"onOverlayActiveCueChanged" -> emitOnOverlayActiveCueChanged(params)
|
|
273
|
+
"onOverlayFeedScrollPhaseChanged" -> emitOnOverlayFeedScrollPhaseChanged(params)
|
|
274
|
+
"onOverlayTimeUpdate" -> emitOnOverlayTimeUpdate(params)
|
|
275
|
+
"onOverlayFullState" -> emitOnOverlayFullState(params)
|
|
276
|
+
else -> {
|
|
277
|
+
android.util.Log.w("SK:Module", "sendEvent: unknown event name '$name', using legacy emitter")
|
|
278
|
+
reactApplicationContext
|
|
279
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
280
|
+
.emit(name, params)
|
|
741
281
|
}
|
|
742
|
-
else -> FeedHeight.Fullscreen
|
|
743
282
|
}
|
|
744
|
-
} catch (
|
|
745
|
-
|
|
283
|
+
} catch (e: Exception) {
|
|
284
|
+
android.util.Log.e("SK:Module", "sendEvent($name) EXCEPTION: ${e.message}", e)
|
|
746
285
|
}
|
|
747
286
|
}
|
|
748
287
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
* - `"{\"type\":\"custom\"}"` -> Custom with bridge overlay factory
|
|
753
|
-
*/
|
|
754
|
-
private fun parseVideoOverlay(json: String?): VideoOverlayMode {
|
|
755
|
-
if (json.isNullOrEmpty()) return VideoOverlayMode.None
|
|
756
|
-
return try {
|
|
757
|
-
// Try parsing — might be a simple string "none" or an object
|
|
758
|
-
val parsed = json.trim()
|
|
759
|
-
|
|
760
|
-
// Strip outer quotes if double-stringified simple string
|
|
761
|
-
if (parsed == "\"none\"" || parsed == "none") {
|
|
762
|
-
return VideoOverlayMode.None
|
|
763
|
-
}
|
|
288
|
+
// -----------------------------------------------------------------
|
|
289
|
+
// Op buffering
|
|
290
|
+
// -----------------------------------------------------------------
|
|
764
291
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
JSONObject(parsed.substring(1, parsed.length - 1).replace("\\\"", "\""))
|
|
769
|
-
} else {
|
|
770
|
-
JSONObject(parsed)
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
if (inner.optString("type") == "custom") {
|
|
774
|
-
// The Fabric view will handle the actual overlay view creation.
|
|
775
|
-
// For the module, we signal custom mode so the SDK allocates an overlay slot.
|
|
776
|
-
VideoOverlayMode.Custom { ShortKitOverlayBridge(reactApplicationContext) }
|
|
777
|
-
} else {
|
|
778
|
-
VideoOverlayMode.None
|
|
779
|
-
}
|
|
780
|
-
} catch (_: Exception) {
|
|
781
|
-
VideoOverlayMode.None
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
/**
|
|
786
|
-
* Parse a double-stringified carousel overlay JSON.
|
|
787
|
-
* - `"\"none\""` -> None
|
|
788
|
-
* - `"{\"type\":\"custom\"}"` -> Custom with bridge overlay factory
|
|
789
|
-
*/
|
|
790
|
-
private fun parseCarouselOverlay(json: String?): CarouselOverlayMode {
|
|
791
|
-
if (json.isNullOrEmpty()) return CarouselOverlayMode.None
|
|
792
|
-
return try {
|
|
793
|
-
val parsed = json.trim()
|
|
794
|
-
|
|
795
|
-
if (parsed == "\"none\"" || parsed == "none") {
|
|
796
|
-
return CarouselOverlayMode.None
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
val inner = if (parsed.startsWith("\"") && parsed.endsWith("\"")) {
|
|
800
|
-
JSONObject(parsed.substring(1, parsed.length - 1).replace("\\\"", "\""))
|
|
801
|
-
} else {
|
|
802
|
-
JSONObject(parsed)
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
if (inner.optString("type") == "custom") {
|
|
806
|
-
CarouselOverlayMode.Custom { ShortKitCarouselOverlayBridge(reactApplicationContext) }
|
|
807
|
-
} else {
|
|
808
|
-
CarouselOverlayMode.None
|
|
809
|
-
}
|
|
810
|
-
} catch (_: Exception) {
|
|
811
|
-
CarouselOverlayMode.None
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
private fun parseFeedInputs(json: String): List<FeedInput>? {
|
|
816
|
-
return try {
|
|
817
|
-
val arr = JSONArray(json)
|
|
818
|
-
val result = mutableListOf<FeedInput>()
|
|
819
|
-
for (i in 0 until arr.length()) {
|
|
820
|
-
val obj = arr.getJSONObject(i)
|
|
821
|
-
when (obj.optString("type")) {
|
|
822
|
-
"video" -> {
|
|
823
|
-
val playbackId = obj.optString("playbackId", null) ?: continue
|
|
824
|
-
result.add(FeedInput.Video(playbackId))
|
|
825
|
-
}
|
|
826
|
-
"imageCarousel" -> {
|
|
827
|
-
val itemObj = obj.optJSONObject("item") ?: continue
|
|
828
|
-
val carouselItem = parseImageCarouselItem(itemObj) ?: continue
|
|
829
|
-
result.add(FeedInput.ImageCarousel(carouselItem))
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
result.ifEmpty { null }
|
|
834
|
-
} catch (_: Exception) {
|
|
835
|
-
null
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
private fun parseImageCarouselItem(obj: JSONObject): ImageCarouselItem? {
|
|
840
|
-
val id = obj.optString("id", null) ?: return null
|
|
841
|
-
val imagesArr = obj.optJSONArray("images") ?: return null
|
|
842
|
-
val images = mutableListOf<CarouselImage>()
|
|
843
|
-
for (i in 0 until imagesArr.length()) {
|
|
844
|
-
val imgObj = imagesArr.getJSONObject(i)
|
|
845
|
-
images.add(CarouselImage(
|
|
846
|
-
url = imgObj.getString("url"),
|
|
847
|
-
alt = imgObj.optString("alt", null)
|
|
848
|
-
))
|
|
849
|
-
}
|
|
850
|
-
return ImageCarouselItem(
|
|
851
|
-
id = id,
|
|
852
|
-
images = images,
|
|
853
|
-
autoScrollInterval = if (obj.has("autoScrollInterval")) obj.getDouble("autoScrollInterval") else null,
|
|
854
|
-
caption = obj.optString("caption", null),
|
|
855
|
-
title = obj.optString("title", null),
|
|
856
|
-
description = obj.optString("description", null),
|
|
857
|
-
author = obj.optString("author", null),
|
|
858
|
-
section = obj.optString("section", null),
|
|
859
|
-
articleUrl = obj.optString("articleUrl", null)
|
|
860
|
-
)
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
/**
|
|
864
|
-
* Parse optional custom dimensions JSON string into map.
|
|
865
|
-
*/
|
|
866
|
-
private fun parseCustomDimensions(json: String?): Map<String, String>? {
|
|
867
|
-
if (json.isNullOrEmpty()) return null
|
|
868
|
-
return try {
|
|
869
|
-
val obj = JSONObject(json)
|
|
870
|
-
val map = mutableMapOf<String, String>()
|
|
871
|
-
val keys = obj.keys()
|
|
872
|
-
while (keys.hasNext()) {
|
|
873
|
-
val key = keys.next()
|
|
874
|
-
map[key] = obj.getString(key)
|
|
875
|
-
}
|
|
876
|
-
map
|
|
877
|
-
} catch (_: Exception) {
|
|
878
|
-
null
|
|
879
|
-
}
|
|
292
|
+
private fun bufferOp(op: () -> Unit) {
|
|
293
|
+
if (pendingBridgeOps == null) pendingBridgeOps = mutableListOf()
|
|
294
|
+
pendingBridgeOps!!.add(op)
|
|
880
295
|
}
|
|
881
296
|
}
|