blackuy / react-native-twilio-video-webrtc

Twilio Video (WebRTC) for React Native
https://www.twilio.com/docs/video
MIT License
609 stars 404 forks source link

onRoomDidConnect & current.connect get called on Debug but not on Release APK #591

Open dnx-xy opened 2 years ago

dnx-xy commented 2 years ago

Steps to reproduce

  1. run npx react-native run-android
  2. twilioRef.current.connect({accessToken: token)} - Get Video on Both Party well (Debug) but not on Release. `import React, { useState, useRef, useEffect } from "react"; import { StyleSheet, Text, TextInput, View, Image, Dimensions, PermissionsAndroid, Alert, TouchableOpacity, } from "react-native"; import { Button, Modal, Input, Center } from 'native-base' import { Rating } from 'react-native-ratings'; import LottieView from 'lottie-react-native'; import { TwilioVideoLocalView, TwilioVideoParticipantView, TwilioVideo, } from "react-native-twilio-video-webrtc"; import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome'; import { faCamera, faPhoneSlash, faVolumeMute, faVolumeOff } from "@fortawesome/free-solid-svg-icons"; import { Header } from 'react-native-elements'

const CameraView = ({ route, navigation }) => { const { isType, namaDokter, fullname, subjektif, token, consultation } = route.params const [isAudioEnabled, setIsAudioEnabled] = useState(true); const [isVideoEnabled, setIsVideoEnabled] = useState(true); const [status, setStatus] = useState("disconnected"); const [participants, setParticipants] = useState(new Map()); const [videoTracks, setVideoTracks] = useState(new Map()); const [timer, setTimer] = useState('30:00'); const twilioRef = useRef(null); const Ref = useRef(null); const [accessToken, setAccessToken] = useState('') const [roomID, setRoomID] = useState('')

const [review, setReview] = useState('')
const [rating, setRating] = useState('')
const [showModal, setShowModal] = useState(false)
const [showOverlayModal, setOverlayModal] = useState(false)

const stateAccessToken = useRef()
stateAccessToken.current = accessToken;

const stateStatus = useRef()
stateStatus.current = status
const stateRoomID = useRef()
stateRoomID.current = roomID;
const ratingCompleted = (rating) => {
    console.log("Rating is: " + rating)
}

const submitReview = () => {
    let data = {
        consultation_code: consultation,
        rating,
        review,
        mode: 'accept'
    }
    fetch('https://cms.xxx.co.id/api/consultation/personnel/confirm', {
        method: 'POST',
        headers: {
            Authorization: 'Bearer ' + token,
            Accept: 'application/json',
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(data)
    })
        .then(response => response.json())
        .then(responseJson => {
            console.log('review', responseJson)
            if (responseJson.data > 0) {
                alert('Review anda telah kami submit. Review anda sangat membantu kami dalam membangun aplikasi yang lebih baik.')
            } else {
                alert('Review anda gagal di submit')
            }
        })
}
useEffect(() => {
    let isMounted = true
    if (isMounted) {
        console.log('VideoKonsul', status)
        const PermissionRequest = async () => {
            await _requestAudioPermission();
            await _requestCameraPermission();
        }
        PermissionRequest()
        const confirmChat = () => {
            let data = {
                consultation_code: consultation,
                mode: 'accept',
            };
            fetch('https://cms.xxx.co.id/api/consultation/patient/confirm', {
                method: "POST",
                headers: {
                    Authorization: 'Bearer ' + token,
                    Accept: 'application/json',
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(data)
            })
                .then(response => response.json())
                .then(responseJson => {
                    console.log('konsultasimode', responseJson)
                })
        }

        const createRoom = () => {
            let chatRoom = {
                consultation_code: consultation,
                username: fullname,
            }
            fetch('https://cms.xxx.co.id/api/consultation/patient/room', {
                method: "POST",
                headers: {
                    Authorization: 'Bearer ' + token,
                    Accept: 'application/json',
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(chatRoom)
            })
                .then(response => response.json())
                .then(async responseJson => {
                    // console.log('roomIDN', responseJson.room.roomID)
                    await getToken(responseJson.room.roomID)
                    setRoomID(responseJson.room.roomID)
                })
        }

        const getToken = (roomer) => {
            console.log('roomer', roomer, fullname)
            fetch(`http://54.251.173.71:3403/token?identity=${fullname}=&room=${roomer}`)
                .then(response => response.json())
                .then(responseJson => {
                    // console.log('corstwilio', responseJson)
                    setAccessToken(responseJson.token)
                })
        }
        createRoom()
        confirmChat()
        twilioRef.current.connect({accessToken: accessToken})
        setOverlayModal(true)

    }
    return () => { isMounted = false }
}, [])

const getTimeRemaining = (e) => {
    const total = Date.parse(e) - Date.parse(new Date());
    const seconds = Math.floor((total / 1000) % 60);
    const minutes = Math.floor((total / 1000 / 60) % 60);
    const hours = Math.floor((total / 1000 * 60 * 60) % 24);
    return {
        total, hours, minutes, seconds
    };
}
const startTimer = (e) => {
    let { total, minutes, seconds }
        = getTimeRemaining(e);
    if (total >= 0) {
        setTimer(
            (minutes > 9 ? minutes : '0' + minutes) + ':'
            + (seconds > 9 ? seconds : '0' + seconds)
        )
    }
}
const clearTimer = (e) => {
    setTimer('30:00');
    if (Ref.current) clearInterval(Ref.current);
    const id = setInterval(() => {
        startTimer(e);
    }, 1000)
    Ref.current = id;
}

const getDeadTime = () => {
    let deadline = new Date();
    deadline.setSeconds(deadline.getSeconds() + 1800);
    return deadline;
}

const _onConnectButtonPress = () => {
    twilioRef.current.connect({
        accessToken: accessToken,
    })
    setStatus('connecting');
    clearTimer(getDeadTime());
};

const _onEndButtonPress = () => {
    twilioRef.current.disconnect();
    setShowModal(true)
};

const _onMuteButtonPress = () => {
    twilioRef.current
        .setLocalAudioEnabled(!isAudioEnabled)
        .then((isEnabled) => setIsAudioEnabled(isEnabled));
};

const _onFlipButtonPress = () => {
    twilioRef.current.flipCamera();
};

const _onRoomDidConnect = ({ roomName, error }) => {
    console.log('onRoomDidConnect: ', roomName);
    alert('onRoomDidConnect: ', roomName);
        setStatus('connected');
};

const _onRoomDidDisconnect = ({ roomName, error }) => {
    console.log('[Disconnect]ERROR: ', error);
    setStatus('disconnected');
};

const _onRoomDidFailToConnect = error => {
    console.log('[FailToConnect]ERROR: ', error);
    setStatus('disconnected');
};

const _onParticipantAddedVideoTrack = ({ participant, track }) => {
    console.log("onParticipantAddedVideoTrack: ", participant, track);

    setVideoTracks(
        new Map([
            ...videoTracks,
            [
                track.trackSid,
                { participantSid: participant.sid, videoTrackSid: track.trackSid },
            ],
        ])
    );
};

const _onParticipantRemovedVideoTrack = ({ participant, track }) => {
    console.log('onParticipantRemovedVideoTrack: ', participant, track);

    const videoTracksLocal = videoTracks;
    videoTracksLocal.delete(track.trackSid);

    setVideoTracks(videoTracksLocal);
};

const _onNetworkLevelChanged = ({ participant, isLocalUser, quality }) => {
    console.log("Participant", participant, "isLocalUser", isLocalUser, "quality", quality);
};

const _onDominantSpeakerDidChange = ({ roomName, roomSid, participant }) => {
    console.log("onDominantSpeakerDidChange", `roomName: ${roomName}`, `roomSid: ${roomSid}`, "participant:", participant);
};

const _requestAudioPermission = () => {
    return PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
        {
            title: "Need permission to access microphone",
            message:
                "To run this demo we need permission to access your microphone",
            buttonNegative: "Cancel",
            buttonPositive: "OK",
        }
    );
};

const _requestCameraPermission = () => {
    return PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA, {
        title: "Need permission to access camera",
        message: "To run this demo we need permission to access your camera",
        buttonNegative: "Cancel",
        buttonPositive: "OK",
    });
};

const leftHeader = () => {
    return (
        <View style={{ alignItems: 'center', flexDirection: 'row', width: 300, alignItems: 'center' }}>
            <TouchableOpacity onPress={() => goBack()}>
                <Image source={require('../../assets/leftarrow.png')} alt="leftarrow" style={{ width: 16, height: 16, marginTop: 5, marginRight: 5 }} resizeMode="contain" />
            </TouchableOpacity>
            <Text style={{ color: "#333", top: 3, fontFamily: 'poppins-medium' }}>Telekonsultasi</Text>
        </View>
    );
}

return (
    <View style={styles.container}>
        <Header
            backgroundColor="#fff"
            leftComponent={leftHeader}
            containerStyle={{
                shadowColor: '#777',
                shadowOffset: {
                    width: 0,
                    height: 2,
                },
                shadowOpacity: 0.2,
                shadowRadius: 3.84,

                elevation: 5,
                elevation: 2,
            }}
        />
        {
            status === 'disconnected' &&
            <View style={{ margin: 20, flex: 1 }}>
                <View style={{ flex: 1 }}>
                    <Center>
                        <Text style={styles.welcome}>Telekonsultasi Video</Text>
                        <LottieView
                            source={require('../../assets/doctor-waiting.json')}
                            autoPlay
                            loop
                            style={{ width: 300, height: 300 }}
                        />
                        <Text style={{ fontFamily: 'poppins-regular', color: "#333" }}>Hi, <Text style={{ fontFamily: 'poppins-bold' }}>{fullname},</Text></Text>
                        <Text style={{ fontFamily: 'poppins-regular', fontSize: 13, color: "#333" }}>Mohon tunggu sebentar, {namaDokter} akan segera menjawab pertanyaan konsultasi anda.</Text>

                    </Center>
                </View>
                <View>
                    <Text style={{ fontFamily: 'poppins-light', fontSize: 10, marginTop: 10, color: "#999", marginBottom: 10, textAlign: 'center' }}>Jika anda sudah siap, silahkan tekan tombol dibawah ini untuk memulai Telekonsultasi Video</Text>
                    <Button style={{ alignItems: 'flex-end' }} size="md" style={{ backgroundColor: "#0eabaa" }} onPress={() => {
                        fetch(`https://cms.xxx.co.id/api/consultation/patient/history/detail?consultation_code=${consultation}`, {
                            headers: {
                                Authorization: 'Bearer ' + token,
                                Accept: 'application/json',
                                'Content-Type': 'application/json',
                            },
                        })
                            .then(response => response.json())
                            .then(responseJson => {
                                console.log('checkbayar', responseJson)
                                if (responseJson.transaction_status == 'Approve' && responseJson.status == 'Completed') {
                                    twilioRef.current.connect({accessToken: accessToken})
                                    setStatus('connecting');
                                    clearTimer(getDeadTime());
                                } else {
                                    Alert.alert(
                                        'Dokter Belum Memasuki Ruangan',
                                        `${namaDokter} belum memasuki Ruangan Telekonsultasi Ini. Silahkan tunggu beberapa saat lagi.`,
                                        [{ text: 'OK', onPress: () => console.log('OK') }],
                                    );
                                }
                            })
                            .catch(e => alert(e))
                    }}>Mulai Telekonsultasi</Button>
                </View>
            </View>
        }

        {status === 'review' &&
            <View>
                <Center style={{ marginTop: 20 }}>
                    <Text style={styles.welcome}>Video Konsultasi</Text>
                    <Text style={{ color: "#777", fontFamily: 'poppins-medium', marginBottom: 10, marginTop: -5 }}>Dengan {namaDokter}</Text>
                    <Text style={{ color: "#aaa", fontFamily: 'poppins-light', paddingBottom: 0, margin: 20, marginTop: 0, textAlign: 'center' }}>Konsultasi sudah berakhir. Silahkan kirimkan ulasan anda untuk {namaDokter}.</Text>
                    {/* <LottieView
                    source={require('../../assets/videodokter.json')}
                    autoPlay
                    loop
                    style={{ width: 150, height: 150 }}
                /> */}
                    <View style={{ alignItems: 'center', padding: 10 }}>
                        <Image source={require('../../assets/avatar.png')} style={{ width: 50, borderRadius: 100, height: 50 }} resizeMode="contain" />
                        <Text style={{ color: "#444", fontFamily: "poppins-bold", marginTop: 10, fontSize: 20 }}>{namaDokter}</Text>
                        <Rating
                            type='star'
                            ratingCount={5}
                            imageSize={25}
                            showRating
                            onFinishRating={ratingCompleted}
                        />
                    </View>
                    <Input onChangeText={setReview} value={review} w="90%" inputStyle={{ color: "#333", fontFamily: 'poppins-regular' }} style={{ marginTop: 20, borderWith: 1, borderColor: "#ccc" }} numberOfLines={3} placeholder="Masukan komentar anda..." />
                    <Button
                        style={{ marginTop: 10, width: '90%' }}
                        onPress={() => {
                            submitReview()
                            navigation.navigate('Home', {
                                screen: 'Homescreen'
                            })
                        }}
                    >
                        Submit
                    </Button>
                </Center>
            </View>
        }

        {(status === "connected" || status === "connecting") && (
            <View style={styles.callContainer}>
                {status === "connected" && (
                    <View style={styles.remoteGrid}>
                        {Array.from(videoTracks, ([trackSid, trackIdentifier]) => {
                            return (
                                <TwilioVideoParticipantView
                                    style={styles.remoteVideo}
                                    key={trackSid}
                                    trackIdentifier={trackIdentifier}
                                />
                            );
                        })}
                    </View>
                )}
                <View style={styles.optionsContainer}>
                    <Text style={{ color: "#333", fontSize: 24, fontFamily: 'poppins-medium', position: 'absolute', top: -110, left: 20 }}>{namaDokter}</Text>
                    <Text style={{ color: "#0eabaa", fontSize: 14, fontFamily: 'poppins-medium', position: 'absolute', top: -75, left: 20, width: 260 }}>Video Konsultasi dengan {fullname}</Text>
                    <View style={{ flexDirection: 'row', position: 'absolute', top: -30, left: 20, backgroundColor: "#eee", borderRadius: 100, padding: 5 }}>
                        <Text style={{ backgroundColor: "red", borderRadius: 100, height: 12, width: 12, marginTop: 5, marginRight: 10 }}></Text>
                        <Text style={{ color: "#222", fontSize: 14, fontFamily: 'poppins-medium' }}>{timer}</Text>
                    </View>
                    <TouchableOpacity
                        style={styles.optionButton}
                        onPress={_onEndButtonPress}
                    >
                        <FontAwesomeIcon color="#fff" size={23} icon={faPhoneSlash} />
                    </TouchableOpacity>
                    <TouchableOpacity
                        style={styles.optionButton}
                        onPress={_onMuteButtonPress}
                    >
                        {isAudioEnabled ?
                            (
                                <FontAwesomeIcon color="#fff" size={25} icon={faVolumeMute} />
                            )
                            :
                            (<FontAwesomeIcon color="#fff" size={25} icon={faVolumeOff} />
                            )}
                    </TouchableOpacity>
                    <TouchableOpacity
                        style={styles.optionButton}
                        onPress={_onFlipButtonPress}
                    >
                        <FontAwesomeIcon color="#fff" size={23} icon={faCamera} />
                    </TouchableOpacity>
                    <TwilioVideoLocalView enabled={true} style={styles.localVideo} />
                </View>
            </View>
        )}

        <TwilioVideo
            ref={twilioRef}
            onRoomDidConnect={_onRoomDidConnect}
            onRoomDidDisconnect={_onRoomDidDisconnect}
            onRoomDidFailToConnect={_onRoomDidFailToConnect}
            onParticipantAddedVideoTrack={_onParticipantAddedVideoTrack}
            onParticipantRemovedVideoTrack={_onParticipantRemovedVideoTrack}
            onNetworkQualityLevelsChanged={_onNetworkLevelChanged}
            onDominantSpeakerDidChange={_onDominantSpeakerDidChange}
        />
        <Modal isOpen={showModal} onClose={() => setShowModal(false)}>
            <Modal.Content maxWidth="400px" >
                <Modal.Header >
                    <View style={{ flexDirection: 'row', justifyContent: 'space-between',alignSelf:'flex-start'}}>
                        <Image source={require('../../assets/avatar.png')} style={{ width: 40, height: 40 }} resizeMode="contain" />
                        <View style={{ flexDirection: 'column',alignSelf:'flex-start' }}>
                            <Text style={{ color: "#333", fontFamily: "poppins-medium" }}>Konsultasi Online</Text>
                            <Text style={{ color: "#333", fontFamily: "poppins-regular", fontSize: 10 }}>04 Juli 2020, 07:35 - 07:40</Text>
                        </View>
                        {/* <Text style={{ color: "#333", fontSize: 10 }}>3KO-KO251244</Text> */}
                    </View>
                </Modal.Header>
                <Modal.Body style={{ backgroundColor: "#fff" }}>
                    <View style={{ alignItems: 'center', padding: 10 }}>
                        <Image source={require('../../assets/avatar.png')} style={{ width: 50, borderRadius: 100, height: 50 }} resizeMode="contain" />
                        <Text style={{ color: "#444", fontFamily: "poppins-bold", marginTop: 10 }}>{namaDokter}</Text>
                        <Rating
                            type='star'
                            ratingCount={5}
                            imageSize={30}
                            showRating
                            onFinishRating={ratingCompleted}
                        />
                        <Input onChangeText={setReview} w="100%" mt="5" value={review} inputStyle={{ color: "#333", fontFamily: 'poppins-regular' }} numberOfLines={3} placeholder="Masukan komentar anda..." />
                    </View>
                </Modal.Body>
                <Modal.Footer>
                    <Button.Group space={2}>
                        <Button
                            variant="ghost"
                            colorScheme="blueGray"
                            onPress={() => {
                                setShowModal(false)
                            }}
                        >
                            Cancel
                        </Button>
                        <Button
                            onPress={() => {
                                submitReview()
                                setShowModal(false)
                                navigation.navigate('Home', {
                                    screen: 'Homescreen'
                                })
                            }}
                        >
                            Submit
                        </Button>
                    </Button.Group>
                </Modal.Footer>
            </Modal.Content>
        </Modal>

    </View>

);

}; export default CameraView; `

Expected behaviour

twilioRef.current.connect should call _onDidConnect method

Actual behaviour

No Participant Video nor Client on Room | _onDidConnect doesnt called. No Video Remote Participantt (RELEASE)

But Works On Debug

_onDidConnect doesn't fire no video remote show

Environment

react-native-twilio-video-webrtc

Version: npm version or "master" Latest 2.10

slycoder commented 2 years ago

Working in dev and not working in prod often speaks to a race condition. I noticed that in a few places you were making calls without actually ensuring the previous promises were resolved. I would consider adding some logging to ensure that events are actually occurring in the order you expect them to, and to be more consistent in your use of promises/await/async.

dnx-xy commented 2 years ago

hi, @slycoder thanks for the comment. I actualy pass the token as params in _onConnectButtonPress.. On Debug mode it works flawlessly. I thinks it because race condition you mention. But i have clean up some codes with just passing / fetching API for token and roomName, & passing it into _onConnectButton press in Production still doesnt kicking the onRoomDidConnect.

Does it have something to do with the Reff or anything elsel, or maybe you could shed some light about why it doesn't connect on production

`const {consultation, token,fullname} = route.params const createRoom = () => { let chatRoom = { consultation_code: consultation, username: fullname, } fetch('https://cms.xxx.co.id/api/consultation/patient/room', { method: "POST", headers: { Authorization: 'Bearer ' + token, Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify(chatRoom) }) .then(response => response.json()) .then(async responseJson => { console.log('roomIDN', responseJson.room.roomID) await getToken(responseJson.room.roomID) setRoomID(responseJson.room.roomID) }) }

        const getToken = (roomer) => {
            console.log('roomer', roomer, fullname)
            fetch(`http://54.xxx.173.71:3403/token?identity=${fullname}=&room=${roomer}`)
                .then(response => response.json())
                .then(async responseJson => {
                    console.log('corstwilio', responseJson)
                    setAccessToken(responseJson.token)
                    await _onConnectButtonPress(responseJson.token,roomer)
                })
        }
        const _onConnectButtonPress = async(tokenVids, roomer) => {
            if(Platform.OS == 'android'){
                await _requestAudioPermission();
                await _requestCameraPermission();
            }
            twilioRef.current.connect({
                accessToken: tokenVids,
                roomName: roomer
            })
            setStatus('connecting');
            clearTimer(getDeadTime());
        };`

        I'm trying to turn off/on hermes to see if it works without it
dnx-xy commented 2 years ago

I have solved the problem. Apparently due to Android restriction cannot resolve from HTTP connection on release version

My token get from http connection. Thanks

Nziranziza commented 2 years ago

I am having similar issue even if I am able to fetch the token

pierroo commented 10 months ago

I have solved the problem. Apparently due to Android restriction cannot resolve from HTTP connection on release version

My token get from http connection. Thanks

would you mind sharing how did you actuall solve the issue?

dnx-xy commented 8 months ago

Just enable http clear traffic true in Android.xml