Flutter WebRTC with Firebase:视频在点对点连接中不显示

Flutter WebRTC with Firebase : Video not displaying in Peer-to-Peer connection

提问人:clément Baille 提问时间:7/19/2023 更新时间:7/19/2023 访问量:230

问:

我目前正在做一个 Flutter 项目,我正在尝试使用 Flutter 建立点对点音频和视频连接。

我已经成功实现了提供/应答系统,并且两台设备都能够共享流 ID。但是,我遇到了视频保持黑色且不显示的问题。 我真的卡住了,因为日志没有向我显示任何错误来证明这个黑屏的合理性。

以下是我的设置概述:

  • 我正在将 Flutter 与 flutter_webrtc 包一起使用以实现 WebRTC 功能。
  • 我正在使用 Firebase 实时数据库在设备之间交换选答 SDP 和 ICE 候选项。
  • 我正在使用 2 部物理安卓手机,一部是 Android12,另一部是 Android13
  • 我的连接足够强大,可以进行 RTC 调用,并且它们不会被阻止。

Remotre 流仅显示黑屏 我的库的版本 Firebase 数据

(在 firebase 中,IPAdress 等数据在 ICECandidates 中,在屏幕上不可见)

我将非常感谢有关如何解决此问题并正确显示视频流的任何见解或建议。提前感谢您的帮助!

(PS:我不是以英语为母语的人,所以对不起语言近似) (PSS:我只做了很短的时间,我是大三学生,也很抱歉)

这是我的代码:

import 'dart:async';
import 'dart:developer';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_database/firebase_database.dart';

import '../Resources/styles.dart';

class CameraSharingScreen extends StatefulWidget {
  final String roomId;
  final bool isHost;

  CameraSharingScreen({required this.roomId, required this.isHost});
  @override
  _CameraSharingScreenState createState() => _CameraSharingScreenState();
}

class _CameraSharingScreenState extends State<CameraSharingScreen> {
  DatabaseReference? _sessionRef;
  RTCPeerConnection? _peerConnection;
  MediaStream? _localStream;
  MediaStream? _remoteStream;
  RTCVideoRenderer _localRenderer = RTCVideoRenderer();
  RTCVideoRenderer _remoteRenderer = RTCVideoRenderer();
  String statePhone = "";
  String ipAddress = "";

  String localDescription = "";
  String remoteDescription = "";

  bool _isOfferPending = false;
  Timer? _offerTimer;
  Timer? _answerTimer;

  final Map<String, dynamic> offerSdpConstraints = {
    "mandatory": {
      // "OfferToReceiveAudio": true,
      "OfferToReceiveVideo": true,
    },
    "optional": [],
  };

  @override
  void initState() {
    super.initState();
    initializeApp();
    initializeRenderers();
    // Step1 : create Session
    createSession();
    getLocalStream();
    //_createPeerConnection();
  }

  @override
  void dispose() {
    _localStream?.dispose();
    _remoteStream?.dispose();
    _peerConnection?.close();
    _peerConnection = null;
    _localRenderer.dispose();
    _remoteRenderer.dispose();
    _offerTimer?.cancel();
    _answerTimer?.cancel();
    super.dispose();
  }

  initializeApp() async {
    await Firebase.initializeApp();
  }

  initializeRenderers() async {
    await _localRenderer.initialize();
    await _remoteRenderer.initialize();
  }

  createSession() {
    FirebaseDatabase.instance
        .ref()
        .child('sessions')
        .child(widget.roomId)
        .remove();
    _sessionRef =
        FirebaseDatabase.instance.ref().child('sessions').child(widget.roomId);
    // _sessionRef = FirebaseDatabase.instance.ref().child('sessions').push();
  }

  getLocalStream() async {
    MediaStream stream = await navigator.mediaDevices.getUserMedia({
      // 'audio': true,
      'video': true,
    });
    setState(() {
      _localStream = stream;
    });
    _localRenderer.srcObject = _localStream;
  }

  _createOffer() async {
    _isOfferPending = true;
    //Step desc 1 : Set Local E
    RTCSessionDescription description =
        await _peerConnection!.createOffer({'offerToReceiveVideo': 1});

    _peerConnection!.setLocalDescription(description);

    log("local plouf: " + description.sdp!);

    _sessionRef!.child('offer').set({
      'sdp': description.sdp,
      'type': description.type,
    });
  }

  _createAnswer() async {
    if (!_isOfferPending) {
      RTCSessionDescription description =
          await _peerConnection!.createAnswer({'offerToReceiveVideo': 1});

      _peerConnection!.setLocalDescription(description);
      log("local plouf: " + description.sdp!);

      _sessionRef!.child('answer').set({
        'sdp': description.sdp,
        'type': description.type,
      });
    }
  }

  setRemoteDescription(dynamic data) async {
    RTCSessionDescription description = RTCSessionDescription(
        data['sdp'], statePhone == "e" ? "answer" : "offer");
    await _peerConnection!.setRemoteDescription(description);
    log("remote plouf: " + description.sdp!);
  }

  setRemoteIceCandidate(dynamic data) async {
    if (data != null) {
      RTCIceCandidate candidate = RTCIceCandidate(
        data['candidate'],
        data['sdpMid'],
        data['sdpMLineIndex'],
      );
      await _peerConnection!.addCandidate(candidate);
      log("remote plouf: " + candidate.candidate!);
    }
  }

  _createPeerConnection() async {
    Map<String, dynamic> configuration = {
      // "sdpSemantics": "plan-b",
      'iceServers': [
        {'url': 'stun:stun.l.google.com:19302'},
      ],
    };

    _peerConnection =
        await createPeerConnection(configuration, offerSdpConstraints);

    if (_localStream != null) {
      _localStream!.getTracks().forEach((track) {
        _peerConnection!.addTrack(track, _localStream!);
      });
    }
    _peerConnection!.onTrack = (event) {
      if (event.track.kind == 'video') {
        setState(() {
          _remoteStream = event.streams[0];
          log("IMPORTANT stream detected ! " +
              _localStream!.id +
              " // " +
              _remoteStream!.id);
          _remoteRenderer.srcObject = _remoteStream;
        });
      }
    };

    if (statePhone == "e") {
      //Step 2 Create Offer // Step desc 2 Send Local E
      _createOffer();

      //Step 5 : Listen Answer
      _sessionRef!.child('answer').onValue.listen((event) {
        if (event.snapshot.value != null) {
          //Step desc 6 : Set E remote from R local
          setRemoteDescription(event.snapshot.value);
        }
      });
    }

    if (statePhone == "r") {
      //Step 3 : listen for Offer // Step desc 3 : Set Remote 4 from Local E
      _sessionRef!.child('offer').onValue.listen((event) {
        if (event.snapshot.value != null) {
          setRemoteDescription(event.snapshot.value);

          //Step 4 : Create Answer // Step desc 4 & 5
          _createAnswer();
        }
      });
    }

    _peerConnection!.onIceCandidate = (candidate) {
      if (candidate != null) {
        _sessionRef!.child('iceCandidates').push().set({
          'candidate': candidate.candidate,
          'sdpMid': /* candidate.sdpMid */ 'video',
          'sdpMLineIndex': candidate.sdpMLineIndex,
        });
      }
    };

    _sessionRef!.child('iceCandidates').onChildAdded.listen((event) {
      setRemoteIceCandidate(event.snapshot.value);
    });
  }

  becameReceiver() {
    String sessionKey = "-NZkn0LpxoWvxCZpOsuF";
    _sessionRef =
        FirebaseDatabase.instance.ref().child('sessions').child(sessionKey);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Column(
          children: [
            Text(statePhone +
                " // " +
                "${(_localStream == null) ? '' : _localStream!.id}"),
            Text(statePhone +
                " // " +
                "${(_remoteStream == null) ? '' : _remoteStream!.id}"),
          ],
        ),
      ),
      body: Container(
        decoration: BoxDecoration(gradient: Styles.mainGradientColor),
        child: Row(
          children: [
            Flexible(
              child: RTCVideoView(_remoteRenderer),
            ),
            Flexible(
              child: RTCVideoView(_localRenderer),
            ),
          ],
        ),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.endTop,
      floatingActionButton: FloatingActionButton(
        child: Icon((widget.isHost) ? Icons.send : Icons.call_received),
        onPressed: () {
          statePhone = (widget.isHost) ? "e" : "r";
          setState(() {});
          _createPeerConnection();
        },
      ),
    );
  }
}

Android Flutter Firebase WebRTC 视频聊天

评论


答: 暂无答案