Hello
I’m using openvidu, kurento and react-native to build an app and I’m having the following problem, and that is that the screen that receives the streaming is black, transmits everything well, even on the web you can see the video that is transmitting but in the mobile only the local video is seen, the remote one does not.
This the screenshot of mobile app:
// video component
import React from ‘react’;
import {View, StatusBar, Text, BackHandler} from ‘react-native’;
import {compose, withState} from ‘recompose’;
import {RTCIceCandidate} from ‘react-native-webrtc’;
import InCallManager from ‘react-native-incall-manager’;
import Lottie from ‘lottie-react-native’;
import Display from ‘react-native-display’;
// module libraries
import * as webrtc from ‘…/…/utils/webrtc’;
import ReceiveScreen from ‘./ReceiveScreen’
import processToken from “./utils”;
import {ReleaseMediaSource} from “…/…/utils/webrtc”;
import client from “…/…/utils/client”;
import {showMessage} from “react-native-flash-message”;
import {CREATE_TOKEN} from “./graphql”;
type Props = {}
let i = 0;
let requestID = 0;
let AAA = {};
let participants = {};
let socket = null;
let s = true;
function sendMessage(message) {
if (socket) {
let jsonMessage = JSON.stringify({ id: requestID, …message});
console.log(">>>", jsonMessage);
if (message.method === 'publishVideo') {
AAA[`${requestID}`] = message['__sdp__'];
}
if (message.method === 'receiveVideoFrom') {
AAA[`${requestID}`] = message.params.sender
}
requestID++;
socket.send(jsonMessage);
}
}
class AlertVideoScreen extends React.Component {
state = {
remoteURL: []
};
componentDidMount() {
//
i = 0;
requestID = 0;
AAA = {};
participants = {};
socket = null;
s = true;
//
InCallManager.setSpeakerphoneOn(true);
InCallManager.setKeepScreenOn(true);
// subscribe to back action
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
// create alert
this.createAlert();
}
componentWillUnmount() {
// unsubscribe from back action
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
}
handleBackPress = () => {
if (this.props.loading) {
// make goBack task pending
this.props.setBackPress(true);
// show task in progress
alert('Hay una tarea que se esta ejecutando, espere un momento.');
return true;
}
// close video session
if (this.props.localStream) {
// exit from room
this.leaveRoom();
// release media source
ReleaseMediaSource();
//
InCallManager.setSpeakerphoneOn(true);
InCallManager.setKeepScreenOn(true);
}
// clear chat
if (this.props.socket) {
this.props.socket.close();
}
i = 0;
AAA = {};
participants = {};
// go to back
this.props.navigation.goBack();
return true;
};
joinRoom = (data) => {
this.props.setLoadingMessage('Iniciando\nvideo');
const {token, sessionId} = data;
sendMessage({
"jsonrpc": "2.0",
"method": "joinRoom",
"params": {
token: token,
session: sessionId,
platform: "Chrome 74.0.3729.157 on Linux 64-bit",
metadata: JSON.stringify({
clientData: 'Participant' + Math.floor(Math.random() * 100),
userName: 'Participant' + Math.floor(Math.random() * 100),
// hasAudio: true,
// hasVideo: true
}),
secret: "",
recorder: false
}
});
};
leaveRoom = () => {
sendMessage({
"jsonrpc": "2.0",
"method": "leaveRoom",
"id": "leaveRoom"
});
};
messageProcessHandler = (message) => {
const msg = JSON.parse(message.data);
console.log("<<<", msg);
if (msg.result && msg.result.value === 'pong') {
setTimeout(() => {
sendMessage({"jsonrpc": "2.0", "method": "ping"})
}, 3000)
}
if (msg.error) {
return;
}
if (msg.result && msg.result.metadata) {
this.setState({
userInfo: msg.result
});
//
webrtc.startCommunication(sendMessage, msg.result.id, (stream) => {
this.props.setLocalStream(stream.toURL());
this.props.setLoading(false);
// AAA = msg.result.value;
msg.result.value.forEach((item, index) => {
participants[item.id] = item;
// AAA.push(item);
if (!item['streams'] || !item['streams'][0] || !item['streams'][0].id) {
return;
}
webrtc.receiveVideo(sendMessage, item['streams'][0].id, (pc) => {
pc.onaddstream = (event) => {
const {remoteURL = []} = this.state;
this.setState({remoteURL: [...remoteURL, event.stream]});
};
});
});
});
return
}
if (msg.method === 'iceCandidate') {
webrtc.addIceCandidate(msg.params.senderConnectionId, new RTCIceCandidate(msg.params));
return
}
if (msg.method === 'participantJoined') {
participants[msg.params.id] = msg.params.id;
// webrtc.receiveVideo(sendMessage, msg.params.id, (pc) => {
// pc.onaddstream = (event) => {
// const {remoteURL = []} = this.state;
// this.setState({remoteURL: [...remoteURL, event.stream]});
// };
// });
return;
}
if (msg.method === 'participantPublished') {
participants[msg.params.id] = msg.params.id;
const item = msg.params;
if (!item['streams'] || !item['streams'][0] || !item['streams'][0].id) {
return
}
webrtc.receiveVideo(sendMessage, item['streams'][0].id, (pc) => {
// console.log('webrtc.receiveVideo.pc', pc);
pc.onaddstream = (event) => {
const {remoteURL = []} = this.state;
this.setState({remoteURL: [...remoteURL, event.stream]});
};
});
return;
}
if (msg.result && msg.result.sdpAnswer) {
if (this.state.userInfo.id && s) { // && s
s = false;
webrtc.ProcessAnswer(this.state.userInfo.id, msg.result.sdpAnswer, (err) => {
if (err) {
alert(JSON.stringify(err));
}
});
}
if (this.state.userInfo.id != AAA[`${msg.id}`]) {
webrtc.ProcessAnswer(AAA[`${msg.id}`], msg.result.sdpAnswer, (err) => {
if (err) {
alert(JSON.stringify(err));
}
});
}
return
}
switch (msg.id) {
case 1:
break;
case 'participantLeft':
this.participantLeft(msg.name);
break;
default:
// console.error('Unrecognized message', msg.message);
}
};
participantLeft = (name) => {
if (participants[name]) {
delete participants[name];
}
if (Object.keys(participants).length == 0) {
this.setState({
remoteURL: null
});
}
};
createAlert = () => {
this.props.setLoadingMessage('Obteniendo\nalerta');
const alertId = 'Session10' ; // this.props.navigation.getParam('alertId');
this.props.setAlertId(alertId);
// generate token to connect
this.createToken(alertId);
};
createToken = (sessionId) => {
// make state message
this.props.setLoadingMessage('Creando\ntoken');
client.mutate({
mutation: CREATE_TOKEN,
variables: {
sessionId
}
}).then((response) => {
if (response.data && response.data.createToken) {
if (this.props.goBack) {
this.handleBackPress();
} else {
this.initStreaming(response.data.createToken);
}
} else {
//
this.props.setError(true);
this.props.setLoading(false);
// show error
showMessage({
message: "Error",
description: `Ha ocurrido un error al crear el token para generar el streaming del video`,
type: "danger"
});
// go back
this.handleBackPress();
}
}).catch(error => {
//
this.props.setError(true);
this.props.setLoading(false);
//
showMessage({
message: "Error",
description: `Ha ocurrido el siguiente error al crear el token: ${error.message}`,
type: "danger"
});
// go back
this.handleBackPress();
});
};
initStreaming = (response) => {
const {endpoint} = response;
this.props.setToken(response.token);
// proccess token
const openvidu = processToken(endpoint);
this.props.setOpenVidu(openvidu);
// start websocket connection with
socket = new WebSocket(openvidu.wsUri);
this.props.setSocket(socket);
socket.onopen = () => {
sendMessage({ "jsonrpc":"2.0","method":"ping","params":{"interval":5000} });
this.joinRoom({token: endpoint, sessionId: openvidu.sessionId});
};
socket.onmessage = (message) => {
this.messageProcessHandler(message);
}
};
render() {
if (this.props.loading) {
return (
<View style={{flexGrow: 1, alignItems: "center", justifyContent: "center"}}>
<Text style={{textAlign: 'center'}}>{this.props.loadingMessage}</Text>
<Lottie
source={require('./data.json')}
autoPlay
loop
/>
</View>
)
}
if (this.props.error) {
return (
<View style={{flexGrow: 1, alignItems: "center", justifyContent: "center"}}>
<Lottie
source={require('./error-animation-lottie.json')}
autoPlay
loop
/>
</View>
)
}
return (
<View style={{flexGrow: 1, backgroundColor: '#cacaca'}}>
<StatusBar hidden={true} animated/>
{this.state.backPress && <Text>Espere mientras se detiene la cámara</Text>}
{
this.state.remoteURL && this.state.remoteURL.map((item) => (
<Display enable={item} style={{width: 100, height: 100}} key={item.toURL()}>
<ReceiveScreen
videoURL={item.toURL()}
/>
</Display>
))
}
<View style={{width: 100, height: 100}}>
<ReceiveScreen
videoURL={this.props.localStream}
/>
</View>
<Text>{JSON.stringify(this.state.remoteURL)}</Text>
</View>
)
}
}
export default compose(
// initial state
withState(‘loading’, ‘setLoading’, true),
withState(‘error’, ‘setError’, false),
withState(‘backPress’, ‘setBackPress’, false),
withState(‘loadingMessage’, ‘setLoadingMessage’, ‘’),
withState(‘localStream’, ‘setLocalStream’, null),
withState(‘recording’, ‘setRecording’, false),
withState(‘alertId’, ‘setAlertId’, null),
withState(‘socket’, ‘setSocket’, null),
withState(‘token’, ‘setToken’, null),
withState(‘session’, ‘setSession’, null),
withState(‘subscribers’, ‘setSubscribers’, []),
withState(‘openvidu’, ‘setOpenVidu’, []),
)(AlertVideoScreen);
// webrtc-utils.js
import {
mediaDevices,
RTCPeerConnection,
RTCSessionDescription,
} from ‘react-native-webrtc’;
let pcArray = {};
let isEnableAudio = true;
let isEnableVideo = true;
let localstream = null;
// const ICE_CONFIG = { ‘iceServers’: [{ url: ‘stun:stun.l.google.com:19302’ }] };
const ICE_CONFIG = {
iceServers: [
{
urls: ‘–’
},
{
urls: [
"--"
],
username: '--',
credential: '--'
},
]
};
/**
*
-
@param {*} _sendMessage
-
@param {*} _name
-
@param {*} callback
*/
export function startCommunication(_sendMessage, _name, callback) {
getStream(true).then(stream => {
localstream = stream;
let options = {
mandatory: {
OfferToReceiveAudio: true,
OfferToReceiveVideo: true,
},
};
let pc = createPC(_sendMessage, _name, true, options, false);pcArray[_name] = pc; callback(stream);
});
}
/**
-
@param {*} _sendMessae
-
@param {*} _name
-
@param {*} callback
*/
export function receiveVideo(_sendMessae, _name, callback) {
let options = {
mandatory: {
OfferToReceiveAudio: true,
OfferToReceiveVideo: true,
},
};
let pc = createPC(_sendMessae, _name, true, options, true);
pcArray[_name] = pc;callback(pc);
}
/**
*
*
- @param {*} isFront
-
@param {*} callback
*/
export const getStream = (isFront) => new Promise((resolve, reject) => {
mediaDevices.enumerateDevices().then(sourceInfos => {
let videoSourceId;
for (let i = 0; i < sourceInfos.length; i++) {
const sourceInfo = sourceInfos[i];
if (sourceInfo.kind === ‘videoinput’ && sourceInfo.label === (isFront ? ‘Camera 1, Facing front, Orientation 270’:’’)) {
videoSourceId = sourceInfo.deviceId;
}
}
mediaDevices.getUserMedia({
audio: true,
video: {
mandatory: {
minWidth: 500, // Provide your own width, height and frame rate here
minHeight: 300,
minFrameRate: 30
},
facingMode: (isFront ? “user” : “environment”),
optional: (videoSourceId ? [{sourceId: videoSourceId}] : [])
}
})
.then(stream => {
// Got stream!
resolve(stream)
})
.catch(error => {
// Log error
reject(error);
});
});
});
/**
*
*
*
-
@param {*} sendMessage
-
@param {*} name
-
@param {*} isOffer
-
@param {*} options
*/
export function createPC(sendMessage, name, isOffer, options, type) {
let pc = new RTCPeerConnection(ICE_CONFIG);pc.onnegotiationneeded = () => {
if (isOffer) {
isOffer = false;
createOffer();
}
};pc.onicecandidate = (event) => {
if (event.candidate) {
let msg = {
jsonrpc:“2.0”,
method:“onIceCandidate”,
params: {
candidate: event.candidate.candidate,
endpointName: name,
sdpMid: event.candidate.sdpMid,
sdpMLineIndex: event.candidate.sdpMLineIndex
},
};
sendMessage(msg);
}
};pc.oniceconnectionstatechange = (event) => {
if (event.target.iceConnectionState === ‘disconnected’) {
localstream.release();
localstream = null;
if (pc !== null) {
pc.close();
pc = null;
}
}
};pc.onsignalingstatechange = (event) => {
// console.log(‘onsignalingstatechange’, event);
};// send local stream
pc.addStream(localstream);function createOffer() {
pc.createOffer(options).then(desc => {
pc.setLocalDescription(desc).then(() => {
let msg = {
jsonrpc:“2.0”,
method:“publishVideo”,
params:{
//sender: name,
audioActive: true,
doLoopback: false,
frameRate: 30,
hasAudio: true,
hasVideo: true,
sdpOffer: pc.localDescription.sdp,
typeOfVideo: “CAMERA”,
videoActive: true,
videoDimensions: { width:640, height:480 }
},
“sdp” : name,
};
if (type) {
msg = {
jsonrpc: “2.0”,
method: “receiveVideoFrom”,
params:{
sender: name,
sdpOffer: pc.localDescription.sdp,
},
“sdp” : name,
}
}
sendMessage(msg);
});
});
}return pc;
}
/** -
@param {*} name
-
@param {*} candidate
*/
export function addIceCandidate(name, candidate) {
// const sp = name.split(’_’)[0];
let pc = pcArray[name] || pcArray[${name}_CAMERA
] ; // || pcArray[sp];
if (pc) {
pc.addIceCandidate(candidate);
}
}
/**
*
*
- @param {*} name
- @param {*} sdp
-
@param {*} callback
*/
export function ProcessAnswer(name, sdp, callback) {
let pc = pcArray[name] || pcArray[${name}_CAMERA
];
if (pc) {
let answer = {
‘type’: ‘answer’,
‘sdp’: sdp,
};
pc.setRemoteDescription(new RTCSessionDescription(answer), () => {
callback();
}, err => {
callback(err);
});
}
}
/**
*
*/
export function ReleaseMediaSource() {
if (localstream) {
localstream.release();
localstream = null;
}
if (pcArray !== null) {
for (let mem in pcArray) {
pcArray[mem].close();
delete pcArray[mem];
}
}
}
/**
*
*/
export function toggleAudio() {
if (localstream) {
isEnableAudio = !isEnableAudio;
localstream.getAudioTracks().forEach((track) => {
track.enabled = isEnableAudio;
});
}
return isEnableAudio;
}
/**
*
*/
export function toggleVideo() {
if (localstream) {
isEnableVideo = !isEnableVideo;
localstream.getVideoTracks().forEach((track) => {
track.enabled = isEnableVideo;
});
}
return isEnableVideo;
}
/**
*
*
*/
export function switchVideoType() {
if (localstream) {
localstream.getVideoTracks().forEach(track => {
track._switchCamera();
});
}
}
function logError(error) {
}