aws-samples / amazon-chime-react-native-demo

A React Native demo application for Android and iOS using the Amazon Chime SDK.
MIT No Attribution
102 stars 24 forks source link

[RCTView renderFrameWithFrame:]: unrecognized selector sent to instance 0x106316210" --- iOS #20

Closed guillaume-g closed 4 years ago

guillaume-g commented 4 years ago

Hi guys,

Thanks for your work ! I have been working the past few weeks with infos that you provided on this repo, and I have successfully developed a meeting application with visio and audio on Android.

Unfortunately, I can't make in work on iOS... When entering the room, I can hear the audio, but when I activate my camera, everything crash with the following error :

[RCTView renderFrameWithFrame:]: unrecognized selector sent to instance 0x106316210"


Here is my Meeting.tsx

/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: MIT-0
 */

import React from 'react';
import { View, Text, Alert, FlatList, TouchableHighlight } from 'react-native';
import styles from '../assets/style';
import { getSDKEventEmitter, MobileSDKEvent, MeetingError } from '../utils/bridge';
import { RNVideoRenderView } from './RNVideoRenderView';
import { AttendeeItem } from '../components/AttendeeItem';
import { NativeFunction } from '../utils/bridge';

// Maps attendee Id to attendee Name
const attendeeNameMap = {};

export class Meeting extends React.Component {
  constructor() {
    super();
    this.state = {
      attendees: [],
      videoTiles: [],
      mutedAttendee: [],
      selfVideoEnabled: false,
      meetingTitle: '',
      screenShareTile: null
    };
  }

  componentDidMount() {
    /**
     * Attendee Join and Leave handler
     */
    this.onAttendeesJoinSubscription = getSDKEventEmitter().addListener(MobileSDKEvent.OnAttendeesJoin, ({ attendeeId, externalUserId }) => {
      console.log('onAttendeesJoinSubscription : ', { attendeeId, externalUserId });
      if (!(attendeeId in attendeeNameMap)) {
        // The externalUserId will be a format such as c19587e7#Alice
        attendeeNameMap[attendeeId] = externalUserId.split("#")[1] || attendeeId;
      }
      this.setState((oldState) => ({
        ...oldState,
        attendees: oldState.attendees.concat([attendeeId])
      }));
    });

    this.onAttendeesLeaveSubscription = getSDKEventEmitter().addListener(MobileSDKEvent.OnAttendeesLeave, ({ attendeeId }) => {
      console.log('onAttendeesLeaveSubscription : ', { attendeeId });
      this.setState((oldState) => ({
        ...oldState,
        attendees: oldState.attendees.filter((attendeeToCompare => attendeeId != attendeeToCompare))
      }));
    });

    /**
     * Attendee Mute & Unmute handler
     */
    this.onAttendeesMuteSubscription = getSDKEventEmitter().addListener(MobileSDKEvent.OnAttendeesMute, attendeeId => {
      console.log('onAttendeesMuteSubscription : ', { attendeeId });
      this.setState((oldState) => ({
        ...oldState,
        mutedAttendee: oldState.mutedAttendee.concat([attendeeId])
      }));
    });

    this.onAttendeesUnmuteSubscription = getSDKEventEmitter().addListener(MobileSDKEvent.OnAttendeesUnmute, attendeeId => {
      console.log('onAttendeesUnmuteSubscription : ', { attendeeId });
      this.setState((oldState) => ({
        ...oldState,
        mutedAttendee: oldState.mutedAttendee.filter((attendeeToCompare => attendeeId != attendeeToCompare))
      }));
    });

    /**
     * Video tile Add & Remove Handler
     */
    this.onAddVideoTileSubscription = getSDKEventEmitter().addListener(MobileSDKEvent.OnAddVideoTile, (tileState) => {
      console.log('onAddVideoTileSubscription : ', { tileState });
      this.setState(oldState => ({
        ...oldState,
        videoTiles: [...oldState.videoTiles, tileState.tileId],
        selfVideoEnabled: tileState.isLocal ? true : oldState.selfVideoEnabled
      }));
    });

    this.onRemoveVideoTileSubscription = getSDKEventEmitter().addListener(MobileSDKEvent.OnRemoveVideoTile, (tileState) => {
      console.log('onRemoveVideoTileSubscription : ', { tileState });
      this.setState(oldState => ({
        ...oldState,
        videoTiles: oldState.videoTiles.filter(tileIdToCompare => tileIdToCompare != tileState.tileId),
        selfVideoEnabled: tileState.isLocal ? false : oldState.selfVideoEnabled
      }));
    });

    /**
     * General Error handler
     */
    this.onErrorSubscription = getSDKEventEmitter().addListener(MobileSDKEvent.OnError, (errorType) => {
      console.log('onErrorSubscription : ', { errorType });
      console.log('errorType', errorType);
      switch(errorType) {
        case MeetingError.OnMaximumConcurrentVideoReached:
          Alert.alert("Failed to enable video", "maximum number of concurrent videos reached!");
          break;
        default:
          Alert.alert("Error", errorType);
          break;
      }
    });
  }

  componentWillUnmount() {
    if (this.onAttendeesJoinSubscription) {
      this.onAttendeesJoinSubscription.remove();
    }
    if (this.onAttendeesLeaveSubscription) {
      this.onAttendeesLeaveSubscription.remove();
    }
    if (this.onAttendeesMuteSubscription) {
      this.onAttendeesMuteSubscription.remove();
    }
    if (this.onAttendeesUnmuteSubscription) {
      this.onAttendeesUnmuteSubscription.remove();
    }
    if (this.onAddVideoTileSubscription) {
      this.onAddVideoTileSubscription.remove();
    }
    if (this.onRemoveVideoTileSubscription) {
      this.onRemoveVideoTileSubscription.remove();
    }
    if (this.onErrorSubscription) {
      this.onErrorSubscription.remove();
    }
  }

  render() {
    return (
      <View style={[styles.container, { justifyContent: 'flex-start' }]}>
        <Text style={styles.title}>{this.props.meetingTitle}</Text>
        <View style={styles.buttonContainer}>
          <TouchableHighlight disabled={this.state.selfVideoEnabled} onPress={() => NativeFunction.setCameraOn(!this.state.selfVideoEnabled)}>
            <Text>Allumer ma caméra</Text>
          </TouchableHighlight>
        </View>
        <Text style={styles.title}>Video</Text>
        <View style={styles.videoContainer}>
          {
            this.state.videoTiles.length > 0 ? this.state.videoTiles.map(tileId =>
              <RNVideoRenderView style={styles.video} key={tileId} tileId={tileId} />
            ) : <Text style={styles.subtitle}>No one is sharing video at this moment</Text>
          }
        </View>
        <Text style={styles.title}>Attendee</Text>
        <FlatList
          style={styles.attendeeList}
          data={this.state.attendees}
          renderItem={({ item }) => <AttendeeItem attendeeName={attendeeNameMap[item] ? attendeeNameMap[item] : item} muted={this.state.mutedAttendee.includes(item)} />}
          keyExtractor={(item) => item}
        />
      </View>
    );
  }
}

When I use NativeFunction.setCameraOn, I got the following error : [RCTView renderFrameWithFrame:]: unrecognized selector sent to instance 0x106316210


Because calling setCameraOn while instantiate a component RNVideoRenderView, I have done some digging and the exact function that make everything go crazy is NativeFunction.bindVideoView(findNodeHandle(this), this.props.tileId).

Here is my RNVideoRenderView.tsx

/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: MIT-0
 */

import React from 'react';
import {requireNativeComponent, findNodeHandle, StyleSheet, View } from 'react-native';
import { NativeFunction } from '../utils/bridge';

interface Props {
    /**
     * An int value to identify the Video view, will be used to bind video stream later
     */
    tileId: number;
}

export const LiveStreamStyles = StyleSheet.create({
    container: {
        width: '100%',
        height: 400,
        flexDirection: 'column',
        justifyContent: 'center',
        alignItems: 'center',
    },
    camera: {
        width: '100%',
        height: '100%',
    },
});

export class RNVideoRenderView extends React.Component<Props> {

    componentDidMount() {
        // we need to delay the bind video
        // Because "componentDidMount" will be called "immediately after the initial rendering occurs"
        // This is *before* RCTUIManager add this view to register (so that viewForReactTag() can return a view)
        // So we need to dispatch bindVideoView after this function complete
        setTimeout(() => {
            NativeFunction.bindVideoView(findNodeHandle(this), this.props.tileId);
        });
    }

    componentWillUnmount() {
        NativeFunction.unbindVideoView(this.props.tileId);
    }

    render() {
        return <View style={LiveStreamStyles.container}>
            <RNVideoRenderViewNative {...this.props} style={LiveStreamStyles.camera} />
        </View>
    }
}

// RNVideoRenderView.propTypes = {
    /**
     * A int value to identifier the Video view, will be used to bind video stream later
     */
//     tileId: PropTypes.number,
// };

const RNVideoRenderViewNative = requireNativeComponent('RNVideoView', RNVideoRenderView);

/*export const RNVideoRenderView = ({
    tileId,
}: Props) => {
    const ref = useMemo(() => createRef(), []);

    useEffect(() => {
        // @ts-ignore
        setTimeout(() => {
            if (ref.current) {
                NativeFunction.bindVideoView(findNodeHandle(ref), tileId)
            }
        });
        return () => NativeFunction.unbindVideoView(tileId);
    }, [ref]);

    console.log('RNVideoRenderViewNative');
    console.log(RNVideoRenderViewNative);
    // @ts-ignore
    return <RNVideoRenderViewNative ref={ref} tileId={tileId} />;
};

// @ts-ignore
const RNVideoRenderViewNative = requireNativeComponent('RNVideoView', RNVideoRenderView);*/

I hope that my English was good enough, and if you want anymore informations, I can provide it quickly...

Thanks in advance for your help 🙏

I am using the latest iOS 13 with the latest xCode version. React Native 0.62

tianyux-amz commented 4 years ago

Hi @guillaume-g ,

Thank you for reaching out.

Did you change the implementation of bindVideoView in the sample native bridge? You need to bind the video tile to a class that implements VideoRenderView protocol, in which renderFrame() was defined. Otherwise, it may throw an error because renderFrameWithFrame was not implemented on your videoRenderView.

Let me know if you have more questions or concerns.

Best, Tianyu

guillaume-g commented 4 years ago

Hi @tianyux-amz, sorry for the delay.

I have copy pasted everything, so everything should be like in the demo...

I will try something from the ground up and let you know if it's fix and related to my old project.

Thansk for your time