DEV Community

timsar2
timsar2

Posted on • Edited on

Ionic video call by peerjs

Hello,
Before starting, this is ionic 5 angular 9 simple example of video call.
At the end you can connect two device in same network if the app is running on both of them.
if you want to wake up the app first you have to use something like push notification, we don't implement it now.

let do it fast :D

ionic must be installed.

from your console run

ionic start ionic-peer blank
Enter fullscreen mode Exit fullscreen mode

then inside of your project folder, get peerjs:

npm install peerjs --save
Enter fullscreen mode Exit fullscreen mode

add esModuleInterop to your tsconfig.json as below:

"compilerOptions": {
    ....
    "esModuleInterop": true,
    ....
}
Enter fullscreen mode Exit fullscreen mode

create peer service:

ionic generate service providers/webrtc
Enter fullscreen mode Exit fullscreen mode

edit WebrtcService, it'll initiate peer-to-peer connection and make call.

import Peer from 'peerjs';

export class WebrtcService {
  peer: Peer;
  myStream: MediaStream;
  myEl: HTMLMediaElement;
  partnerEl: HTMLMediaElement;

  stun = 'stun.l.google.com:19302';
  mediaConnection: Peer.MediaConnection;
  options: Peer.PeerJSOption;
  stunServer: RTCIceServer = {
    urls: 'stun:' + this.stun,
  };

  constructor() {

    this.options = {  // not used, by default it'll use peerjs server
      key: 'cd1ft79ro8g833di',
      debug: 3
    };
  }

  getMedia() {
    navigator.getUserMedia({ audio: true, video: true }, (stream) => {
      this.handleSuccess(stream);
    }, (error) => {
      this.handleError(error);
    });
  }

  async init(userId: string, myEl: HTMLMediaElement, partnerEl: HTMLMediaElement) {
    this.myEl = myEl;
    this.partnerEl = partnerEl;
    try {
      this.getMedia();
    } catch (e) {
      this.handleError(e);
    }
    await this.createPeer(userId);
  }

  async createPeer(userId: string) {
    this.peer = new Peer(userId);
    this.peer.on('open', () => {
      this.wait();
    });
  }

  call(partnerId: string) {
    const call = this.peer.call(partnerId, this.myStream);
    call.on('stream', (stream) => {
      this.partnerEl.srcObject = stream;
    });
  }

  wait() {
    this.peer.on('call', (call) => {
      call.answer(this.myStream);
      call.on('stream', (stream) => {
        this.partnerEl.srcObject = stream;
      });
    });
  }

  handleSuccess(stream: MediaStream) {
    this.myStream = stream;
    this.myEl.srcObject = stream;
  }

  handleError(error: any) {
    if (error.name === 'ConstraintNotSatisfiedError') {
      const v = constraints.video;
     // this.errorMsg(`The resolution ${v.width.exact}x${v.height.exact} px is not supported by your device.`);
      this.errorMsg(`The resolution px is not supported by your device.`);
    } else if (error.name === 'PermissionDeniedError') {
      this.errorMsg('Permissions have not been granted to use your camera and ' +
        'microphone, you need to allow the page access to your devices in ' +
        'order for the demo to work.');
    }
    this.errorMsg(`getUserMedia error: ${error.name}`, error);
  }

  errorMsg(msg: string, error?: any) {
    const errorElement = document.querySelector('#errorMsg');
    errorElement.innerHTML += `<p>${msg}</p>`;
    if (typeof error !== 'undefined') {
      console.error(error);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

init method will create a peer connection with yourId and then register wait method to listen to any call .
if any call receive inside wait method, it'll answer the call automatically,
you can handel it by yourself
(new peer() without option will connect to default server, you can use other server or run your own peer server)
it'll also start to stream video/audio as you define in option and push it to object of html element.(we have to create a html element to show video in home.html page)

call method will ask peer server to connect you to your partner as call type.
both call method and wait method duty is streaming partner video/audio and show it inside of partner media frame(we will create it inside home page)

Edit home.html

<ion-content>
  <div id="errorMsg"></div>

  <video id="partner-video" (click)="swapVideo('my-video')" autoplay playsinline
    [ngClass]="{'top-video': topVideoFrame === 'partner-video', 'main-video': topVideoFrame != 'partner-video'}">
  </video>
  <video id="my-video" (click)="swapVideo('partner-video')" autoplay playsinline
    [ngClass]="{'top-video': topVideoFrame === 'my-video', 'main-video': topVideoFrame != 'my-video'}" >
  </video>


  <ion-row nowrap>
    <ion-button (click)="init()">Login As: </ion-button>
    <ion-item>
      <ion-input type="text" [(ngModel)]="userId" placeholder="enter your nick name"></ion-input>
    </ion-item>
  </ion-row>

  <ion-row nowrap>
    <ion-button (click)="call()">Call To: </ion-button>
    <ion-item>
      <ion-input type="text" [(ngModel)]="partnerId" placeholder="your partner nick name"></ion-input>
    </ion-item>
  </ion-row>  
</ion-content>

Enter fullscreen mode Exit fullscreen mode

video tag will show you stream media of you and your partner.
you have to login with nick name, because of using default server, you may see other people with easy nick name :D

Edit home.ts like this

import { WebrtcService } from '../providers/webrtc.service';

export class HomePage {
  topVideoFrame = 'partner-video';
  userId: string;
  partnerId: string;
  myEl: HTMLMediaElement;
  partnerEl: HTMLMediaElement;

  constructor(
    public webRTC: WebrtcService,
    public elRef: ElementRef
  ) {}

  init() {
    this.myEl = this.elRef.nativeElement.querySelector('#my-video');
    this.partnerEl = this.elRef.nativeElement.querySelector('#partner-video');
    this.webRTC.init(this.userId, this.myEl, this.partnerEl);
  }

  call() {
    this.webRTC.call(this.partnerId);
    this.swapVideo('my-video');
  }

  swapVideo(topVideo: string) {
    this.topVideoFrame = topVideo;
  }
}

Enter fullscreen mode Exit fullscreen mode

login method will assign video frame to html element and pass them to webrtcservice, so the service can push stream to them.

call method will pass partnerId to webrtc service for connection to your partner.

Edit home.scss

.top-video{
    position: absolute;
    top: 100px;
    right: 100px;
    max-width: 100px;
    z-index: 2;
}

.main-video{
    width: 100%;
    max-height: 70%;
}
Enter fullscreen mode Exit fullscreen mode

after you and your partner get connected, you can swap video with swapVideo method and [ngClass].

webrtc service will ask for camera/microphone permission.
because of that, when we run application outside of localhost, browser will block permission, so we have to run it with --ssl parameter.
first join your pc/laptop with your second device (pc/laptop or mobile) to the same network
then run ionic serve with your ip address, for example:

ionic serve --address 192.168.43.105 --ssl
Enter fullscreen mode Exit fullscreen mode

in your second device visit that address with https prefix:

https://192.168.43.105:8100
Enter fullscreen mode Exit fullscreen mode

source on github

That's it, i hope it's useful.

Top comments (43)

Collapse
 
eestebanjjosue profile image
eestebanjjosue • Edited

Hi, thanks for this awesome tutorial, I could make a basic videocall app, but I am facing a problem, in my phone when I make it an app the camera does not work, I can see the camera and audio in the other device(my pc). Sometimes I get an error "getUserMedia error: NotReadableError". Ant idea what could be wrong? If I use my android device as a second device and I connect to the app throug LAN it works.
P.D. The sound gets returned from the speakers of the pc so it makes that awful sound if I am not wearing headphones. Is there a way to fix this too?

Collapse
 
timsar2 profile image
timsar2

If you have two htmlElemeny, one for your camera and another for your partner, you can disable input audio from partner element.
stackoverflow.com/questions/184061...

Collapse
 
timsar2 profile image
timsar2

Thank you, You can mute your video media output.

Collapse
 
naonvl profile image
naonvl • Edited

Hi this is awesome, i'm building an app based on your tutorial. i currently want to add some functionality like waiting for an answer, and also automatic destroy both user.

i also posting on stackoverflow stackoverflow.com/questions/672497..., if you got time pls help me there. thanks

Collapse
 
timsar2 profile image
timsar2 • Edited

I'm glad to hear that, I implemented video call in a real app and for that, I used simple-peer instead of peerJs.
I had no time to write about my new experience, also in the real app I found we will need a turn server to work when clients are not in the same network.
For turn server i used this repository:
github.com/coturn/coturn
run it in Linux. (for windows os use WSL)

Collapse
 
naonvl profile image
naonvl

wow thats great, so do you prefer simple peer instead of peerjs? cause i got problems with listening an event if someone on the other side click a button or something. they said to use socket.io, but i dontwanna use that

Thread Thread
 
timsar2 profile image
timsar2

Yes simplepeer was better for me,
for socket i used signalr in dot net core.
using hubconnection
you can use any other language as your signaling server line nodjs, java, python etc...

Thread Thread
 
naonvl profile image
naonvl • Edited

great!! but do you have any suggestion on listening to an event that someone on the other side fire an event? like closing or destroying the connection? i can show you my code if you could help me out

Thread Thread
 
timsar2 profile image
timsar2

Calling peer close() function in frontend side must work and As I said, From backend you can call hubconnection methods in signalr dotnet core
for other language i dont know

Thread Thread
 
naonvl profile image
naonvl • Edited

actually i use peerjs cloud server, so i only working on front end. Is using my own peerjs server a must?

Thread Thread
 
timsar2 profile image
timsar2

peerjs server is just for signaling, (both clients will create some signale and send them to each other with server)
you can implement signaling server in any language.
but for your priblem to alter other client to close the connection it must work. I don't know why close() function not triggered.
maybe you have to look at peerjs document.

Thread Thread
 
naonvl profile image
naonvl

yeah what i wrote was following the peerjs docs, i post an issue on their repo. Thanks for your help, hope you'll share more knowledge

Collapse
 
iometrine profile image
iometrine

Hello,

Thank you for your post.
I try this on my ionic capacitor project all works good (web and android) but ios.
On IOS, getUserMerdia throw an exception notallowederror. it doesn't work on ios 14.4
Did you try on ios ? If yes how did you get the stream ?
Do i need to use a plugin such as iosrtc?
Thank you for your answer

Collapse
 
timsar2 profile image
timsar2 • Edited

It's good sound if it is helpful.
Sry I didn't try on ios.
If you are going to use it on mobile network you need turn server. I hope i have free time to write about how to make real video chat

Collapse
 
gemdajs profile image
Nasirudeen Olawale

It will be helpful if you can share directives on how to make the app serve with ssl on (android) devices.

Collapse
 
timsar2 profile image
timsar2 • Edited

I used same address then running android + Conveyore (an extension) for visual studio
to solve cors issue on local host
try something like this: ionic cordova run android --address 192.168.43.105 --ssl

Collapse
 
lautalopez profile image
lautalopez

how can i implement a webrtc backend server?

Collapse
 
timsar2 profile image
timsar2 • Edited

Look at this repository
github.com/Shhzdmrz/SignalRCoreWebRTC
Good backend with signalr
And this:
dev.to/sebalr/video-call-with-webr...

You just need a signaling server
Each client should send his signal to other client
For doing this you need a server to swap client 's signal.
So used webrtc to create signal in both client and swap them with backend.
I used signalr and websocket
You can use any language and technic to swap signal between clients.

Collapse
 
innovativethinkingptyltd profile image
Innovative Thinking

Are you available to provide a paid integration?

Thread Thread
 
timsar2 profile image
timsar2 • Edited

Send me some detail please:
info.golab@gmail.com

Collapse
 
travbradfield profile image
TravBradfield

How do I end the call and close the video?

Collapse
 
timsar2 profile image
timsar2

this.peer.close()

Collapse
 
prosenjitbarui9 profile image
Prosenjit Barui

How to create room with peerjs so multi user can join that room

Collapse
 
cacardinal profile image
Chris

Would this work with a Firebase WebRTC or does it require a Peerjs server?

webrtc.org/getting-started/firebas...

Collapse
 
timsar2 profile image
timsar2 • Edited

i didn't touch firebase webrtc, but webrtc client's just need a signaling server to swap their signale(created in client side).
you can also use simple peer and a signaling server github.com/feross/simple-peer

Collapse
 
gafeell profile image
Muhammad Bilal

Hi, Can you please guide me how do i connect video call from two different network. Your code only work if app is connected to same network.

Collapse
 
timsar2 profile image
timsar2 • Edited

Ofc, As i said you need turn server to establish a connection.
Look at coturn project on github.
run it on linux or first instal wsl in windwos and run it from wsl.

Collapse
 
gafeell profile image
Muhammad Bilal

Do you have any server which i can use in my code?

Thread Thread
 
timsar2 profile image
timsar2

No mate, i dont have free public turn server.
It's just for client.
But why u not try to run it.
Just follow coturn repository from github, Run it from linux or as i said just install wsl on windows and then you can run linux from shell

Collapse
 
iometrine profile image
iometrine

If that can help someone, i created a proof of concept using peersjs with ionic capacitor thanks to the work of @timsar2 .
github.com/iometrine/ionic-iosrtc-....
Enjoy