@shortkitsdk/react-native 0.2.6 → 0.2.11
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 +5 -1
- package/android/src/main/java/com/shortkit/reactnative/ReactCarouselOverlayHost.kt +319 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactLoadingHost.kt +40 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +559 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +984 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedView.kt +88 -220
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedViewManager.kt +12 -3
- package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +123 -741
- package/android/src/main/java/com/shortkit/reactnative/ShortKitPlayerNativeView.kt +2 -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 +458 -0
- package/ios/SKFabricSurfaceWrapper.h +18 -0
- package/ios/SKFabricSurfaceWrapper.mm +57 -0
- package/ios/ShortKitBridge.swift +186 -63
- package/ios/ShortKitFeedView.swift +62 -229
- package/ios/ShortKitFeedViewManager.mm +3 -2
- package/ios/ShortKitModule.mm +66 -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 +2380 -522
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +39 -12
- 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 +39 -12
- 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 +2380 -522
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +39 -12
- 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 +39 -12
- 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 -25
- package/src/ShortKitFeed.tsx +110 -41
- package/src/ShortKitLoadingSurface.tsx +24 -0
- package/src/ShortKitOverlaySurface.tsx +205 -0
- package/src/ShortKitPlayer.tsx +6 -7
- package/src/ShortKitProvider.tsx +27 -286
- package/src/index.ts +5 -3
- package/src/serialization.ts +19 -39
- package/src/specs/NativeShortKitModule.ts +58 -46
- package/src/specs/ShortKitFeedViewNativeComponent.ts +3 -2
- package/src/types.ts +78 -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,263 @@
|
|
|
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
|
|
30
|
+
@Volatile private var hasListeners = false
|
|
55
31
|
|
|
56
|
-
|
|
57
|
-
val sdk: ShortKit? get() = shortKit
|
|
58
|
-
|
|
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(
|
|
301
|
-
return
|
|
302
|
-
}
|
|
303
|
-
// Try cached data first
|
|
304
|
-
val json = player.getStoryboardData(playbackId)
|
|
305
|
-
if (json != null) {
|
|
306
|
-
promise.resolve(json)
|
|
208
|
+
override fun preloadFeed(configJSON: String, promise: Promise) {
|
|
209
|
+
val b = bridge
|
|
210
|
+
if (b == null) {
|
|
211
|
+
promise.resolve("")
|
|
307
212
|
return
|
|
308
213
|
}
|
|
309
|
-
|
|
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
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// -----------------------------------------------------------------------
|
|
328
|
-
// Overlay lifecycle events (called by Fabric view)
|
|
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)
|
|
214
|
+
b.preloadFeed(configJSON) { result -> promise.resolve(result) }
|
|
340
215
|
}
|
|
341
216
|
|
|
342
|
-
//
|
|
343
|
-
//
|
|
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
|
+
// NOTE: In RN 0.78 new architecture, the codegen EventEmitter<T> does
|
|
242
|
+
// NOT call the legacy addListener/removeListeners methods, so the
|
|
243
|
+
// hasListeners flag stays false forever. We skip the guard and always
|
|
244
|
+
// emit. The try/catch handles the case where the catalyst instance
|
|
245
|
+
// is torn down.
|
|
527
246
|
try {
|
|
528
247
|
reactApplicationContext
|
|
529
248
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
530
249
|
.emit(name, params)
|
|
531
|
-
} catch (
|
|
532
|
-
|
|
250
|
+
} catch (e: Exception) {
|
|
251
|
+
android.util.Log.e("SK:Module", "sendEvent($name) EXCEPTION: ${e.message}", e)
|
|
533
252
|
}
|
|
534
253
|
}
|
|
535
254
|
|
|
536
|
-
//
|
|
537
|
-
//
|
|
538
|
-
//
|
|
255
|
+
// -----------------------------------------------------------------
|
|
256
|
+
// Op buffering
|
|
257
|
+
// -----------------------------------------------------------------
|
|
539
258
|
|
|
540
|
-
private fun
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
shortKit?.release()
|
|
544
|
-
shortKit = null
|
|
545
|
-
if (shared === this) shared = null
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// -----------------------------------------------------------------------
|
|
549
|
-
// Content item serialization
|
|
550
|
-
// -----------------------------------------------------------------------
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Build a flat WritableMap for `onCurrentItemChanged`.
|
|
554
|
-
* `captionTracks` and `customMetadata` are JSON-serialized strings.
|
|
555
|
-
*/
|
|
556
|
-
private fun contentItemMap(item: ContentItem): WritableMap {
|
|
557
|
-
return Arguments.createMap().apply {
|
|
558
|
-
putString("id", item.id)
|
|
559
|
-
item.playbackId?.let { putString("playbackId", it) }
|
|
560
|
-
putString("title", item.title)
|
|
561
|
-
item.description?.let { putString("description", it) }
|
|
562
|
-
putDouble("duration", item.duration)
|
|
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())
|
|
741
|
-
}
|
|
742
|
-
else -> FeedHeight.Fullscreen
|
|
743
|
-
}
|
|
744
|
-
} catch (_: Exception) {
|
|
745
|
-
FeedHeight.Fullscreen
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
/**
|
|
750
|
-
* Parse a double-stringified overlay JSON.
|
|
751
|
-
* - `"\"none\""` -> None
|
|
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
|
-
}
|
|
764
|
-
|
|
765
|
-
// Try as JSON object
|
|
766
|
-
val inner = if (parsed.startsWith("\"") && parsed.endsWith("\"")) {
|
|
767
|
-
// Double-stringified: strip outer quotes and unescape
|
|
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
|
-
}
|
|
259
|
+
private fun bufferOp(op: () -> Unit) {
|
|
260
|
+
if (pendingBridgeOps == null) pendingBridgeOps = mutableListOf()
|
|
261
|
+
pendingBridgeOps!!.add(op)
|
|
880
262
|
}
|
|
881
263
|
}
|