react-native-webrtc 不显示远程流

问题描述 投票:0回答:1

嘿,我正在尝试创建一个视频通话功能,在我的代码中,我已经完成了所有操作(就我对 WebRTC 的了解而言,因为我刚接触 WebRTC 5 天),例如交换报价和答案、交换候选人,但出于某种原因我我无法看到远程流(对方视频和音频),甚至也没有收到任何错误,这里我有 3 个屏幕 CallingScreen(其中发生呼叫并且 2 个对等方正常通信)、OutCallScreen(其中一个用户创建报价并将其发送给另一方),InCallScreen(用户收到通知,他正在接到来电,并创建应答并将其发送回报价所有者),我还有 WebSocket 服务器,我可以在其中获取报价、应答,候选人并将他们发送到各自的目的地,请检查这些并让我知道我在这些中缺少什么,我无法看到远程流。

我希望提供所有这 4 个完整文件可能有助于解决问题

CallingScreen.js

import { StyleSheet, Text, View, Pressable, Image } from 'react-native';
import React, { useState, useRef, useEffect } from 'react';
import { Height, Width } from '../utils';
import { moderateScale } from 'react-native-size-matters';
import { ScreenCapturePickerView, RTCPeerConnection, RTCIceCandidate, RTCSessionDescription, RTCView, MediaStream, MediaStreamTrack, mediaDevices, registerGlobals } from 'react-native-webrtc';
import { setDoc, collection, doc, getDocs, onSnapshot } from 'firebase/firestore';
import { FIREBASE_DB, FIREBASE_AUTH } from '../firebaseConfig';
import { encryptData, decryptData } from '../EncryptData'
import AsyncStorage from '@react-native-async-storage/async-storage';
import ws from './WebSocketConn';
import { peerConnection } from '../CallingUtils';

import Constants from 'expo-constants';
const SECRET_KEY = Constants.expoConfig.extra.SECRET_KEY;

const peerConstraints = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] };

const CallingScreen = ({ navigation, route }) => {
  const [localMediaStream, setLocalMediaStream] = useState(null);
  const [remoteMediaStream, setRemoteMediaStream] = useState(null);
  const [isVoiceOnly, setIsVoiceOnly] = useState(false);
  const { DocId, answer, recipientId } = route.params;


  useEffect(() => {
    getMediaStream();
    let localDescriptionSet = false;
  }, []);

  const getMediaStream = async () => {
    const mediaConstraints = {
      audio: true,
      video: true,
    };
    try {
      const stream = await mediaDevices.getUserMedia(mediaConstraints);
      setLocalMediaStream(stream);

    } catch (error) {
      console.error('Error getting media stream:', error);
    }
  };

  const hangup = async () => {
    try {
      if (peerConnection) {
        peerConnection.close();
      }
      if (localMediaStream) {
        localMediaStream.getTracks().forEach(track => track.stop());
      }
    } catch (error) {
      console.error('Error hanging up:', error);
    } finally {
      setLocalMediaStream(null);
      setRemoteMediaStream(null);
    }
  };

  const handleIceCandidates = async () => {
    try {

      localMediaStream.getTracks().forEach(track => {
        peerConnection.addTrack(track, localMediaStream);
      });

      peerConnection.onicecandidate = async (event) => {
        if (event.candidate) {
          console.log('sending the candidate to', recipientId, 'by', CustomUUID);
          const idToken = await FIREBASE_AUTH.currentUser.getIdToken();
          const encryptedIdToken = encryptData(idToken, SECRET_KEY);
          const CustomUUID = await AsyncStorage.getItem('CustomUUID');
          console.log('my id is', CustomUUID, 'and i am sending candidate to', recipientId);
          const candidateMessage = {
            type: 'candidate',
            userId: CustomUUID,
            recipientId: recipientId,
            idToken: encryptedIdToken,
            candidate: event.candidate
          };
          const candidateMessageString = JSON.stringify(candidateMessage);
          ws.send(candidateMessageString);
        }
      };

      ws.onmessage = async (event) => {
        console.log('candidate, being set in CallingScreen');
        const message = JSON.parse(event.data);
        if (message.type === 'candidate') {
          try {
            const candidate = new RTCIceCandidate(message.candidate);
            await peerConnection.addIceCandidate(candidate);
          } catch (error) {
            console.error('Error handling ICE candidate:', error);
          }
        }
      };

      peerConnection.ontrack = (event) => {
        const remoteStream = event.streams[0];
        setRemoteMediaStream(remoteStream);
      };

    } catch (error) {
      console.error('Error handling incoming answer:', error);
    }
  }


  useEffect(() => {
    if (answer && recipientId && localMediaStream) {
      handleIceCandidates();
    }
  }, [answer])



  return (
    <View style={styles.Container}>
      <View style={styles.CallingTop}>

        {localMediaStream ? <RTCView mirror={true} objectFit={'cover'} streamURL={localMediaStream.toURL()} zOrder={0} style={styles.CallingTopImage} />
          : <Image source={require('../assets/Images/call1.jpg')} style={styles.CallingTopImage} />}

        <View style={styles.CallingTopNameView}>
          <Image source={require('../assets/Images/CallingNameShadow.png')} style={styles.CallingTopNameShadowImage} />
          <View style={styles.CallerDetails}>
            <Image source={require('../assets/Images/call1.jpg')} style={styles.CallerDetailsImage} />
            <Text style={styles.CallerDetailsName}>Yung-Chen</Text>
          </View>
        </View>
      </View>
      <View style={styles.CallingBottom}>

        {remoteMediaStream ? <RTCView mirror={true} objectFit={'cover'} streamURL={remoteMediaStream.toURL()} zOrder={0} style={styles.CallingBottomImage} />
          : <Image source={require('../assets/Images/call2.jpg')} style={styles.CallingBottomImage} />}

        <View style={styles.CallingBottomCallOptions}>
          <Pressable style={styles.CallOptionsButton}>
            <Image source={require('../assets/Icons/CallMuteIcon.png')} style={styles.CallOptionsButtonImage} />
          </Pressable>
          <Pressable style={styles.CallOptionsButton}>
            <Image source={require('../assets/Icons/CallMicIcon.png')} style={styles.CallOptionsButtonImage} />
          </Pressable>
          <Pressable style={styles.CallOptionsButton}>
            <Image source={require('../assets/Icons/CallCameraOption.png')} style={styles.CallOptionsButtonImage} />
          </Pressable>
          <Pressable onPress={() => { hangup() }} style={styles.CallHangUpOptionsButton}>
            <Image source={require('../assets/Icons/CallHangUpIcon.png')} style={styles.CallHangUpOptionsButtonImage} />
          </Pressable>
        </View>
      </View>
    </View>
  )
}

export default CallingScreen

OutCallingScreen.js

import { StyleSheet, Text, View, Pressable, Image } from 'react-native';
import React, { useState, useRef, useEffect } from 'react';
import { Height, Width } from '../utils';
import { moderateScale } from 'react-native-size-matters';
import { ScreenCapturePickerView, RTCPeerConnection, RTCIceCandidate, RTCSessionDescription, RTCView, MediaStream, MediaStreamTrack, mediaDevices, registerGlobals } from 'react-native-webrtc';
import { setDoc, addDoc, collection, doc, getDocs, onSnapshot, Timestamp } from 'firebase/firestore';
import { FIREBASE_DB, FIREBASE_AUTH } from '../firebaseConfig';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { encryptData, decryptData } from '../EncryptData'
import ws from './WebSocketConn';
import { peerConnection } from '../CallingUtils';

import Constants from 'expo-constants';
const SECRET_KEY = Constants.expoConfig.extra.SECRET_KEY;

const peerConstraints = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] };


const OutCallScreen = ({ navigation, route }) => {

  const [localMediaStream, setLocalMediaStream] = useState(null);
  const [CustomUUID, setCustomUUID] = useState(null);
  const { chatId } = route.params;

  AsyncStorage.getItem('CustomUUID').then((CustomUUID) => {
    setCustomUUID(CustomUUID);
  });

  useEffect(() => {
    getMediaStream();

    return () => {
      if (peerConnection) {
        peerConnection.close();
      }
      if (localMediaStream) {
        localMediaStream.getTracks().forEach(track => track.stop());
      }
    };
  }, []);

  const getMediaStream = async () => {
    const mediaConstraints = {
      audio: true,
      video: true,
    };
    try {
      const stream = await mediaDevices.getUserMedia(mediaConstraints);
      setLocalMediaStream(stream);
    } catch (error) {
      console.error('Error getting media stream:', error);
    }
  };

  const createOffer = async () => {
    try {
      localMediaStream.getTracks().forEach(track => {
        peerConnection.addTrack(track, localMediaStream);
      });

      const offerDescription = await peerConnection.createOffer();
      await peerConnection.setLocalDescription(offerDescription);

      const idToken = await FIREBASE_AUTH.currentUser.getIdToken();
      const encryptedIdToken = encryptData(idToken, SECRET_KEY);
      console.log('sending offer to', CustomUUID === chatId.split('_')[0] ? chatId.split('_')[1] : chatId.split('_')[0], 'by', CustomUUID);
      const offerStructure = {
        senderId: CustomUUID,
        recipientId: CustomUUID === chatId.split('_')[0] ? chatId.split('_')[1] : chatId.split('_')[0],
        status: 'pending',
        timestamp: Timestamp.now(),
        offerDescription: offerDescription,
      }
      const encryptedOfferStructure = encryptData(JSON.stringify(offerStructure), SECRET_KEY);

      ws.send(JSON.stringify({
        type: 'offer',
        userId: CustomUUID,
        idToken: encryptedIdToken,
        offer: encryptedOfferStructure
      }));

    } catch (error) {
      console.error('Error creating offer:', error);
    }
  };


  useEffect(() => {
    if (chatId && CustomUUID && localMediaStream) {
      createOffer();
    }
    ws.onmessage = async (event) => {
      const parsedMessage = JSON.parse(event.data);
      if (parsedMessage.type === 'answer') {
        const answerDescription = new RTCSessionDescription(parsedMessage.answer);
        await peerConnection.setRemoteDescription(answerDescription);
        console.log('got the answer from ', parsedMessage.userId);
        navigation.navigate('CallingScreen', { answer: parsedMessage.answer, recipientId: parsedMessage.userId });
      }
    }
  }, [chatId, CustomUUID, localMediaStream]);



  return (
    <View style={styles.Container}>
      {localMediaStream ? <RTCView mirror={true} objectFit={'cover'} streamURL={localMediaStream.toURL()} zOrder={0} style={styles.CallingTopImage} />
        : <Image source={require('../assets/Images/call1.jpg')} style={styles.CallingTopImage} />}
      <View style={styles.backgroundShadowCover}>
        <View style={styles.recipientInfo}>
          <Image source={require('../assets/Images/call1.jpg')} style={styles.recipientImage} />
          <Text style={styles.recipientName}>Mei Ling</Text>
          <Text style={styles.recipientCallTime}>Connecting</Text>
        </View>
        <View style={styles.CallingOptions}>
          <Pressable style={styles.VideoToggleButton}>
            <Image source={require('../assets/Icons/CallCameraOption.png')} style={styles.VideoToggleButtonImage} />
          </Pressable>
          <Pressable style={styles.hangUpButton}>
            <Image source={require('../assets/Icons/CallHangUpIcon.png')} style={styles.hangUpButtonImage} />
          </Pressable>
          <Pressable style={styles.MicToggleButton}>
            <Image source={require('../assets/Icons/CallMicIcon.png')} style={styles.MicToggleButtonImage} />
          </Pressable>
        </View>
      </View>
    </View>
  )
}

export default OutCallScreen

InCallScreen.js

import { StyleSheet, Text, View, Pressable, Image } from 'react-native';
import React, { useState, useRef, useEffect } from 'react';
import { Height, Width } from '../utils';
import { moderateScale } from 'react-native-size-matters';
import { ScreenCapturePickerView, RTCPeerConnection, RTCIceCandidate, RTCSessionDescription, RTCView, MediaStream, MediaStreamTrack, mediaDevices, registerGlobals } from 'react-native-webrtc';
import { setDoc, collection, doc, getDocs, onSnapshot } from 'firebase/firestore';
import { FIREBASE_DB, FIREBASE_AUTH } from '../firebaseConfig';
import { encryptData, decryptData } from '../EncryptData'
import AsyncStorage from '@react-native-async-storage/async-storage';
import ws from './WebSocketConn';
import { peerConnection } from '../CallingUtils';


import Constants from 'expo-constants';
const SECRET_KEY = Constants.expoConfig.extra.SECRET_KEY;

const peerConstraints = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] };

const InCallScreen = ({ navigation, route }) => {

  const [localMediaStream, setLocalMediaStream] = useState(null);
  const { offer, DocId } = route.params;

  useEffect(() => {
    getMediaStream();

    return () => {
      if (localMediaStream) {
        localMediaStream.getTracks().forEach(track => track.stop());
      }
    };
  }, []);

  const getMediaStream = async () => {
    const mediaConstraints = {
      audio: true,
      video: true,
    };
    try {
      const stream = await mediaDevices.getUserMedia(mediaConstraints);
      setLocalMediaStream(stream);
    } catch (error) {
      console.error('Error getting media stream:', error);
    }
  };

  const AccpetCall = async () => {
    try {
      const idToken = await FIREBASE_AUTH.currentUser.getIdToken();
      const encryptedIdToken = encryptData(idToken, SECRET_KEY);
      const response = await fetch(`http://10.0.2.2:5000/users/UpdateCallStatus`, {
        method: 'PUT',
        credentials: 'include',
        headers: {
          'Authorization': 'Bearer ' + encryptedIdToken,
          'Accept': 'application/json',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ DocId: DocId, status: 'Accepted' })
      })
      const data = await response.json();
      if (response.status == 200) {
        createAnswer()
      }
    } catch (error) {
      console.error('error, updating call status, and creating answer:', error)
    }
  }

  const createAnswer = async () => {
    try {
      if (!peerConnection) {
        console.error('Peer connection is not initialized');
        return;
      }

      localMediaStream.getTracks().forEach(track => {
        peerConnection.addTrack(track, localMediaStream);
      });

      // Set remote description from the offer
      const offerDescription = new RTCSessionDescription(offer.offerDescription);
      await peerConnection.setRemoteDescription(offerDescription);

      // Create answer and set it as local description
      const answer = await peerConnection.createAnswer();
      await peerConnection.setLocalDescription(answer);

      // Get CustomUUID
      const CustomUUID = await AsyncStorage.getItem('CustomUUID');

      // Get idToken and encrypt it
      const idToken = await FIREBASE_AUTH.currentUser.getIdToken();
      const encryptedIdToken = encryptData(idToken, SECRET_KEY);

      ws.onmessage = async (event) => {
        console.log('candidate, being set in InCallScreen');
        const message = JSON.parse(event.data);
        if (message.type === 'candidate') {
          try {
            const candidate = new RTCIceCandidate(message.candidate);
            await peerConnection.addIceCandidate(candidate);
          } catch (error) {
            console.error('Error handling ICE candidate:', error);
          }
        }
      };

      console.log(offer.senderId);
      // Send ICE candidates to the offering peer
      peerConnection.onicecandidate = async (event) => {
        console.log('candidate, being sended by InCallScreen');
        console.log('sending candidate by', CustomUUID, 'to', offer.senderId);
        if (event.candidate) {
          const candidateMessage = {
            type: 'candidate',
            userId: CustomUUID,
            recipientId: offer.senderId,
            idToken: encryptedIdToken,
            candidate: event.candidate
          };
          const candidateMessageString = JSON.stringify(candidateMessage);
          ws.send(candidateMessageString);
        }
      };

      // Send the answer message to the offering peer
      const answerMessage = {
        type: 'answer',
        userId: CustomUUID,
        recipientId: offer.senderId,
        idToken: encryptedIdToken,
        answer: answer
      };
      console.log('sending answer by', CustomUUID, 'to', offer.senderId);
      const answerMessageString = JSON.stringify(answerMessage);
      ws.send(answerMessageString);

      // Navigate to the CallingScreen with offer and DocId
      navigation.navigate('CallingScreen', { answer: answer, DocId: DocId });
    } catch (error) {
      console.error('Error creating answer and sending it back:', error);
    }
  }

  const RejectCall = async () => {
    try {
      const idToken = await FIREBASE_AUTH.currentUser.getIdToken();
      const encryptedIdToken = encryptData(idToken, SECRET_KEY);
      const response = await fetch(`http://10.0.2.2:5000/users/UpdateCallStatus`, {
        method: 'PUT',
        credentials: 'include',
        headers: {
          'Authorization': 'Bearer ' + encryptedIdToken,
          'Accept': 'application/json',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ DocId: DocId, status: 'Rejected' })
      })
      const data = await response.json();
      if (response.status == 200) {
        navigation.goBack();
      }
    } catch (error) {
      console.error('error, updating call status:', error)
    }
  }


  return (
    <View style={styles.Container}>
      {localMediaStream ? <RTCView mirror={true} objectFit={'cover'} streamURL={localMediaStream.toURL()} zOrder={0} style={styles.CallerImage} />
        : <Image source={require('../assets/Images/call1.jpg')} style={styles.CallerImage} />}
      {/* <Image source={require('../assets/Icons/IncomingCallShadowCover.png')} style={styles.backgroundShadowCover} /> */}
      <View style={styles.backgroundShadowCover} />
      <View style={styles.ContentContainer}>
        <View style={styles.CallerInfo}>
          <Text style={styles.CallTypeText}>Incoming Call</Text>
          <Text style={styles.CallerName}>Mei-Ling</Text>
        </View>
        <View style={styles.CallOptions}>
          <Pressable style={styles.RejectCallButton}>
            <Image onPress={RejectCall} source={require('../assets/Icons/CallHangUpIcon.png')} style={styles.RejectCallButtonImage} />
          </Pressable>
          <Pressable onPress={AccpetCall} style={styles.AcceptCallButton}>
            <Image source={require('../assets/Icons/CallHangUpIcon.png')} style={styles.AcceptCallButtonImage} />
          </Pressable>
        </View>
      </View>
    </View>
  )
}

export default InCallScreen

服务器.js

const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const routes = require('./routes/route');
const admin = require('./firebaseAdminSDK');
const essentials = require('./essentials');
const http = require('http');
const WebSocket = require('ws');

require('dotenv-safe').config();

const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

const { SECRET_KEY } = process.env;
const AUTH = admin.auth();
const DB = admin.firestore();
const STORAGE = admin.storage();

const PORT = process.env.PORT || 5000;

const userConnections = new Map();

app.use(bodyParser.json({ limit: '10mb' }));
app.use(bodyParser.urlencoded({ limit: '10mb', extended: true }));
app.use(cookieParser());

app.use('/', routes); app.listen(PORT, () => {
  console.log('Server is listening on Port:', PORT);
});

server.listen(8000, () => {
  console.log('websocket server started on port 8000');
});


// Websocket Code
wss.on('connection', async (ws, req) => {
  let userId;

  ws.on('message', async (message) => {
    const { userId: messageUserId, idToken, recipientId, type, offer, answer, candidate } = JSON.parse(message);
    try {
      const decryptedIdToken = essentials.decryptData(idToken, SECRET_KEY);
      const decodedToken = await AUTH.verifyIdToken(decryptedIdToken);
      if (decodedToken.uid) {
        userConnections.set(messageUserId, ws);
        userId = messageUserId;
      } else {
        ws.send('Unauthorized');
        return;
      }
    } catch (error) {
      console.error('Error handling message:', error);
      ws.send('Unauthorized');
    }




    try {
      if (type === 'answer') {
        console.log('sending back the answer to offer ownwer', recipientId)
        if (recipientId) {
          const connection = userConnections.get(recipientId);
          if (connection) {
            connection.send(JSON.stringify({ type: 'answer', answer: answer, userId: messageUserId }));
          }
        }
      }
    } catch (error) {
      console.error('Error sending answer:', error);
      ws.send('Error sending answer');
    }




    try {
      if (type === 'candidate') {
        if (recipientId && candidate) {
          const recipientConnection = userConnections.get(recipientId);
          if (recipientConnection) {
            recipientConnection.send(JSON.stringify({ type: 'candidate', candidate: candidate }));
          }
        }
      }
    } catch (error) {
      console.error('Error sending answer:', error);
      ws.send('Error sending answer');
    }




    try {
      if (type === 'offer') {
        const decryptedOffer = essentials.decryptData(offer, SECRET_KEY);
        const docRef = await DB.collection('calls').add(JSON.parse(decryptedOffer));
        const recipientUserId = JSON.parse(decryptedOffer).recipientId;
        const notification = {
          type: 'incoming_call',
          docId: docRef.id,
          offer: offer,
        };
        sendCallNotification(recipientUserId, notification);
      }
    } catch (error) {
      console.error('Error handling offer:', error);
      ws.send('Error Creating Offer');
    }
  });




  ws.on('close', () => {
    if (userId) {
      userConnections.delete(userId);
    }
  });
});


function sendCallNotification(recipientUserId, notification) {
  const connection = userConnections.get(recipientUserId);
  if (connection) {
    connection.send(JSON.stringify(notification));
  }
}
react-native websocket webrtc video-streaming react-native-webrtc
1个回答
0
投票

您的问题是如何解决的?请分享。

© www.soinside.com 2019 - 2024. All rights reserved.