-
-
Save bramus/1536b9ec32dc9a02e417ff63e2a2e4ce to your computer and use it in GitHub Desktop.
// index.ios.js
videoPlayer.seekTo(100, (error, someData) => {
if (error) {
console.error(error)
} else {
console.log(someData)
}
})
// index.android.js
videoPlayer.seekTo(
100,
(msg) => {
console.error(msg)
},
(someData) => {
console.log(someData)
}
)
RCT_EXPORT_METHOD(seekTo:(double)time callback:(RCTResponseSenderBlock)callback)
{
NSArray *someData;
callback(@[[NSNull null], someData]); // (error, someData) in js
}
@ReactMethod
public void seekTo(double time, Callback errorCallback, Callback successCallback) {
try {
successCallback.invoke(someData);
} catch (Exception e) {
errorCallback.invoke(e.getMessage());
}
}
Keep in mind:
- Events share namespace (so come up with an app-wide naming convention)
// VideoPlayer.js
class VideoPlayer extends React.PureComponent {
_onEnd = (event) => {
if (!this.props.onEnd) {
return;
}
this.props.onEnd(event.nativeEvent)
}
render() {
// Re-assign onEnd to the private _onEnd and store it in `nativeProps`
const nativeProps = {
...this.props,
onEnd: this._onEnd,
}
return (
<RCTVideo
{...nativeProps}
/>
)
}
}
const RCTVideo = requireNativeComponent('RCTVideo', VideoPlayer)
VideoPlayer.propTypes = {
/**
* Callback that is called when the current player item ends.
*/
onEnd: PropTypes.func,
}
Notes: Bubbling events are like DOM events so that a parent component can capture an event fired by its child. Generally these are UI-related, like "the user touched this box". Direct events are not bubbled and are intended for more abstract events like "this image failed to load".
// VideoPlayer.h
#import <UIKit/UIKit.h>
#import <React/RCTView.h>
@interface VideoPlayer : UIView
// or RCTBubblingEventBlock
@property (nonatomic, copy) RCTDirectEventBlock onEnd;
@end
// VideoPlayerManager.m (inherits from RCTViewManager)
@implementation VideoPlayerManager
RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(onEnd, RCTDirectEventBlock)
- (UIView *)view
{
...
}
/*
* `VideoPlayerManager` acts as the delegate of all of the `VideoPlayer` views. This is just one
* pattern and it's perfectly fine to call `onEnd` from the `VideoPlayer` directly.
*/
- (void)onEnd:(VideoPlayer *)videoPlayer
{
if (!videoPlayer.onEnd) {
return;
}
videoPlayer.onEnd(@{ @"some-data" : @1 });
}
@end
Define a custom event mapping by overriding getExportedCustomDirectEventTypeConstants
in the
manager class:
// VideoPlayerManager.java
@Override
public @Nullable Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(
"onEnd",
MapBuilder.of("registrationName", "onEnd")
);
}
Dispatch the event:
// VideoPlayerView.java
private void dispatchOnEnd() {
WritableMap event = Arguments.createMap();
...
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
getId(),
"onEnd",
event
);
}
Keep in mind:
- Events share namespace (so come up with an app-wide naming convention)
import { NativeEventEmitter, NativeModules } from 'react-native'
const videoPlayer = NativeModules.VideoPlayer
const videoPlayerEmitter = new NativeEventEmitter(VideoPlayer)
const subscription = videoPlayerEmitter.addListener('video-progress', (data) => console.log(data.progress))
// Don't forget to unsubscribe, typically in `componentWillUnmount`
subscription.remove()
Important Note: Don't send events if there are no listeners attached. You will get a warning about spending unneccessary resources. Override
-startObserving
and-stopObserving
like in the example below:
// VideoPlayer.h
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface CalendarManager : RCTEventEmitter <RCTBridgeModule>
@end
// VideoPlayer.m
#import "VideoPlayer.h"
@implementation VideoPlayer
{
BOOL _hasListeners;
}
RCT_EXPORT_MODULE();
- (NSArray<NSString *> *)supportedEvents
{
return @[@"video-progress"];
}
// Will be called when this module's first listener is added.
- (void)startObserving
{
_hasListeners = YES;
}
// Will be called when this module's last listener is removed, or on dealloc.
- (void)stopObserving
{
_hasListeners = NO;
}
- (void)onVideoProgress
{
CGFloat progress = ...;
if (_hasListeners) {
[self sendEventWithName:@"video-progress" body:@{ @"progress": @(progress) }];
}
}
@end
private void onVideoProgress() {
WriteableMap params = Arguments.createMap();
...
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("video-progress", params);
}
async function loadVideo() {
try {
var videoLoaded = await VideoPlayer.loadVideo();
this.setState(videoLoaded: videoLoaded)
} catch (e) {
console.error(e)
}
}
loadVideo()
RCT_REMAP_METHOD(loadVideo, loadVideoWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
BOOL videoLoaded = ...; // could be any data type listed under https://facebook.github.io/react-native/docs/native-modules-ios.html#argument-types
if (videoLoaded) {
resolve(videoLoaded);
} else {
NSError *error = ...
reject(@"error", @"error description", error);
}
}
@ReactMethod loadVideo(Promise promise) {
try {
Boolean videoLoaded = ...; // could be any data type listed under https://facebook.github.io/react-native/docs/native-modules-android.html#argument-types
if (videoLoaded) {
promise.resolve(videoLoaded);
}
} catch (Exception e) {
promise.reject(ERROR, e);
}
}
render() {
<VideoPlayer loop={false} />
}
VideoPlayer.propTypes = {
loop: PropTypes.bool,
}
VideoPlayer.defaultProps = {
loop: false,
}
// BMEVideoManager.m
RCT_EXPORT_VIEW_PROPERTY(loop, BOOL);
@ReactProp(name = "loop")
public void setLoop(Boolean loop) {
...
}
import { NativeModules } from 'react-native'
const videoPlayer = NativeModules.VideoPlayer
videoPlayer.seekTo(100)
Keep in mind:
RCT_EXPORT_METHOD
exposes the name of the method up to the first colon in js land.- When many methods share the same name, use
RCT_REMAP_METHOD
to define a different name for js and map it to the native method. ie.RCT_REMAP_METHOD(differentMethodName, methodName:(BOOL)arg1 arg2:(BOOL)arg2)
.
// VideoPlayer.h
#import <React/RCTBridgeModule.h>
@interface VideoPlayer : NSObject <RCTBridgeModule>
@end
@implementation VideoPlayer
RCT_EXPORT_MODULE(); // or RCT_EXPLORT_MODULE(AwesomeVideoPlayer) -- custom name
RCT_EXPORT_METHOD(seekTo:(double)time)
{
// seek to time
}
@end
// VideoPlayer.java
package com.beme.react
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class VideoModule extends ReactContextBaseJavaModule {
public VideoModule(ReactApplicationContext reactContext) {
super(reactContext)
}
@Override
public String getName() {
return "VideoPlayer";
}
@ReactMethod
public void seekTo(double time) {
// seek to time
}
}
Register the module
// VideoPackage.java
package com.beme.react
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
public class VideoPackage implements ReactPackage {
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new VideoModule(reactContext));
return modules;
}
}
// MainApplication.java
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new VideoPackage()); // <-- Add this line with your package name.
}
Important Note: Don't send events if there are no listeners attached. You will get a warning about spending unneccessary resources. Override -startObserving and -stopObserving like in the example below:
Is there a way to know if the event listener is attached in Android?
Thank you for so cool info!
Is it really to use in the one example, (in react native wave form as case) to use both case: requireNativeComponent and NativeModules?
If I want to pass from js to native callback, I can't stop player, because I loose example of created OGWaveFormView. I hope that you or somebody can just add example with code and two methods in 1 component requireNativeComponent and NativeModules.
I can't use only 1, because in the requireNativeComponent It passed props and in NativeModules I created callback.