Code Monkey home page Code Monkey logo

Comments (8)

un-hongly avatar un-hongly commented on June 10, 2024 2

@Blancduman Thank you for your time. πŸ˜„
I solved it by changing announcedIp to my public IP on the server-side.

listenIps: [
{
ip: '0.0.0.0',
announcedIp: 'public IP',
}
],

from mediasoup-client-flutter.

Blancduman avatar Blancduman commented on June 10, 2024 1

Image

from mediasoup-client-flutter.

Blancduman avatar Blancduman commented on June 10, 2024

from mediasoup-client-flutter.

un-hongly avatar un-hongly commented on June 10, 2024

Here is my flutter code.

import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:mediasoup_client_flutter/mediasoup_client_flutter.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:video_call_mediasoup/socket.dart';


void main() {
  HttpOverrides.global = MyHttpOverrides();
  runApp(MyApp());
}

 class MyHttpOverrides extends HttpOverrides{
  @override
  HttpClient createHttpClient(SecurityContext? context){
    return super.createHttpClient(context)
      ..badCertificateCallback = (X509Certificate cert, String host, int port)=> true;
  }
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  RTCVideoRenderer _localRenderer = new RTCVideoRenderer();
  RTCVideoRenderer _remoteRenderer = new RTCVideoRenderer();
  late MediaStream _localStream;
  late Transport _producerTransport;
  late Transport _consumerTransport;
  late Device device;
  @override
  void initState() {
    SocketUtils.connect();
    _localRenderer.initialize();
    _remoteRenderer.initialize();
    super.initState();
  }

  @override
  deactivate() {
    _localRenderer.dispose();
    _remoteRenderer.dispose();
    super.deactivate();
  }

  @override
  dispose() {
    _localRenderer.dispose();
    _remoteRenderer.dispose();
    super.dispose();
  }

  getUserMedia() async {

  }

  connectAndProduce() async {
    var isHasPermission = true;
    if (!kIsWeb) {
      var status = await Permission.camera.request();
      isHasPermission = status.isGranted;
    }

    if (isHasPermission) {
      final Map<String, dynamic> constraints = {
        'audio': false,
        'video': true
      };
      _localStream = await navigator.mediaDevices.getUserMedia(constraints);
      this.setState(() {
        _localRenderer.srcObject = _localStream;
      });
    } else {
      print('PERMISSION NOT ALLOWED');
    }

    _producerTransport.produce(
      track: _localStream.getVideoTracks().first,
      stream: _localStream,
      source: 'webcam',
      codecOptions: ProducerCodecOptions(
        videoGoogleStartBitrate: 1000,
      ),
      encodings: [
        // RtpEncodingParameters(rid: 'r0', scalabilityMode: 'S1T3', maxBitrate: 100000),
        // RtpEncodingParameters(rid: 'r1', scalabilityMode: 'S1T3', maxBitrate: 300000),
        // RtpEncodingParameters(rid: 'r2', scalabilityMode: 'S1T3', maxBitrate: 900000)
      ],
    );

    _producerTransport.on('trackended', () {
      print('Video track ended');
    }); 
  }

  connectAndReceive() async {
    SocketUtils.connection.emitWithAck('consume', {
      'rtpCapabilities': device.rtpCapabilities.toMap(),
    }, ack: (response) async {
      _consumerTransport.consume(
        id: response['id'],
        producerId: response['producerId'],
        peerId: response['producerId'],
        kind: RTCRtpMediaTypeExtension.fromString(response['kind']),
        rtpParameters: RtpParameters.fromMap(response['rtpParameters']),
        accept: (param) {
        }
      );
    });
  }

  _startCall() async {
    await getUserMedia();
    await createDevice();
    await createWebRtcTransport();
    await connectAndProduce();
  }

  _answerCall() async {
    await createRecvTransport();
    await connectAndReceive();
  }

  createDevice () async {
    // server side will create router and return RtpCapabilities then we need to load it to device
    device = Device();
    var routerRtpCapabilities = await getRtpCapabilities();
    print(routerRtpCapabilities);
    await device.load(routerRtpCapabilities: routerRtpCapabilities);
  }

  createWebRtcTransport() async {
    var webRtcTransport = await getWebRtcTransport(true);
    _producerTransport = device.createSendTransportFromMap(webRtcTransport);

    _producerTransport.on('connect', (Map data) {
      var callback = data['callback'];
      var errback = data['errback'];
      try {
        SocketUtils.connection.emit('transport-connect', {
          'transportId': _producerTransport.id,
          'dtlsParameters': data['dtlsParameters'].toMap()
        });
        callback();
      } catch(error) {
        errback(error);
      }
    });

    _producerTransport.on('produce', (Map data) {
        var callback = data['callback'];
        var errback = data['errback'];
      try {
        var rtpParameters = data['rtpParameters'];
        var kind = data['kind'];
        var appData = data['appData'];
        SocketUtils.connection.emitWithAck('transport-produce', {
          'transportId': _producerTransport.id,
          'kind': kind,
          'rtpParameters': rtpParameters.toMap(),
          'appData': appData
        }, ack: (response) {
          callback(response['id']);
        });
      } catch (error) {
        errback(error);
      }
    });
  }


  createRecvTransport() async {
    var webRtcTransport = await getWebRtcTransport(false);
    _consumerTransport = device.createRecvTransportFromMap(
      webRtcTransport,
      consumerCallback: (Consumer consumer, [dynamic accept]) {
        this.setState(() {
          _remoteRenderer.srcObject = consumer.stream;
        });
        accept({});
        SocketUtils.connection.emit('consumer-resume');
      },
      dataConsumerCallback: (data) {

      }
    );

    _consumerTransport.on('connect', (Map data) {
      var callback = data['callback'];
      var errback = data['errback'];
      try {
        SocketUtils.connection.emit('transport-recv-connect', {
          'transportId': _consumerTransport.id,
          'dtlsParameters': data['dtlsParameters'].toMap()
        });
        callback();
      } catch(error) {
        print(error);
        errback(error);
      }
    });
  }

  getRtpCapabilities() {
    Completer completer = new Completer();
    SocketUtils.connection.emitWithAck('getRtpCapabilities', null, ack: (response) {
      var rtpCapabilities = RtpCapabilities.fromMap(response['rtpCapabilities']);
      completer.complete(rtpCapabilities);
    });
    return completer.future;
  }

  getWebRtcTransport(bool sender) async {
    Completer completer = new Completer();
    SocketUtils.connection.emitWithAck('createWebRtcTransport', { 'sender': sender }, ack: (response) {
      completer.complete(response);
    });
    return completer.future;
  }

SizedBox videoRenderers() => SizedBox(
    height: 200,
    child: Row(
      children: [
        Flexible(
          child: Container(
            key: Key('local'),
            margin: EdgeInsets.fromLTRB(5.0, 5.0, 5.0, 5.0),
            decoration: BoxDecoration(color: Colors.grey),
            child: RTCVideoView(_localRenderer),
          )
        ),
        Flexible(
          child: Container(
            key: Key('remote'),
            margin: EdgeInsets.fromLTRB(5.0, 5.0, 5.0, 5.0),
            decoration: BoxDecoration(color: Colors.grey),
            child: RTCVideoView(_remoteRenderer),
          )
        ),
      ],
    ),
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Container(
      child: Column(
        children: [
          videoRenderers(),
          ElevatedButton(onPressed: _startCall, child: Text('Call')),
          ElevatedButton(onPressed: _answerCall, child: Text('Answer'))
        ],),
    ),
    );
  }
}

from mediasoup-client-flutter.

un-hongly avatar un-hongly commented on June 10, 2024

And this is serverside code:

import express from 'express'
const app = express()

import https from 'httpolyglot'
import fs from 'fs'
import path from 'path'
const __dirname = path.resolve()

import { Server } from 'socket.io'
import mediasoup from 'mediasoup'

app.get('/', (req, res) => {
  res.send('Hello from mediasoup app!')
})

app.use('/sfu', express.static(path.join(__dirname, 'public')))

// SSL cert for HTTPS access
const options = {
  key: fs.readFileSync('./server/ssl/key.key', 'utf-8'),
  cert: fs.readFileSync('./server/ssl/cert.crt', 'utf-8')
}

const httpsServer = https.createServer(options, app)
httpsServer.listen(3000, () => {
  console.log('listening on port: ' + 3000)
})

const io = new Server(httpsServer)

// socket.io namespace (could represent a room?)
const peers = io.of('/mediasoup')

let worker
let router
let producerTransport
let consumerTransport
let producer
let consumer

const createWorker = async () => {
  worker = await mediasoup.createWorker({
    rtcMinPort: 2000,
    rtcMaxPort: 2020,
  })
  console.log(`worker pid ${worker.pid}`)

  worker.on('died', error => {
    // This implies something serious happened, so kill the application
    console.error('mediasoup worker has died')
    setTimeout(() => process.exit(1), 2000) // exit in 2 seconds
  })

  return worker
}

// We create a Worker as soon as our application starts
worker = createWorker()

// This is an Array of RtpCapabilities
// https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/#RtpCodecCapability
// list of media codecs supported by mediasoup ...
// https://github.com/versatica/mediasoup/blob/v3/src/supportedRtpCapabilities.ts
const mediaCodecs = [
  {
    kind: 'audio',
    mimeType: 'audio/opus',
    clockRate: 48000,
    channels: 2,
  },
  {
    kind: 'video',
    mimeType: 'video/vp8',
    clockRate: 90000,
    parameters: {
      'x-google-start-bitrate': 1000,
    },
  },
]

peers.on('connection', async socket => {
  console.log(socket.id)
  socket.emit('connection-success', {
    socketId: socket.id
  })

  socket.on('disconnect', () => {
    // do some cleanup
    console.log('peer disconnected')
  })

  // worker.createRouter(options)
  // options = { mediaCodecs, appData }
  // mediaCodecs -> defined above
  // appData -> custom application data - we are not supplying any
  // none of the two are required
  router = await worker.createRouter({ mediaCodecs, })

  // Client emits a request for RTP Capabilities
  // This event responds to the request
  socket.on('getRtpCapabilities', (callback) => {

    const rtpCapabilities = router.rtpCapabilities

    console.log('rtp Capabilities', rtpCapabilities)

    // call callback from the client and send back the rtpCapabilities
    callback({ rtpCapabilities })
  })

  // Client emits a request to create server side Transport
  // We need to differentiate between the producer and consumer transports
  socket.on('createWebRtcTransport', async ({ sender }, callback) => {
    console.log(`Is this a sender request? ${sender}`)
    // The client indicates if it is a producer or a consumer
    // if sender is true, indicates a producer else a consumer
    if (sender)
      producerTransport = await createWebRtcTransport(callback)
    else
      consumerTransport = await createWebRtcTransport(callback)
  })

  // see client's socket.emit('transport-connect', ...)
  socket.on('transport-connect', async ({ dtlsParameters }) => {
    console.log('DTLS PARAMS... ', { dtlsParameters })
    await producerTransport.connect({ dtlsParameters })
  })

  // see client's socket.emit('transport-produce', ...)
  socket.on('transport-produce', async ({ kind, rtpParameters, appData }, callback) => {
    // call produce based on the prameters from the client
    producer = await producerTransport.produce({
      kind,
      rtpParameters,
    })

    console.log('Producer ID: ', producer.id, producer.kind)

    producer.on('transportclose', () => {
      console.log('transport for this producer closed ')
      producer.close()
    })

    // Send back to the client the Producer's id
    callback({
      id: producer.id
    })
  })

  // see client's socket.emit('transport-recv-connect', ...)
  socket.on('transport-recv-connect', async ({ dtlsParameters }) => {
    console.log(`DTLS PARAMS: ${dtlsParameters}`)
    await consumerTransport.connect({ dtlsParameters })
  })

  socket.on('consume', async ({ rtpCapabilities }, callback) => {
    console.log('CONS');
    try {
      // check if the router can consume the specified producer
      if (router.canConsume({
        producerId: producer.id,
        rtpCapabilities
      })) {
        // transport can now consume and return a consumer
        consumer = await consumerTransport.consume({
          producerId: producer.id,
          rtpCapabilities,
          paused: true,
        })

        consumer.on('transportclose', () => {
          console.log('transport close from consumer')
        })

        consumer.on('producerclose', () => {
          console.log('producer of consumer closed')
        })

        // from the consumer extract the following params
        // to send back to the Client
        const params = {
          id: consumer.id,
          producerId: producer.id,
          kind: consumer.kind,
          rtpParameters: consumer.rtpParameters,
        }

        // send the parameters to the client
        callback(params)
      }
    } catch (error) {
      console.log(error.message)
      callback({
        params: {
          error: error
        }
      })
    }
  })

  socket.on('consumer-resume', async () => {
    console.log('consumer resume')
    await consumer.resume()
  })
})

const createWebRtcTransport = async (callback) => {
  try {
    // https://mediasoup.org/documentation/v3/mediasoup/api/#WebRtcTransportOptions
    const webRtcTransport_options = {
      listenIps: [
        {
          ip: '0.0.0.0', // replace with relevant IP address
          announcedIp: '127.0.0.1',
        }
      ],
      enableUdp: true,
      enableTcp: true,
      preferUdp: true,
    }

    // https://mediasoup.org/documentation/v3/mediasoup/api/#router-createWebRtcTransport
    let transport = await router.createWebRtcTransport(webRtcTransport_options)
    console.log(`transport id: ${transport.id}`)

    transport.on('dtlsstatechange', dtlsState => {
      if (dtlsState === 'closed') {
        transport.close()
      }
    })

    transport.on('close', () => {
      console.log('transport closed')
    })

    // send back to the client the following prameters
    callback({
      // https://mediasoup.org/documentation/v3/mediasoup-client/api/#TransportOptions
        id: transport.id,
        iceParameters: transport.iceParameters,
        iceCandidates: transport.iceCandidates,
        dtlsParameters: transport.dtlsParameters,
      }
    )

    return transport

  } catch (error) {
    console.log(error)
    callback( {
        error: error
      }
    )
  }
}

from mediasoup-client-flutter.

Blancduman avatar Blancduman commented on June 10, 2024
  • try to debug your signaling's messages.
  • check your transports seperatly

Let me know what you found out

from mediasoup-client-flutter.

IVLabs-1 avatar IVLabs-1 commented on June 10, 2024

Hi @un-hongly,

I am aslo new to webrtc and mediasoup. I have also followed the same tutorial on youtube as yours and I am also developing the app on flutter. I have used your above code, it is working fine for producer but camera is not rendering remote video. Can you help me to solve this issue?

from mediasoup-client-flutter.

sahibalam avatar sahibalam commented on June 10, 2024

Hi un-hongly...I have tried your code its working fine on wifi but not work with mobile hotspot and also not work on ec2 server where I have used nginx as a reverse proxy and allowed all the relevant ports....though I have changed announced ip to public ip....

from mediasoup-client-flutter.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.