Take the Video player as an example and package a React Native video player component for Android and ios to show basically everything that a React Native component needs. React Native uses the React Native video library, which supports multiple platforms and is easy to use.

android

Depend on the installation

Official githubPLDroidPlayer, check its related documentation, download jar and so copy into the project.

implementation

Customize video player view

In Android view rendering, subviews change size and events bubble up until the root view is processed. In React Native, the root view handles nothing, so if you want to resize a view, you have to manually resize it in requestLayout.

import android.content.Context;
import android.util.Log;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.pili.pldroid.player.PLOnCompletionListener;
import com.pili.pldroid.player.PLOnPreparedListener;
import com.pili.pldroid.player.widget.PLVideoView;

import javax.annotation.Nullable;

public class MyPLVideoView extends PLVideoView {

  private final static String TAG = "MyPLVideoView";

  public MyPLVideoView(Context context) {
    super(context);
    setOnPreparedListener(new PLOnPreparedListener() {
      @Override
      public void onPrepared(int i) { reLayout(); }}); setOnCompletionListener(new PLOnCompletionListener() {
      @Override
      public void onCompletion(a) {
        seekTo(0);
        MyPLVideoView.this.start();
        sendEvent("onPlayEnd".null); }}); }@Override
public void requestLayout(a) {
  super.requestLayout();
  // Avoid not working properly after switching resolution
  reLayout();
}

  public void reLayout(a) {
    if (getWidth() > 0 && getHeight() > 0) {
      int w = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY);
      inth = MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY); measure(w, h); layout(getPaddingLeft() + getLeft(), getPaddingTop() + getTop(), getWidth() + getPaddingLeft() + getLeft(), getHeight() + getPaddingTop() + getTop()); }}// The event is sent
  public void sendEvent(String name, @Nullable WritableMap event) { ReactContext reactContext = (ReactContext) getContext(); reactContext.getJSModule(RCTEventEmitter.class) .receiveEvent(getId(), name, event); }}Copy the code

Methods that need to be exposed to the view manager need to be called when updating prop. If you need to send events to the JS side, you need to use RCTEventEmitter, which is encapsulated in the view.

View manager

ViewGroupManager is used for container views and provides addView and other methods. SimpleViewManager is used for general views. The view manager mainly exports view props and provides JS -> native calls and native -> JS calls.

The @reactProp annotation exports a prop and calls this function when the component sets or modifies the prop. The first argument is the current view and the second argument is the value of the prop.

GetName returns the component name, which is used to find native components in the JS layer.

Native – > js: prop type for functions need to be registered in getExportedCustomDirectEventTypeConstants adds when triggered the callback.

Js -> native: ref methods are registered in getCommandsMap and handled in receiveCommand.

import android.net.Uri;
import android.util.Log;

import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.Nullable;

public class PLVideoViewManager extends SimpleViewManager<MyPLVideoView> {

  private static final String TAG = "PLVideoViewManager";

  @Override
  public String getName(a) {
    return "RTCPLVideo";
  }

  @Override
  protected MyPLVideoView createViewInstance(ThemedReactContext reactContext) {
    return new MyPLVideoView(reactContext);
  }

  // Video URI prop
  @ReactProp(name = "uri")
  public void uri(MyPLVideoView root, String uri) {
    root.setVideoURI(Uri.parse(uri));
  }

  // Video pause prop
  @ReactProp(name = "paused")
  public void paused(MyPLVideoView root, Boolean paused) {
    if (paused) {
      root.pause();
    } else{ root.start(); }}@Nullable
  @Override
  public Map<String, Integer> getCommandsMap(a) {
    Map<String, Integer> commandsMap = new HashMap<>();
    // ref method registration
    commandsMap.put("stop".1);
    return commandsMap;
  }

  @Override
  public void receiveCommand(MyPLVideoView root, int commandId, @Nullable ReadableArray args) {
    switch (commandId) {
      case 1:
        // Stop playing and release the player
        root.stopPlayback();
        break; }}@Nullable
  @Override
  public Map<String, Object> getExportedCustomDirectEventTypeConstants(a) {
    MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
    // prop function registration
    String[] events = {
        "onPlayEnd"
    };
    for (String event: events) {
      builder.put(event, MapBuilder.of("registrationName", event));
    }
    returnbuilder.build(); }}Copy the code

View the export

public class MyReactPackage implements ReactPackage {
  @Override
  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
    return Arrays.<ViewManager>asList(
        newPLVideoViewManager() ); }}Copy the code

Package export

public class MainApplication extends Application implements ReactApplication {
  @Override
    protected List<ReactPackage> getPackages(a) {
      return Arrays.asList(
        new MyReactPackage()
      );
}
Copy the code

ios

Depend on the installation

Official githubPLPlayerKit, check its integration instructions, use POD or manual integration.

implementation

view

.h

#import <Foundation/Foundation.h> #import <UIKit/UIKit.h> #import <React/RCTView.h> #import <PLPlayerKit/PLPlayerKit.h> @class RCTEventDispatcher; @interface RTCPLVideo : UIView<PLPlayerDelegate> - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; // prop @Property (nonatomic, copy) RCTBubblingEventBlock onPlayEnd; - (void) stop; @endCopy the code

.m

#import "RTCPLVideo.h" @interface RTCPLVideo()<PLPlayerDelegate> @property (nonatomic, strong) PLPlayer *player; @end @implementation RTCPLVideo { RCTEventDispatcher *_eventDispatcher; } - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { if ((self = [super init])) { } return self; } - (void)player:(nonnull PLPlayer *)player statusDidChange:(PLPlayerStatus)state { if (state == PLPlayerStatusCompleted) { CMTime start = CMTimeMakeWithSeconds(0, 600); [self.player seekTo: start]; If (self.onPlayEnd) {// Call prop function self.onPlayEnd(@{}); } } } - (void) setUri:(NSString *) uri { NSURL *url = [NSURL URLWithString:uri]; if (self.player == nil) { PLPlayerOption *option = [PLPlayerOption defaultOption]; [option setOptionValue:@15 forKey:PLPlayerOptionKeyTimeoutIntervalForMediaPackets]; self.player = [PLPlayer playerWithURL:url option:option]; self.player.delegate = self; [self addSubview:self.player.playerView]; [self.player play]; } else { [self.player playWithURL:url sameSource:NO]; } } - (void) setPaused: (BOOL) paused { if (self.player) { if (paused) { [self.player pause]; } else { [self.player play]; } } } - (void) cache:(NSString *)url { if (self.player) { NSURL *uri = [NSURL URLWithString:url]; [self.player openPlayerWithURL:uri]; } } - (void) stop { if (self.player) { [self.player stop]; } } @endCopy the code

View management

.h

#import <React/RCTUIManager.h>

@interface RTCPLVideoManager : RCTViewManager

@end
Copy the code

.m

#import "rtcplvideomanager.h" #import "rtcplvideo.h" @implementation RTCPLVideoManager // Export prop RCT_EXPORT_VIEW_PROPERTY(onPlayEnd, RCTBubblingEventBlock) NSString) RCT_EXPORT_VIEW_PROPERTY(paused, BOOL) - (UIView *)view { return [[RTCPLVideo alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; } typedef void(^js_call_black)(RTCPLVideo *view); Void js_call: (NSNumber *) node black: void js_call: (NSNumber *) node black: (js_call_black) black { dispatch_async(dispatch_get_main_queue(), ^(){ UIView* temp = [self.bridge.uiManager viewForReactTag:node]; if ([[temp class] isEqual:[RTCPLVideo class]]) { RTCPLVideo* view = (RTCPLVideo*) temp; black(view); }}); } RCT_EXPORT_METHOD(stop: (nonnull NSNumber *) node) {[self js_call:node black:^(RTCPLVideo *view) {// execute the method}]; } @endCopy the code

typescript

import React from 'react'; import {findNodeHandle, requireNativeComponent, UIManager, ViewStyle} from 'react-native'; interface IProps { uri: string; paused: boolean; style? : ViewStyle; onPlayEnd: () => void; } const RTCPLVideo = requireNativeComponent<IProps>('RTCPLVideo'); export default class PLVideo extends React.Component<IProps> { private plVideo? : any; private callNative(name: string, args: Array<any> = []) { const commandId = (UIManager as any).RTCPLVideo.Commands[name]; (UIManager as any).dispatchViewManagerCommand(findNodeHandle(this.plVideo), commandId, args); } private stop() { this.plVideo && this.callNative('stop'); } componentWillUnmount() { this.stop(); } render() { return ( <RTCPLVideo ref={plVideo => this.plVideo = plVideo! } {... this.props}/> ); }}Copy the code

conclusion

In React Native view packages, knowing prop exports, JS -> Native, and Native -> JS can package and export most Native components.