PeerDart를 통한 간소화된 피어 투 피어 통신

PeerDart는 사용하기 쉬운 WebRTC API용 래퍼입니다. P2P 통신을 사용하여 클라이언트 간에 실시간으로 데이터 또는 미디어 스트리밍

이 자습서에서는 PeerDart를 사용하여 두 클라이언트가 쉽게 비디오 및 오디오를 연결하고 스트리밍할 수 있는 기본 비디오 채팅 앱을 만듭니다.

PeerDart는 WebRTC를 어떻게 단순화합니까?
응용 프로그램의 실시간 P2P 통신과 관련하여 WebRTC는 많은 개발자가 사용하는 표준입니다. 그러나 다음과 같은 몇 가지 복잡성이 있습니다.

순수한 WebRTC를 사용하는 경우 먼저 STUN(Session Traversal Utilities for NAT) 서버를 정의하여 통신에 관련된 각 피어에 대한 ICE(Interactive Connectivity Establishment) 후보를 생성합니다.
그런 다음 서버를 사용하여 이러한 ICE 후보 세부 정보를 저장해야 합니다.
마지막으로 실시간 업데이트를 처리하려면 WebSocket을 구현해야 합니다.
이전에 WebRTC로 작업한 적이 없더라도; 구현의 복잡성을 느끼셨을 것입니다. 그러나 걱정하지 마십시오. PeerDart가 구조를 위해 여기에 있습니다.

PeerDart를 사용하면 STUN, ICE 후보 또는 서버 생성에 대해 걱정할 필요가 없습니다. WebSockets 구현도 피할 수 있습니다.

PeerDart는 완벽하고 구성 가능한 P2P 연결 API와 PeerDart 클라이언트 간의 연결을 쉽게 설정할 수 있는 PeerServer라는 서버를 제공합니다.

이제 PeerDart를 사용하여 간단한 채팅/비디오 애플리케이션을 만드는 방법을 살펴보겠습니다.

설정하기
1단계 — 새 Flutter 프로젝트 만들기
먼저 평소처럼 Flutter 프로젝트를 만듭니다.

flutter create peerdart-example


  • PeerDart 및 flutter-webrtc를 종속 항목으로 추가합니다.

  • flutter pub add peerdart flutter-webrtc
    


    2단계 - 구현

    1- PeerDart 초기화

    final Peer peer = Peer()
    


    팁: id 속성을 사용하여 고유한 id를 할당할 수 있습니다.

    final Peer peer = Peer(id: “custom id”)



    피어 인스턴스에는 피어 간의 통신을 처리하는 여러 가지 방법이 있습니다. peer.on은 피어 이벤트를 수신하는 데 사용되며 원격 피어로부터 호출/데이터를 수신할 때 유용합니다.

    PeerServer에 성공적으로 연결되면 open 이벤트가 발생하며 이 이벤트를 사용하여 peerId 및 피어 인스턴스의 상태를 업데이트합니다.

    peer.on("open").listen((event) => print("Connection established"))
    


    그런 다음 연결 이벤트를 사용하여 원격 피어 연결을 수신해야 합니다.

    peer.on<DataConnection>("connection").listen((data) {
      setState(() {
        connected = true;
      });
    })
    


    그런 다음 다른 피어로부터 받은 데이터를 듣기 위해 다음을 수행합니다.

    peer.on("data").listen((data) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(data)));
    })
    


    이제 메시지 수신을 위한 모든 기능을 구현했습니다. 마지막 단계로 메시지를 보내는 방법을 만들어 봅시다.

    peer.connect 메서드를 사용하면 피어 ID를 지정하여 피어에 연결할 수 있습니다. 그런 다음 피어에 메시지 데이터를 보내는 데 사용할 수 있는 DataConnection 개체를 반환합니다.

    void connect() {
      final connection = peer.connect(_controller.text);
      conn = connection;
    
      conn.on("open").listen((event) {
        setState(() {
          connected = true;
        });
    
        // Peer closed
        connection.on("close").listen((event) {
          setState(() {
            connected = false;
          });
        });
    
        conn.on("data").listen((data) {
          ScaffoldMessenger.of(context)
              .showSnackBar(SnackBar(content: Text(data)));
        });
      });
    }
    


    그리고 다른 피어에게 메시지를 보내려면 send() 함수를 사용하여 보내면 됩니다.

    void sendHelloWorld() {
      conn.send("Hello world!");
    }
    


    3단계 - 비디오 채팅 구현
    이제 동영상 메시지를 보낼 수 있도록 대화방을 수정해 보겠습니다. 이를 구현하는 것은 이전 단계에서 논의한 것과 거의 유사합니다. peer.on 메서드 내에서 호출 이벤트를 사용하여 원격 피어의 호출을 수신할 수 있습니다. MediaConnection이라는 개체로 콜백을 제공하고 수신기의 비디오 및 오디오 스트림은 MediaConnection 개체의 응답 메서드에 제공됩니다.

    1단계 비디오 인스턴스 렌더링을 위한 추가 상태 추가.

    final _localRenderer = RTCVideoRenderer();
    final _remoteRenderer = RTCVideoRenderer();
    bool inCall = false;
    


    2단계 initState 내부에서 비디오 렌더러를 초기화합니다.

    @override
    void initState() {
      super.initState();
      _localRenderer.initialize();
      _remoteRenderer.initialize();
    }
    


    3단계 — 전화 수신을 위한 추가 리스너 추가.

     @override
      void initState() {
        super.initState();
        //.....
    
        peer.on<MediaConnection>("call").listen((call) async {
          final mediaStream = await navigator.mediaDevices
              .getUserMedia({"video": true, "audio": false});
    
          call.answer(mediaStream);
    
          call.on("close").listen((event) {
            setState(() {
              inCall = false;
            });
          });
    
          call.on<MediaStream>("stream").listen((event) {
            _localRenderer.srcObject = mediaStream;
            _remoteRenderer.srcObject = event;
    
            setState(() {
              inCall = true;
            });
          });
        });
      }
    


    Step — 4 call() 함수로 다른 피어 호출

    void connect() async {
      final mediaStream = await navigator.mediaDevices
          .getUserMedia({"video": true, "audio": false});
    
      final conn = peer.call(_controller.text, mediaStream);
    
      conn.on("close").listen((event) {
        setState(() {
          inCall = false;
        });
      });
    
      conn.on<MediaStream>("stream").listen((event) {
        _remoteRenderer.srcObject = event;
        _localRenderer.srcObject = mediaStream;
    
        setState(() {
          inCall = true;
        });
      });
    }
    


    단계 — 5 마무리 작업

    @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  _renderState(),
                  const Text(
                    'Connection ID:',
                  ),
                  SelectableText(peerId ?? ""),
                  TextField(
                    controller: _controller,
                  ),
                  ElevatedButton(onPressed: connect, child: const Text("connect")),
                  ElevatedButton(
                      onPressed: send, child: const Text("send message")),
                  if (inCall)
                    Expanded(
                      child: RTCVideoView(
                        _localRenderer,
                      ),
                    ),
                  if (inCall)
                    Expanded(
                      child: RTCVideoView(
                        _remoteRenderer,
                      ),
                    ),
                ],
              ),
            ));
      }
    
      Widget _renderState() {
        Color bgColor = inCall ? Colors.green : Colors.grey;
        Color txtColor = Colors.white;
        String txt = inCall ? "Connected" : "Standby";
        return Container(
          decoration: BoxDecoration(color: bgColor),
          child: Text(
            txt,
            style:
                Theme.of(context).textTheme.titleLarge?.copyWith(color: txtColor),
          ),
        );
      }
    


    그게 다야! 이제 빠른 화상 채팅을 할 준비가 모두 끝났습니다. 최종 구현은 다음과 같을 것이며 여기 예제에서 전체 코드를 찾을 수 있습니다.

    좋은 웹페이지 즐겨찾기