Skip to content

Commit dbd26c5

Browse files
authored
feat: Expose startDownload API (#50)
Clients needed a way to manually trigger video downloads from their own custom UI, outside of the standard player interface. This change bridges the native startDownload functionality to JavaScript so developers can fully control when and how downloads are initiated, while maintaining support for advanced native features like the resolution picker dialog.
1 parent b18f0ef commit dbd26c5

9 files changed

Lines changed: 85 additions & 17 deletions

TPStreamsRNPlayerView.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Pod::Spec.new do |s|
2121
'DEFINES_MODULE' => 'YES',
2222
'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES'
2323
}
24-
s.dependency "TPStreamsSDK" , "1.2.16"
24+
s.dependency "TPStreamsSDK" , "1.2.23"
2525

2626

2727
# Ensure the module is not built as a framework to avoid bridging header conflicts

android/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ Tpstreams_minSdkVersion=24
33
Tpstreams_targetSdkVersion=34
44
Tpstreams_compileSdkVersion=35
55
Tpstreams_ndkVersion=27.1.12297006
6-
Tpstreams_tpstreamsAndroidPlayerVersion=1.1.8
6+
Tpstreams_tpstreamsAndroidPlayerVersion=1.1.9

android/src/main/java/com/tpstreams/JsonUtils.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.tpstreams
22

3+
import org.json.JSONException
34
import org.json.JSONObject
45

56
object JsonUtils {
@@ -43,4 +44,16 @@ object JsonUtils {
4344
}
4445
return map
4546
}
46-
}
47+
48+
fun jsonStringToMap(jsonString: String?): Map<String, String>? {
49+
if (jsonString.isNullOrEmpty()) return null
50+
return try {
51+
val jsonObject = JSONObject(jsonString)
52+
jsonObject.keys()
53+
.asSequence()
54+
.associate { it to jsonObject.getString(it) }
55+
} catch (e: JSONException) {
56+
null
57+
}
58+
}
59+
}

android/src/main/java/com/tpstreams/TPStreamsDownloadModule.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.facebook.react.bridge.Arguments
66
import com.facebook.react.bridge.ReactMethod
77
import com.facebook.react.bridge.ReactApplicationContext
88
import com.facebook.react.bridge.ReactContextBaseJavaModule
9+
import com.facebook.react.bridge.ReadableMap
910
import com.facebook.react.bridge.WritableMap
1011
import com.facebook.react.modules.core.DeviceEventManagerModule
1112
import com.tpstreams.player.download.DownloadClient
@@ -23,6 +24,37 @@ class TPStreamsDownloadModule(private val reactContext: ReactApplicationContext)
2324
return "TPStreamsDownload"
2425
}
2526

27+
@ReactMethod
28+
fun startDownload(
29+
videoId: String,
30+
accessToken: String,
31+
resolution: String?,
32+
metadata: ReadableMap?,
33+
promise: Promise
34+
) {
35+
try {
36+
val metadataString = metadata?.let { org.json.JSONObject(it.toHashMap()).toString() }
37+
val metadataMap = JsonUtils.jsonStringToMap(metadataString)
38+
39+
val activity = currentActivity ?: run {
40+
promise.reject("DOWNLOAD_START_ERROR", "No current activity available")
41+
return
42+
}
43+
44+
downloadClient.startDownload(
45+
activity,
46+
videoId,
47+
accessToken,
48+
resolution,
49+
metadataMap
50+
)
51+
promise.resolve(null)
52+
} catch (e: Exception) {
53+
Log.e(TAG, "Error starting download: ${e.message}", e)
54+
promise.reject("DOWNLOAD_START_ERROR", e.message, e)
55+
}
56+
}
57+
2658
@ReactMethod
2759
fun addDownloadProgressListener(promise: Promise) {
2860
try {

android/src/main/java/com/tpstreams/TPStreamsRNPlayerViewManager.kt

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -96,20 +96,7 @@ class TPStreamsRNPlayerViewManager : SimpleViewManager<TPStreamsRNPlayerView>(),
9696

9797
@ReactProp(name = "downloadMetadata")
9898
override fun setDownloadMetadata(view: TPStreamsRNPlayerView, metadata: String?) {
99-
val metadataMap = if (!metadata.isNullOrEmpty()) {
100-
try {
101-
val jsonObject = org.json.JSONObject(metadata)
102-
val map = jsonObject.keys()
103-
.asSequence()
104-
.associate { it to jsonObject.getString(it) }
105-
.toMutableMap()
106-
map
107-
} catch (e: org.json.JSONException) {
108-
android.util.Log.w("TPStreamsRN", "Error parsing download metadata: ${e.message}")
109-
null
110-
}
111-
} else null
112-
view.setDownloadMetadata(metadataMap)
99+
view.setDownloadMetadata(JsonUtils.jsonStringToMap(metadata))
113100
}
114101

115102
// Command implementations

ios/TPStreamsDownloadModule.mm

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ @interface RCT_EXTERN_MODULE(TPStreamsDownload, RCTEventEmitter)
1111
rejecter:(RCTPromiseRejectBlock)reject)
1212

1313
// Download Control Methods
14+
RCT_EXTERN_METHOD(startDownload:(NSString *)videoId
15+
accessToken:(NSString *)accessToken
16+
resolution:(NSString *)resolution
17+
metadata:(NSDictionary *)metadata
18+
resolver:(RCTPromiseResolveBlock)resolve
19+
rejecter:(RCTPromiseRejectBlock)reject)
20+
1421
RCT_EXTERN_METHOD(pauseDownload:(NSString *)videoId
1522
resolver:(RCTPromiseResolveBlock)resolve
1623
rejecter:(RCTPromiseRejectBlock)reject)

ios/TPStreamsDownloadModule.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,20 @@ class TPStreamsDownloadModule: RCTEventEmitter, TPStreamsDownloadDelegate {
4949
return true
5050
}
5151

52+
@objc
53+
func startDownload(_ videoId: String, accessToken: String, resolution: String?, metadata: NSDictionary?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
54+
DispatchQueue.main.async { [weak self] in
55+
guard let self = self else { return }
56+
57+
let metadataDict = metadata as? [String: Any]
58+
let res = (resolution?.isEmpty ?? true) ? nil : resolution
59+
let presentingVC = (res == nil) ? RCTPresentedViewController() : nil
60+
61+
self.downloadManager.startDownload(assetID: videoId, accessToken: accessToken, resolution: res, allowResolutionFallback: true, metadata: metadataDict, presentingViewController: presentingVC, completion: nil)
62+
resolve(nil)
63+
}
64+
}
65+
5266
@objc
5367
func addDownloadProgressListener(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
5468
isListening = true

src/TPStreamsDownload.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ export type DownloadStateChangeListener = (
3131

3232
const downloadEventEmitter = new NativeEventEmitter(TPStreamsDownload);
3333

34+
export function startDownload(
35+
videoId: string,
36+
accessToken: string,
37+
resolution: string | null = null,
38+
metadata: Record<string, any> | null = null
39+
): Promise<void> {
40+
return TPStreamsDownload.startDownload(
41+
videoId,
42+
accessToken,
43+
resolution,
44+
metadata
45+
);
46+
}
47+
3448
export function addDownloadProgressListener(): Promise<void> {
3549
return TPStreamsDownload.addDownloadProgressListener();
3650
}

src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export { default as TPStreamsPlayerView } from './TPStreamsPlayer';
88
export type { TPStreamsPlayerRef } from './TPStreamsPlayer';
99

1010
export {
11+
startDownload,
1112
pauseDownload,
1213
resumeDownload,
1314
removeDownload,

0 commit comments

Comments
 (0)