You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何使用flutter-webrtc包实现双用户P2P语音通话连接?

Hey there! I totally get the frustration—you’ve got the theory down, but translating that into working Flutter code can feel like hitting a wall. Let’s walk through a step-by-step, actionable solution for two-user voice calls with flutter-webrtc, focusing on the parts that often trip folks up.

1. First, Nail the Basic Setup & Permissions

Before diving into WebRTC logic, make sure you’ve got the basics locked in:

  • Add the flutter-webrtc dependency to your pubspec.yaml
  • Grant microphone permissions:
    • For Android: Add <uses-permission android:name="android.permission.RECORD_AUDIO" /> to your AndroidManifest.xml
    • For iOS: Add NSMicrophoneUsageDescription to Info.plist with a clear reason for accessing the mic
  • Initialize a basic renderer to handle audio streams (even for voice calls, this helps manage playback)
2. Initialize Core WebRTC Objects

You’ll need two key objects for each user: RTCPeerConnection (manages the peer-to-peer connection) and MediaStream (captures local audio input). Here’s how to set them up:

import 'package:flutter_webrtc/flutter_webrtc.dart';

// Use Google's public STUN server for testing
final Map<String, dynamic> configuration = {
  'iceServers': [
    {'urls': 'stun:stun.l.google.com:19302'}
  ]
};

late RTCPeerConnection peerConnection;
late MediaStream localStream;
final RTCVideoRenderer remoteRenderer = RTCVideoRenderer();

// Initialize WebRTC components
Future<void> initWebRTC() async {
  await remoteRenderer.initialize();
  
  // Create peer connection with STUN config
  peerConnection = await createPeerConnection(configuration);
  
  // Capture local audio stream
  localStream = await navigator.mediaDevices.getUserMedia({
    'audio': true,
    'video': false, // We only need voice for this use case
  });
  
  // Add local audio track to the peer connection
  localStream.getTracks().forEach((track) {
    peerConnection.addTrack(track, localStream);
  });
  
  // Listen for incoming remote audio streams
  peerConnection.onTrack.listen((event) {
    if (event.streams.isNotEmpty) {
      remoteRenderer.srcObject = event.streams[0];
    }
  });
  
  // Send ICE candidates to the other user via signaling
  peerConnection.onIceCandidate.listen((candidate) {
    if (candidate != null) {
      // Send this candidate through your signaling system (e.g., WebSocket, Firebase)
      sendSignalingMessage({
        'type': 'ice_candidate',
        'candidate': candidate.toMap(),
      });
    }
  });
}
3. Implement the Signaling Workflow (The Critical Part)

You know signaling is required, but let’s map exactly what to send when for two users:

For the Call Initiator (User A):

  1. After initializing WebRTC, create an offer and send it to User B:
Future<void> createAndSendOffer() async {
  final offer = await peerConnection.createOffer();
  await peerConnection.setLocalDescription(offer);
  
  // Send the offer to User B via your signaling channel
  sendSignalingMessage({
    'type': 'offer',
    'sdp': offer.toMap(),
  });
}

For the Call Receiver (User B):

  1. When you receive the offer from User A, set it as the remote description and send back an answer:
Future<void> handleOffer(Map<String, dynamic> offerData) async {
  final RTCSessionDescription offer = RTCSessionDescription(
    offerData['sdp']['sdp'],
    offerData['sdp']['type'],
  );
  await peerConnection.setRemoteDescription(offer);
  
  // Create and send answer to User A
  final answer = await peerConnection.createAnswer();
  await peerConnection.setLocalDescription(answer);
  
  sendSignalingMessage({
    'type': 'answer',
    'sdp': answer.toMap(),
  });
}

Handling ICE Candidates (Both Users):

When you receive an ICE candidate from the other user, add it to your peer connection immediately:

void handleIceCandidate(Map<String, dynamic> candidateData) {
  final RTCIceCandidate candidate = RTCIceCandidate(
    candidateData['candidate']['candidate'],
    candidateData['candidate']['sdpMid'],
    candidateData['candidate']['sdpMLineIndex'],
  );
  peerConnection.addIceCandidate(candidate);
}

For the Initiator to Handle the Answer:

When User A gets the answer from User B, set it as the remote description:

Future<void> handleAnswer(Map<String, dynamic> answerData) async {
  final RTCSessionDescription answer = RTCSessionDescription(
    answerData['sdp']['sdp'],
    answerData['sdp']['type'],
  );
  await peerConnection.setRemoteDescription(answer);
}
4. Common Pitfalls to Avoid
  • Signaling Reliability: Your signaling channel (WebSocket, etc.) must be reliable—if an offer/answer/ICE candidate gets lost, the connection will fail. Stick to a channel that guarantees message delivery for testing.
  • ICE Candidate Timing: Don’t wait to send ICE candidates—send them as soon as they’re generated. They can arrive in any order, and the peer connection will sort them out automatically.
  • Permissions: Double-check that microphone permissions are granted before trying to capture the local stream. Flutter won’t throw a loud error here—it’ll just fail silently.
  • Cleanup: Don’t forget to dispose of renderers and close the peer connection when the call ends to avoid memory leaks.

Once you wire up the signaling logic to send/receive these messages between users, you should have a working voice call. Start with a simple WebSocket server for testing if you don’t have a signaling system already—keep it basic at first!

内容的提问来源于stack exchange,提问作者Abdul Mustafa

火山引擎 最新活动