Skip to content

Instantly share code, notes, and snippets.

@gisborne
Created June 28, 2024 22:37
Show Gist options
  • Save gisborne/7a714c72444d91cbcd7f93a92aab73b3 to your computer and use it in GitHub Desktop.
Save gisborne/7a714c72444d91cbcd7f93a92aab73b3 to your computer and use it in GitHub Desktop.

Revisions

  1. gisborne created this gist Jun 28, 2024.
    140 changes: 140 additions & 0 deletions failed-webrtc.dart
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,140 @@
    import 'package:flutter/material.dart';
    import 'package:flutter_webrtc/flutter_webrtc.dart';

    void main() {
    runApp(const MyApp());
    }

    class MyApp extends StatelessWidget {
    const MyApp({Key? key}) : super(key: key);

    @override
    Widget build(BuildContext context) {
    return MaterialApp(
    home: Scaffold(
    appBar: AppBar(title: const Text('MacOS STUN Test')),
    body: const STUNTest(),
    ),
    );
    }
    }

    class STUNTest extends StatefulWidget {
    const STUNTest({Key? key}) : super(key: key);

    @override
    _STUNTestState createState() => _STUNTestState();
    }

    class _STUNTestState extends State<STUNTest> {
    String _status = 'Initializing...';
    RTCPeerConnection? _peerConnection;
    bool _gatheringComplete = false;

    @override
    void initState() {
    super.initState();
    _createPeerConnection();
    }

    Future<void> _createPeerConnection() async {
    _addLog('Creating peer connection...');

    final Map<String, dynamic> configuration = {
    'iceServers': [
    {
    'urls': [
    'stun:stun1.l.google.com:19302',
    'stun:stun2.l.google.com:19302',
    ]
    }
    ],
    'sdpSemantics': 'unified-plan',
    'iceTransportPolicy': 'all',
    'bundlePolicy': 'max-bundle',
    'rtcpMuxPolicy': 'require',
    'iceCandidatePoolSize': 0,
    'enableDscp': true,
    'enableIPv6': true,
    };

    _peerConnection = await createPeerConnection(configuration);

    _peerConnection!.onIceCandidate = (RTCIceCandidate candidate) {
    if (candidate.candidate != null) {
    _processCandidate(candidate.candidate!);
    }
    };

    _peerConnection!.onIceGatheringState = (RTCIceGatheringState state) {
    _addLog('ICE gathering state changed: $state');
    if (state == RTCIceGatheringState.RTCIceGatheringStateComplete) {
    _gatheringComplete = true;
    _checkGatheringComplete();
    }
    };

    _peerConnection!.onConnectionState = (RTCPeerConnectionState state) {
    _addLog('Peer connection state changed: $state');
    };

    _addLog('Creating data channel...');
    await _peerConnection!.createDataChannel('trigger', RTCDataChannelInit());

    _addLog('Creating offer...');
    RTCSessionDescription offer = await _peerConnection!.createOffer();

    _addLog('Setting local description...');
    await _peerConnection!.setLocalDescription(offer);

    // Set a longer timeout for M1 Macs
    Future.delayed(const Duration(seconds: 45), () {
    _checkGatheringComplete();
    });
    }

    void _processCandidate(String candidateString) {
    _addLog('Received candidate: $candidateString');
    final parts = candidateString.split(' ');
    final type = parts.firstWhere((part) => part.startsWith('typ ')).split(' ')[1];
    final ip = parts[4];
    final port = parts[5];
    _addLog('Candidate type: $type, IP: $ip, Port: $port');
    }

    void _checkGatheringComplete() {
    if (!_gatheringComplete) {
    _addLog('Gathering timed out or completed');
    }
    if (mounted) {
    setState(() {
    _status += '\nGathering process finished.';
    });
    }
    }

    void _addLog(String log) {
    print(log);
    if (mounted) {
    setState(() {
    _status += '\n$log';
    });
    }
    }

    @override
    Widget build(BuildContext context) {
    return SingleChildScrollView(
    child: Padding(
    padding: const EdgeInsets.all(16.0),
    child: Text(_status),
    ),
    );
    }

    @override
    void dispose() {
    _peerConnection?.close();
    super.dispose();
    }
    }