Skip to content

Commit

Permalink
feat(*): add a promise to return startStreaming
Browse files Browse the repository at this point in the history
  • Loading branch information
ThibaultBee committed Feb 20, 2024
1 parent e6f736f commit e470229
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ class LiveStreamView @JvmOverloads constructor(
var onPermissionsDenied: ((List<String>) -> Unit)? = null
var onPermissionsRationale: ((List<String>) -> Unit)? = null

// Internal usage only
var onStartStreaming: ((requestId: Int, result: Boolean, error: String?) -> Unit)? = null

private val connectionListener = object : IConnectionListener {
override fun onConnectionSuccess() {
onConnectionSuccess?.let { it() }
Expand Down Expand Up @@ -223,12 +226,20 @@ class LiveStreamView @JvmOverloads constructor(
})
}

fun startStreaming(streamKey: String, url: String?) {
require(permissionsManager.hasPermission(Manifest.permission.CAMERA)) { "Missing permissions Manifest.permission.CAMERA" }
require(permissionsManager.hasPermission(Manifest.permission.RECORD_AUDIO)) { "Missing permissions Manifest.permission.RECORD_AUDIO" }
fun startStreaming(requestId: Int, streamKey: String, url: String?) {
try {
throw RuntimeException("This is a test")
require(permissionsManager.hasPermission(Manifest.permission.CAMERA)) { "Missing permissions Manifest.permission.CAMERA" }
require(permissionsManager.hasPermission(Manifest.permission.RECORD_AUDIO)) { "Missing permissions Manifest.permission.RECORD_AUDIO" }

url?.let { liveStream.startStreaming(streamKey, it) }
?: liveStream.startStreaming(streamKey)

url?.let { liveStream.startStreaming(streamKey, it) }
?: liveStream.startStreaming(streamKey)
onStartStreaming?.let { it(requestId, true, null) }
} catch (e: Exception) {
Log.e(TAG, "Failed to start streaming", e)
onStartStreaming?.let { it(requestId, false, e.message) }
}
}

fun stopStreaming() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import video.api.reactnative.livestream.events.OnConnectionFailedEvent
import video.api.reactnative.livestream.events.OnConnectionSuccessEvent
import video.api.reactnative.livestream.events.OnDisconnectEvent
import video.api.reactnative.livestream.events.OnPermissionsDeniedEvent
import video.api.reactnative.livestream.events.OnStartStreamingEvent
import video.api.reactnative.livestream.utils.getCameraFacing
import video.api.reactnative.livestream.utils.toAudioConfig
import video.api.reactnative.livestream.utils.toVideoConfig
Expand Down Expand Up @@ -40,6 +41,11 @@ class LiveStreamViewManager : LiveStreamViewManagerSpec<LiveStreamView>() {
OnPermissionsDeniedEvent(view.id, permissions)
) ?: Log.e(NAME, "No event dispatcher for react tag ${view.id}")
}
view.onStartStreaming = { requestId, result, error ->
UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.id)?.dispatchEvent(
OnStartStreamingEvent(view.id, requestId, result, error)
) ?: Log.e(NAME, "No event dispatcher for react tag ${view.id}")
}
return view
}

Expand Down Expand Up @@ -92,8 +98,13 @@ class LiveStreamViewManager : LiveStreamViewManagerSpec<LiveStreamView>() {
}

@ReactMethod
override fun startStreaming(view: LiveStreamView, streamKey: String, url: String?) {
view.startStreaming(streamKey, url)
override fun startStreaming(
view: LiveStreamView,
requestId: Int,
streamKey: String,
url: String?
) {
view.startStreaming(requestId, streamKey, url)
}

@ReactMethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ object ViewProps {

// Permission events
PERMISSIONS_DENIED("onPermissionsDenied"),
PERMISSIONS_RATIONALE("onPermissionsRationale");
PERMISSIONS_RATIONALE("onPermissionsRationale"),

// Internal events
START_STREAMING("onStartStreaming");

companion object {
fun toEventsMap(): Map<String, *> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package video.api.reactnative.livestream.events

import com.facebook.react.bridge.Arguments
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.RCTEventEmitter
import video.api.reactnative.livestream.ViewProps

class OnStartStreamingEvent(
private val viewTag: Int,
private val requestId: Int,
private val result: Boolean,
private val error: String? = null
) :
Event<OnStartStreamingEvent>(viewTag) {
private val params = Arguments.createMap().apply {
putInt("requestId", requestId)
putBoolean("result", result)
error?.let { putString("error", it) }
}

override fun getEventName() = ViewProps.Events.START_STREAMING.eventName

override fun dispatch(rctEventEmitter: RCTEventEmitter) {
rctEventEmitter.receiveEvent(viewTag, eventName, params)
}
}
9 changes: 5 additions & 4 deletions android/src/oldarch/LiveStreamViewManagerSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@ abstract class LiveStreamViewManagerSpec<T : View> : SimpleViewManager<T>() {
abstract fun setZoomRatio(view: T, value: Float)
abstract fun setEnablePinchedZoom(view: T, value: Boolean)

abstract fun startStreaming(view: T, streamKey: String, url: String?)
abstract fun startStreaming(view: T, requestId: Int, streamKey: String, url: String?)
abstract fun stopStreaming(view: T)
abstract fun setZoomRatioCommand(view: T, zoomRatio: Float)

override fun receiveCommand(root: T, commandId: String, args: ReadableArray?) {
when (commandId) {
ViewProps.Commands.START_STREAMING.action -> {
val streamKey = args?.getString(0) ?: return
val url = args.getString(1)
startStreaming(root, streamKey, url)
val requestId = args?.getInt(0) ?: return
val streamKey = args.getString(1)
val url = args.getString(2)
startStreaming(root, requestId, streamKey, url)
}

ViewProps.Commands.STOP_STREAMING.action -> {
Expand Down
4 changes: 3 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,11 @@ export default function App() {
ref.current
?.startStreaming(settings.streamKey, settings.rtmpEndpoint)
.then((_: boolean) => {
console.log('Streaming started');
setStreaming(true);
})
.catch((_: any) => {
.catch((e: any) => {
console.log('Failed to start streaming: ', e);
setStreaming(false);
});
}
Expand Down
24 changes: 19 additions & 5 deletions ios/RNLiveStreamView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,23 @@ - (instancetype)initWithFrame:(CGRect)frame
std::static_pointer_cast<const ApiVideoLiveStreamViewEventEmitter>(_eventEmitter)->onDisconnect(data);
}
};


_view.onStartStreaming = [self](NSDictionary *dictionary) {
if (_eventEmitter) {
NSString *error = [dictionary valueForKey:@"error"];
std::string stdError;
if (error != nil) {
stdError = std::string([error UTF8String]);
}
ApiVideoLiveStreamViewEventEmitter::OnStartStreaming data = {
.requestId = [[dictionary valueForKey:@"requestId"] intValue],
.result = static_cast<bool>([[dictionary valueForKey:@"result"] boolValue]),
.error = stdError,
};
std::static_pointer_cast<const ApiVideoLiveStreamViewEventEmitter>(_eventEmitter)->onStartStreaming(data);
}
};

self.contentView = _view;
}
return self;
Expand Down Expand Up @@ -128,11 +144,9 @@ - (void)handleCommand:(nonnull const NSString *)commandName args:(nonnull const

// MARK: RCTComponentViewProtocol

- (void)startStreaming:(nonnull NSString *)streamKey url:(NSString *)url
- (void)startStreaming:(NSInteger)requestId streamKey:(NSString *)streamKey url:(NSString *)url
{
NSError *error = nil;
[_view startStreaming:streamKey url:url error:&error];

[_view startStreamingWithRequestId:requestId streamKey:streamKey url:url];
}

- (void)stopStreaming
Expand Down
34 changes: 27 additions & 7 deletions ios/RNLiveStreamViewImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,31 @@ public class RNLiveStreamViewImpl: UIView {
}
}

@objc public func startStreaming(_ streamKey: String, url: String?) throws {
if let url = url {
try liveStream.startStreaming(streamKey: streamKey, url: url)
} else {
try liveStream.startStreaming(streamKey: streamKey)
}
isStreaming = true
@objc public func startStreaming(requestId: Int, streamKey: String, url: String?) {
do {
if let url = url {
try liveStream.startStreaming(streamKey: streamKey, url: url)
} else {
try liveStream.startStreaming(streamKey: streamKey)
}
isStreaming = true
onStartStreaming([
"requestId": requestId,
"result": true,
])
} catch let LiveStreamError.IllegalArgumentError(message) {
self.onStartStreaming([
"requestId": requestId,
"result": false,
"error": message,
])
} catch {
onStartStreaming([
"requestId": requestId,
"result": false,
"error": error.localizedDescription,
])
}
}

@objc public func stopStreaming() {
Expand All @@ -182,6 +200,8 @@ public class RNLiveStreamViewImpl: UIView {

@objc public var onDisconnect: (_ dictionnary: [String: Any]) -> Void = { _ in }

@objc public var onStartStreaming: (_ dictionnary: [String: Any]) -> Void = { _ in }

@objc override public func removeFromSuperview() {
super.removeFromSuperview()
liveStream.stopPreview()
Expand Down
4 changes: 2 additions & 2 deletions ios/RNLiveStreamViewManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ @interface RCT_EXTERN_REMAP_MODULE(ApiVideoLiveStreamView, RNLiveStreamViewManag
RCT_EXPORT_VIEW_PROPERTY(onConnectionSuccess, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onConnectionFailed, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onDisconnect, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onStartStreaming, RCTDirectEventBlock)

RCT_EXTERN_METHOD(startStreaming:
(nonnull NSNumber *)reactTag
withRequestId:(nonnull NSNumber)requestId
withStreamKey:(nonnull NSString)streamKey
withUrl:(NSString)url)
// resolve:(RCTPromiseResolveBlock)resolve
// reject:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(stopStreaming:(nonnull NSNumber *)reactTag)
RCT_EXTERN_METHOD(setZoomRatioCommand:
(nonnull NSNumber *)reactTag
Expand Down
12 changes: 3 additions & 9 deletions ios/RNLiveStreamViewManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,11 @@ class RNLiveStreamViewManager: RCTViewManager {
return RNLiveStreamViewImpl()
}

@objc(startStreaming:withStreamKey:withUrl:) // resolve:reject:)
func startStreaming(_ reactTag: NSNumber, streamKey: String, url: String? /* , resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock */ ) {
@objc(startStreaming:withRequestId:withStreamKey:withUrl:)
func startStreaming(_ reactTag: NSNumber, withRequestId requestId: NSNumber, streamKey: String, url: String?) {
bridge!.uiManager.addUIBlock { (_: RCTUIManager?, viewRegistry: [NSNumber: UIView]?) in
let view: RNLiveStreamViewImpl = (viewRegistry![reactTag] as? RNLiveStreamViewImpl)!
do {
try view.startStreaming(streamKey, url: url)
// resolve(true)
} catch {
// TODO: reject
// reject("Failed_to_start_streaming", "Could not start streaming", error)
}
view.startStreaming(requestId: Int(truncating: requestId), streamKey: streamKey, url: url)
}
}

Expand Down
12 changes: 11 additions & 1 deletion src/NativeApiVideoLiveStreamView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ export type OnPermissionsDeniedEvent = Readonly<{
permissions: string[];
}>;

export type OnStartStreamingEvent = Readonly<{
requestId: Int32;
result: boolean;
error: string;
}>;

export interface NativeLiveStreamProps extends ViewProps {
camera?: WithDefault<Camera, 'back'>;
video: {
Expand All @@ -45,15 +51,19 @@ export interface NativeLiveStreamProps extends ViewProps {
onDisconnect?: DirectEventHandler<null>;

onPermissionsDenied?: DirectEventHandler<OnPermissionsDeniedEvent>;

// Internal use only
onStartStreaming?: DirectEventHandler<OnStartStreamingEvent>;
}

export type NativeLiveStreamViewType = HostComponent<NativeLiveStreamProps>;

interface NativeCommands {
startStreaming: (
viewRef: React.ElementRef<NativeLiveStreamViewType>,
requestId: Int32,
streamKey: string,
url?: string
url?: string,

Check failure on line 66 in src/NativeApiVideoLiveStreamView.ts

View workflow job for this annotation

GitHub Actions / lint

Delete `,`
) => void;
stopStreaming: (viewRef: React.ElementRef<NativeLiveStreamViewType>) => void;
setZoomRatioCommand: (
Expand Down
35 changes: 31 additions & 4 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,24 +114,50 @@ const ApiVideoLiveStreamView = forwardRef<
props.onPermissionsDenied?.(event.nativeEvent.permissions);
}
: undefined,
onStartStreaming: (

Check failure on line 117 in src/index.tsx

View workflow job for this annotation

GitHub Actions / lint

Replace `⏎········event:·NativeSyntheticEvent<OnStartStreamingEvent>⏎······` with `event:·NativeSyntheticEvent<OnStartStreamingEvent>`
event: NativeSyntheticEvent<OnStartStreamingEvent>
) => {
const { requestId, result, error } = event.nativeEvent;

Check failure on line 120 in src/index.tsx

View workflow job for this annotation

GitHub Actions / lint

Replace `········` with `······`
const promise = _requestMap.current.get(requestId);

Check failure on line 121 in src/index.tsx

View workflow job for this annotation

GitHub Actions / lint

Delete `··`

if (result) {

Check failure on line 123 in src/index.tsx

View workflow job for this annotation

GitHub Actions / lint

Delete `··`
promise?.resolve(result);

Check failure on line 124 in src/index.tsx

View workflow job for this annotation

GitHub Actions / lint

Delete `··`
} else {

Check failure on line 125 in src/index.tsx

View workflow job for this annotation

GitHub Actions / lint

Delete `··`
promise?.reject(error);

Check failure on line 126 in src/index.tsx

View workflow job for this annotation

GitHub Actions / lint

Delete `··`
}

Check failure on line 127 in src/index.tsx

View workflow job for this annotation

GitHub Actions / lint

Delete `··`
_requestMap.current.delete(requestId);

Check failure on line 128 in src/index.tsx

View workflow job for this annotation

GitHub Actions / lint

Delete `··`
},
};

const nativeRef = useRef<React.ElementRef<NativeLiveStreamViewType> | null>(
null
);
let _nextRequestId = useRef<number>(1);
const _requestMap = useRef<
Map<
number,
{ resolve: (result: boolean) => void; reject: (error?: string) => void }
>
>(new Map());

useImperativeHandle(forwardedRef, () => ({
startStreaming: (streamKey: string, url?: string): Promise<boolean> => {
if (nativeRef.current) {
const requestId = _nextRequestId.current++;
const requestMap = _requestMap;

const promise = new Promise<boolean>((resolve, reject) => {
requestMap.current.set(requestId, { resolve, reject });
});

NativeLiveStreamCommands.startStreaming(
nativeRef.current,
requestId,
streamKey,
url
);
// TODO: find a way to return a promise from native startStreaming
return new Promise((resolve, reject) => {
resolve(true);
});

return promise;
} else {
return new Promise((resolve, reject) => {
reject('Native component is not mounted');
Expand Down Expand Up @@ -162,6 +188,7 @@ const ApiVideoLiveStreamView = forwardRef<
onConnectionFailed={nativeLiveStreamProps.onConnectionFailed}
onDisconnect={nativeLiveStreamProps.onDisconnect}
onPermissionsDenied={nativeLiveStreamProps.onPermissionsDenied}
onStartStreaming={nativeLiveStreamProps.onStartStreaming}
ref={nativeRef as any}
/>
);
Expand Down

0 comments on commit e470229

Please sign in to comment.