WebRTC 在本地网络上工作,但不通过互联网(Python)

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

在我的应用程序中,我需要使用 WebRTC 在 flutter 应用程序和 python 脚本之间进行调用。当我从一个颤振到另一个颤振拨打电话时,一切都在本地网络和互联网上运行。但是当我从 flutter 应用程序调用 python 脚本时,它可以在本地网络上运行,但不能通过互联网运行。 我尝试用 TURN 服务器替换 STUN 服务器,但结果仍然相同。请注意,即使使用 stun 服务器,flutter 应用程序也可以通过互联网连接。所以 STUN/TURN 服务器不是问题。谁能告诉我我的 pythin 脚本的问题。我附上了 flutter 和 python 脚本

颤振代码:

import 'dart:async';
import 'dart:convert';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:wasa/locator.dart';

typedef void StreamStateCallback(MediaStream stream);

class Signaling {
  Map<String, dynamic> configuration = {
    'iceServers': [
      {
        'urls': [
          'stun:stun1.l.google.com:19302',
          'stun:stun2.l.google.com:19302'
        ]
      }
      // {
      //   "urls": "stun:stun.relay.metered.ca:80",
      // },
      // {
      //   "urls": "turn:global.relay.metered.ca:80",
      //   "username": "335637ac6798c949833d965a",
      //   "credential": "x3A8BQfH17c3suAR",
      // },
      // {
      //   "urls": "turn:global.relay.metered.ca:80?transport=tcp",
      //   "username": "335637ac6798c949833d965a",
      //   "credential": "x3A8BQfH17c3suAR",
      // },
      // {
      //   "urls": "turn:global.relay.metered.ca:443",
      //   "username": "335637ac6798c949833d965a",
      //   "credential": "x3A8BQfH17c3suAR",
      // },
      // {
      //   "urls": "turns:global.relay.metered.ca:443?transport=tcp",
      //   "username": "335637ac6798c949833d965a",
      //   "credential": "x3A8BQfH17c3suAR",
      // },
    ]
  };

  RTCPeerConnection? peerConnection;
  MediaStream? localStream;
  MediaStream? remoteStream;
  String? roomId;
  String? currentRoomText;
  StreamStateCallback? onAddRemoteStream;

  StreamSubscription<DocumentSnapshot<Object?>>? roomStream;
  StreamSubscription<QuerySnapshot<Map<String, dynamic>>>? callerStream;
  StreamSubscription<QuerySnapshot<Map<String, dynamic>>>? calleeStream;

  Future<String> createRoom(RTCVideoRenderer remoteRenderer) async {
    FirebaseFirestore db = FirebaseFirestore.instance;
    DocumentReference roomRef = db.collection('rooms').doc(storage.user!.uuid);

    print('Create PeerConnection with configuration: $configuration');

    peerConnection = await createPeerConnection(configuration);

    registerPeerConnectionListeners();

    localStream?.getTracks().forEach((track) {
      peerConnection?.addTrack(track, localStream!);
    });

    // Code for collecting ICE candidates below
    var callerCandidatesCollection = roomRef.collection('callerCandidates');

    peerConnection?.onIceCandidate = (RTCIceCandidate candidate) {
      print('Got candidate: ${candidate.toMap()}');
      callerCandidatesCollection.add(candidate.toMap());
    };
    // Finish Code for collecting ICE candidate

    // Add code for creating a room
    RTCSessionDescription offer = await peerConnection!.createOffer();
    await peerConnection!.setLocalDescription(offer);
    print('Created offer: $offer');

    Map<String, dynamic> roomWithOffer = {'offer': offer.toMap()};

    await roomRef.set(roomWithOffer);
    var roomId = roomRef.id;
    print('New room created with SDK offer. Room ID: $roomId');
    currentRoomText = 'Current room is $roomId - You are the caller!';
    // Created a Room

    peerConnection?.onTrack = (RTCTrackEvent event) {
      print('Got remote track: ${event.streams[0]}');

      event.streams[0].getTracks().forEach((track) {
        print('Add a track to the remoteStream $track');
        remoteStream?.addTrack(track);
      });
    };

    // Listening for remote session description below
    roomStream = roomRef.snapshots().listen((snapshot) async {
      print('Got updated room: ${snapshot.data()}');

      Map<String, dynamic> data = snapshot.data() as Map<String, dynamic>;
      if (peerConnection?.getRemoteDescription() != null &&
          data['answer'] != null) {
        var answer = RTCSessionDescription(
          data['answer']['sdp'],
          data['answer']['type'],
        );

        print("Someone tried to connect");
        await peerConnection?.setRemoteDescription(answer);
      }
    });
    // Listening for remote session description above

    // Listen for remote Ice candidates below
    calleeStream =
        roomRef.collection('calleeCandidates').snapshots().listen((snapshot) {
      snapshot.docChanges.forEach((change) {
        if (change.type == DocumentChangeType.added) {
          Map<String, dynamic> data = change.doc.data() as Map<String, dynamic>;
          print('Got new remote ICE candidate: ${jsonEncode(data)}');
          peerConnection!.addCandidate(
            RTCIceCandidate(
              data['candidate'],
              data['sdpMid'],
              data['sdpMLineIndex'],
            ),
          );
        }
      });
    });
    // Listen for remote ICE candidates above

    return roomId;
  }

  Future<void> joinRoom(String roomId, RTCVideoRenderer remoteVideo) async {
    FirebaseFirestore db = FirebaseFirestore.instance;
    print(roomId);
    DocumentReference roomRef = db.collection('rooms').doc('$roomId');
    var roomSnapshot = await roomRef.get();
    print('Got room ${roomSnapshot.exists}');

    if (roomSnapshot.exists) {
      print('Create PeerConnection with configuration: $configuration');
      peerConnection = await createPeerConnection(configuration);

      registerPeerConnectionListeners();

      localStream?.getTracks().forEach((track) {
        peerConnection?.addTrack(track, localStream!);
      });

      // Code for collecting ICE candidates below
      var calleeCandidatesCollection = roomRef.collection('calleeCandidates');
      peerConnection!.onIceCandidate = (RTCIceCandidate? candidate) {
        if (candidate == null) {
          print('onIceCandidate: complete!');
          return;
        }
        print('onIceCandidate: ${candidate.toMap()}');
        calleeCandidatesCollection.add(candidate.toMap());
      };
      // Code for collecting ICE candidate above

      peerConnection?.onTrack = (RTCTrackEvent event) {
        print('Got remote track: ${event.streams[0]}');
        event.streams[0].getTracks().forEach((track) {
          print('Add a track to the remoteStream: $track');
          remoteStream?.addTrack(track);
        });
      };

      // Code for creating SDP answer below
      var data = roomSnapshot.data() as Map<String, dynamic>;
      print('Got offer $data');
      var offer = data['offer'];
      await peerConnection?.setRemoteDescription(
        RTCSessionDescription(offer['sdp'], offer['type']),
      );
      var answer = await peerConnection!.createAnswer();
      print('Created Answer $answer');

      await peerConnection!.setLocalDescription(answer);

      Map<String, dynamic> roomWithAnswer = {
        'answer': {'type': answer.type, 'sdp': answer.sdp}
      };

      await roomRef.update(roomWithAnswer);
      // Finished creating SDP answer

      // Listening for remote ICE candidates below
      callerStream =
          roomRef.collection('callerCandidates').snapshots().listen((snapshot) {
        snapshot.docChanges.forEach((document) {
          var data = document.doc.data() as Map<String, dynamic>;
          print(data);
          print('Got new remote ICE candidate: $data');
          peerConnection!.addCandidate(
            RTCIceCandidate(
              data['candidate'],
              data['sdpMid'],
              data['sdpMLineIndex'],
            ),
          );
        });
      });
    }
  }

  Future<void> openUserMedia(
    RTCVideoRenderer localVideo,
    RTCVideoRenderer remoteVideo,
  ) async {
    var stream = await navigator.mediaDevices
        .getUserMedia({'video': true, 'audio': true});

    localVideo.srcObject = stream;
    localStream = stream;

    remoteVideo.srcObject = await createLocalMediaStream('key');
  }

  Future<void> hangUp(RTCVideoRenderer localVideo, BuildContext context) async {
    List<MediaStreamTrack> tracks = localVideo.srcObject!.getTracks();
    tracks.forEach((track) {
      track.stop();
    });

    if (remoteStream != null) {
      remoteStream!.getTracks().forEach((track) => track.stop());
    }
    if (peerConnection != null) peerConnection!.close();

    calleeStream?.cancel();
    callerStream?.cancel();
    roomStream?.cancel();

    if (storage.user!.uuid != null) {
      var db = FirebaseFirestore.instance;
      var roomRef = db.collection('rooms').doc(storage.user!.uuid);
      var calleeCandidates = await roomRef.collection('calleeCandidates').get();
      calleeCandidates.docs.forEach((document) => document.reference.delete());

      var callerCandidates = await roomRef.collection('callerCandidates').get();
      callerCandidates.docs.forEach((document) => document.reference.delete());

      await roomRef.delete();
    }

    localStream!.dispose();
    remoteStream?.dispose();
    Navigator.pop(context);
  }

  void registerPeerConnectionListeners() {
    peerConnection?.onIceGatheringState = (RTCIceGatheringState state) {
      print('ICE gathering state changed: $state');
    };

    peerConnection?.onConnectionState = (RTCPeerConnectionState state) {
      print('Connection state change: $state');
    };

    peerConnection?.onSignalingState = (RTCSignalingState state) {
      print('Signaling state change: $state');
    };

    peerConnection?.onIceGatheringState = (RTCIceGatheringState state) {
      print('ICE connection state change: $state');
    };

    peerConnection?.onAddStream = (MediaStream stream) {
      print("Add remote stream");
      onAddRemoteStream?.call(stream);
      remoteStream = stream;
    };
  }
}

Python 代码

import asyncio
import re
from itertools import islice

import numpy as np
import sounddevice as sd
from aiortc import MediaStreamTrack, RTCPeerConnection, RTCSessionDescription, RTCIceCandidate, RTCConfiguration, \
    RTCIceServer
from aiortc.contrib.media import MediaPlayer, MediaRecorder, MediaBlackhole
from google.cloud import firestore
from google.oauth2 import service_account

recorder = MediaRecorder('PathToOutputFile.mp3')


async def join_room(room_id):
    credentials = service_account.Credentials.from_service_account_file(PathToKey.json")
    db = firestore.Client(credentials=credentials)

    room_ref = db.collection('rooms').document(room_id)
    room_snapshot = room_ref.get()

    if room_snapshot.exists:

        servers = RTCConfiguration(
            iceServers=[RTCIceServer(
                urls=['turn:global.relay.metered.ca:80', 'turn:global.relay.metered.ca:80?transport=tcp', 'turn:global.relay.metered.ca:443', 'turns:global.relay.metered.ca:443?transport=tcp'],
            credential='x3A8BQfH17c3suAR',
            username='335637ac6798c949833d965a')])
        # servers = RTCConfiguration(iceServers=[RTCIceServer(urls=['stun:stun1.l.google.com:19302','stun:stun2.l.google.com:19302'])])
        peer_connection = RTCPeerConnection(configuration=servers)

        @peer_connection.on("track")
        def on_track(track):
            print("Track %s received", track.kind)

            if track.kind == "audio":
                print('audio')
                recorder.addTrack(track)
            elif track.kind == "video":
                print('video')
                # recorder.addTrack(track)

            @track.on("ended")
            async def on_ended():
                print("Track %s ended", track.kind)

        @peer_connection.on("connectionstatechange")
        async def on_connectionstatechange():
            print("Connection state is %s" % peer_connection.connectionState)
            if peer_connection.connectionState == "connected":
                await recorder.start()
            elif peer_connection.connectionState == "failed":
                await recorder.stop()
                await peer_connection.close()
            elif peer_connection.connectionState == "closed":
                await recorder.stop()
                await peer_connection.close()



        @peer_connection.on("iceConnectionState")
        async def on_iceConnectionState():
            print("iceConnectionState  is %s" % peer_connection.iceConnectionState)

        @peer_connection.on("iceGatheringState")
        async def on_iceGatheringState():
            print("iceGatheringState  is %s" % peer_connection.iceGatheringState)

        # open media source
        # Set the desired video size and fps in the 'options' dictionary
        player = MediaPlayer('/Users/muhammadazeem/Downloads/sample.mp4')

        if player.audio:
            audio_sender = peer_connection.addTrack(player.audio)

        if player.video:
            video_sender = peer_connection.addTrack(player.video)

        # Fetch existing ICE candidates from Firestore
        candidates_collection = room_ref.collection('callerCandidates')
        existing_candidates = candidates_collection.stream()

        for document in existing_candidates:
            data = document.to_dict()
            print(f'Existing remote ICE candidate: {data}')
            # Define a regular expression pattern to extract information
            pattern = re.compile(r'candidate:(\S+) (\d+) (\S+) (\d+) (\S+) (\d+) typ (\S+)')

            candidate_string = data['candidate']

            # Use the pattern to search for matches in the candidate string
            match = pattern.search(candidate_string)

            if match:
                # Extract information from the match groups
                foundation, component_id, transport, priority, connection_address, port, candidate_type = match.groups()
                candidate = RTCIceCandidate(
                    ip=connection_address,
                    protocol=transport,
                    port=port,
                    foundation=foundation,
                    component=component_id,
                    priority=priority,
                    type=candidate_type,
                    sdpMid=data['sdpMid'],
                    sdpMLineIndex=data['sdpMLineIndex'],
                )
                await peer_connection.addIceCandidate(candidate)

        # Assume you have the offer SDP from Firestore
        offer_sdp = room_snapshot.get('offer')['sdp']
        await peer_connection.setRemoteDescription(RTCSessionDescription(sdp=offer_sdp, type='offer'))

        # Create an answer SDP
        answer = await peer_connection.createAnswer()
        print('Created Answer')
        await peer_connection.setLocalDescription(answer)
        print('answer set successfully')

        # Update the "answer" field in Firestore
        room_with_answer = {'answer': {'type': answer.type, 'sdp': answer.sdp}}
        room_ref.update(room_with_answer)

        print('answer written successfully')


        # Extract candidates, sdpMid, and sdpMLineIndex
        candidates = []
        desc = peer_connection.localDescription
        for candidate_line in peer_connection.localDescription.sdp.splitlines():
            if candidate_line.startswith('a=candidate:'):
                candidate = candidate_line[len('a='):]
                candidates.append(candidate)

        print('candidates created successfully')

        # Store candidates in Firestore
        for candidate in candidates:
            candidate_data = {
                'candidate': candidate,
                'sdpMid': '0',
                'sdpMLineIndex': 0,
            }
            room_ref.collection('calleeCandidates').add(candidate_data)

        print('candidates written successfully')

        try:
            # Placeholder for the main loop, you might want to handle user input or other tasks
            while True:
                await asyncio.sleep(1)
        finally:
            print('close')
            # await candidate_listener.aclose()

if __name__ == '__main__':
    room_id_to_join = "Room_Id"
    asyncio.run(join_room(room_id_to_join))

呼叫应该通过互联网从 flutter 应用程序连接到 python 脚本,但它仅在本地网络上连接,而不是通过互联网。另一方面,调用也是通过互联网和本地网络从 flutter 到 flutter 连接,所以 flutter 代码没问题,但 python 代码有问题。

python flutter webrtc webrtc-android aiortc
1个回答
0
投票

经过一些测试,有一些观察结果。当我的 Python 脚本通过 4g 运行并且 flutter 应用程序通过 WiFi 网络运行时,一切正常。但是,当我的 Python 脚本在 WiFi 上运行并且移动应用程序在 4g 上运行时,则无法访问 python 脚本

所以提供商没有限制,但在某种程度上取决于连接 WiFi 的笔记本电脑

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