DEV Community

Cover image for Adding Video Chat To Your Laravel App
Kofi Mupati
Kofi Mupati

Posted on • Updated on

Adding Video Chat To Your Laravel App

Introduction

I was required to build a custom video chat application for a Vuejs and Laravel project. I went through a lot of hoops to get it working. I will share all that I learnt throughout the process over here.

Final Project Repository: https://github.com/Mupati/laravel-video-chat

Requirements

  • This tutorial assumes you know how to set up a new Laravel project with VueJs authentication. Create some users after setting up your project. You should be familiar with Laravel's broadcasting mechanism and have a fair idea of how WebSockets work.

  • Set up a free pusher account on pusher.com

  • Set up your ICE SERVER (TURN SERVER) details. This tutorial is a good guide. HOW TO INSTALL COTURN.

Project Setup

# Install needed packages
composer require pusher/pusher-php-server "~4.0"
npm install --save laravel-echo pusher-js simple-peer
Enter fullscreen mode Exit fullscreen mode

Configuring Backend

  • Add routes for video page in routes/web.php. The routes will be used to visit the video call page, make calls and receive calls.
Route::get('/video-chat', function () {
    // fetch all users apart from the authenticated user
    $users = User::where('id', '<>', Auth::id())->get();
    return view('video-chat', ['users' => $users]);
});

// Endpoints to call or receive calls.
Route::post('/video/call-user', 'App\Http\Controllers\VideoChatController@callUser');
Route::post('/video/accept-call', 'App\Http\Controllers\VideoChatController@acceptCall');
Enter fullscreen mode Exit fullscreen mode
  • Uncomment BroadcastServiceProvider in config/app.php. This allows us to use Laravel's broadcasting system.
+ App\Providers\BroadcastServiceProvider::class
- //App\Providers\BroadcastServiceProvider::class 
Enter fullscreen mode Exit fullscreen mode
  • Create a Presence Channel for the Video Chat Application in routes/channels.php. When an authenticated user subscribes to the channel (presence-video-channel), we return the users's id and name. This is how we are able to get the user who is logged in and can be called.
Broadcast::channel('presence-video-channel', function($user) {
    return ['id' => $user->id, 'name' => $user->name];
});
Enter fullscreen mode Exit fullscreen mode
  • Create StartVideoChat event.This event will be called when placing a call or accepting a call and it will broadcast on the presence-video-call channel. Users subscribed to the channel will be listening to this event on the frontend so that incoming call notifications can be triggered.
php artisan make:event StartVideoChat
Enter fullscreen mode Exit fullscreen mode
  • Add the following code to app/Events/StartVideoChat.php. The StartVideoChat event broadcasts to presence-video-channel so that the data needed to initiate the video call is shared on the channel.
<?php

namespace App\Events;

use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class StartVideoChat implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $data;
    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct($data)
    {
        $this->data = $data;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PresenceChannel('presence-video-channel');
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Create VideoChatController to make and accept calls.
php artisan make:controller VideoChatController
Enter fullscreen mode Exit fullscreen mode
  • Add the following to the VideoChatController
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use App\Events\StartVideoChat;

class VideoChatController extends Controller
{

    public function callUser(Request $request)
    {
        $data['userToCall'] = $request->user_to_call;
        $data['signalData'] = $request->signal_data;
        $data['from'] = Auth::id();
        $data['type'] = 'incomingCall';

        broadcast(new StartVideoChat($data))->toOthers();
    }
    public function acceptCall(Request $request)
    {
        $data['signal'] = $request->signal;
        $data['to'] = $request->to;
        $data['type'] = 'callAccepted';
        broadcast(new StartVideoChat($data))->toOthers();
    }
}
Enter fullscreen mode Exit fullscreen mode

Methods in the VideoChatController

One thing to understand is that the VideoChat Application is a realtime application that works with web sockets. The endpoints are just needed to establish the link between the 2 calling parties after which the communication data is exchanged via websockets.

Let's try to understand what the 2 methods in the Controller are doing:

callUser Method

  • user_to_call: id of the user the initiator of the call wants to reach.
  • signal_data: The initial signal data (offer) sent by the caller from the webrtc client (simple-peerjs is the webrtc wrapper we are using). These are the parameters received. We create a data object with 2 additional properties,from and type then broadcast the data with the StartVideoChat event which will be listened to on the frontend.
  • from: is the id of the user placing the call. We use the authenticated user's id.
  • type: is a property of the data which will indicate that there is an incoming call on the channel. The notification will be shown to the user whose id corresponds to the value of user_to_call.

acceptCall Method

  • signal: This is the callee's answer data.
  • to: The caller's of the call's id. The signal data for the answered call is sent to the user whose id matches to and this is supposed to be the caller's id.
  • type: A property added to the data to be sent over the channel indicating that the call recipient has accepted the call.

Configuring Frontend

  • Instantiate Laravel Echo and Pusher in resources/js/bootstrap.js by uncommenting the following block of code.
+ import Echo from 'laravel-echo';
+ window.Pusher = require('pusher-js');
+ window.Echo = new Echo({
+     broadcaster: 'pusher',
+     key: process.env.MIX_PUSHER_APP_KEY,
+     cluster: process.env.MIX_PUSHER_APP_CLUSTER,
+     forceTLS: true
+ });
- import Echo from 'laravel-echo';
- window.Pusher = require('pusher-js');
- window.Echo = new Echo({
-     broadcaster: 'pusher',
-     key: process.env.MIX_PUSHER_APP_KEY,
-     cluster: process.env.MIX_PUSHER_APP_CLUSTER,
-     forceTLS: true
-});
Enter fullscreen mode Exit fullscreen mode
  • Create resources/js/helpers.js. Add a getPermissions function to help with permission access for microphone and videos. This method handles the video and audio permission that is required by browsers to make the video calls. It waits for the user to accept the permissions before we can proceed with the video call. We allow both audio and video. Read more on MDN Website.
export const getPermissions = () => {
    // Older browsers might not implement mediaDevices at all, so we set an empty object first
    if (navigator.mediaDevices === undefined) {
        navigator.mediaDevices = {};
    }

    // Some browsers partially implement mediaDevices. We can't just assign an object
    // with getUserMedia as it would overwrite existing properties.
    // Here, we will just add the getUserMedia property if it's missing.
    if (navigator.mediaDevices.getUserMedia === undefined) {
        navigator.mediaDevices.getUserMedia = function(constraints) {
            // First get ahold of the legacy getUserMedia, if present
            const getUserMedia =
                navigator.webkitGetUserMedia || navigator.mozGetUserMedia;

            // Some browsers just don't implement it - return a rejected promise with an error
            // to keep a consistent interface
            if (!getUserMedia) {
                return Promise.reject(
                    new Error("getUserMedia is not implemented in this browser")
                );
            }

            // Otherwise, wrap the call to the old navigator.getUserMedia with a Promise
            return new Promise((resolve, reject) => {
                getUserMedia.call(navigator, constraints, resolve, reject);
            });
        };
    }
    navigator.mediaDevices.getUserMedia =
        navigator.mediaDevices.getUserMedia ||
        navigator.webkitGetUserMedia ||
        navigator.mozGetUserMedia;

    return new Promise((resolve, reject) => {
        navigator.mediaDevices
            .getUserMedia({ video: true, audio: true })
            .then(stream => {
                resolve(stream);
            })
            .catch(err => {
                reject(err);
                //   throw new Error(`Unable to fetch stream ${err}`);
            });
    });
};
Enter fullscreen mode Exit fullscreen mode

Incoming Call

  • Create a Video Chat Component, resources/js/components/VideoChat.vue.
<template>
  <div>
    <div class="container">
      <div class="row">
        <div class="col">
          <div class="btn-group" role="group">
            <button
              type="button"
              class="btn btn-primary mr-2"
              v-for="user in allusers"
              :key="user.id"
              @click="placeVideoCall(user.id, user.name)"
            >
              Call {{ user.name }}
              <span class="badge badge-light">{{
                getUserOnlineStatus(user.id)
              }}</span>
            </button>
          </div>
        </div>
      </div>
      <!--Placing Video Call-->
      <div class="row mt-5" id="video-row">
        <div class="col-12 video-container" v-if="callPlaced">
          <video
            ref="userVideo"
            muted
            playsinline
            autoplay
            class="cursor-pointer"
            :class="isFocusMyself === true ? 'user-video' : 'partner-video'"
            @click="toggleCameraArea"
          />
          <video
            ref="partnerVideo"
            playsinline
            autoplay
            class="cursor-pointer"
            :class="isFocusMyself === true ? 'partner-video' : 'user-video'"
            @click="toggleCameraArea"
            v-if="videoCallParams.callAccepted"
          />
          <div class="partner-video" v-else>
            <div v-if="callPartner" class="column items-center q-pt-xl">
              <div class="col q-gutter-y-md text-center">
                <p class="q-pt-md">
                  <strong>{{ callPartner }}</strong>
                </p>
                <p>calling...</p>
              </div>
            </div>
          </div>
          <div class="action-btns">
            <button type="button" class="btn btn-info" @click="toggleMuteAudio">
              {{ mutedAudio ? "Unmute" : "Mute" }}
            </button>
            <button
              type="button"
              class="btn btn-primary mx-4"
              @click="toggleMuteVideo"
            >
              {{ mutedVideo ? "ShowVideo" : "HideVideo" }}
            </button>
            <button type="button" class="btn btn-danger" @click="endCall">
              EndCall
            </button>
          </div>
        </div>
      </div>
      <!-- End of Placing Video Call  -->

      <!-- Incoming Call  -->
      <div class="row" v-if="incomingCallDialog">
        <div class="col">
          <p>
            Incoming Call From <strong>{{ callerDetails.name }}</strong>
          </p>
          <div class="btn-group" role="group">
            <button
              type="button"
              class="btn btn-danger"
              data-dismiss="modal"
              @click="declineCall"
            >
              Decline
            </button>
            <button
              type="button"
              class="btn btn-success ml-5"
              @click="acceptCall"
            >
              Accept
            </button>
          </div>
        </div>
      </div>
      <!-- End of Incoming Call  -->
    </div>
  </div>
</template>

<script>
import Peer from "simple-peer";
import { getPermissions } from "../helpers";
export default {
  props: [
    "allusers",
    "authuserid",
    "turn_url",
    "turn_username",
    "turn_credential",
  ],
  data() {
    return {
      isFocusMyself: true,
      callPlaced: false,
      callPartner: null,
      mutedAudio: false,
      mutedVideo: false,
      videoCallParams: {
        users: [],
        stream: null,
        receivingCall: false,
        caller: null,
        callerSignal: null,
        callAccepted: false,
        channel: null,
        peer1: null,
        peer2: null,
      },
    };
  },

  mounted() {
    this.initializeChannel(); // this initializes laravel echo
    this.initializeCallListeners(); // subscribes to video presence channel and listens to video events
  },
  computed: {
    incomingCallDialog() {
      if (
        this.videoCallParams.receivingCall &&
        this.videoCallParams.caller !== this.authuserid
      ) {
        return true;
      }
      return false;
    },

    callerDetails() {
      if (
        this.videoCallParams.caller &&
        this.videoCallParams.caller !== this.authuserid
      ) {
        const incomingCaller = this.allusers.filter(
          (user) => user.id === this.videoCallParams.caller
        );

        return {
          id: this.videoCallParams.caller,
          name: `${incomingCaller[0].name}`,
        };
      }
      return null;
    },
  },
  methods: {
    initializeChannel() {
      this.videoCallParams.channel = window.Echo.join("presence-video-channel");
    },

    getMediaPermission() {
      return getPermissions()
        .then((stream) => {
          this.videoCallParams.stream = stream;
          if (this.$refs.userVideo) {
            this.$refs.userVideo.srcObject = stream;
          }
        })
        .catch((error) => {
          console.log(error);
        });
    },

    initializeCallListeners() {
      this.videoCallParams.channel.here((users) => {
        this.videoCallParams.users = users;
      });

      this.videoCallParams.channel.joining((user) => {
        // check user availability
        const joiningUserIndex = this.videoCallParams.users.findIndex(
          (data) => data.id === user.id
        );
        if (joiningUserIndex < 0) {
          this.videoCallParams.users.push(user);
        }
      });

      this.videoCallParams.channel.leaving((user) => {
        const leavingUserIndex = this.videoCallParams.users.findIndex(
          (data) => data.id === user.id
        );
        this.videoCallParams.users.splice(leavingUserIndex, 1);
      });
      // listen to incomming call
      this.videoCallParams.channel.listen("StartVideoChat", ({ data }) => {
        if (data.type === "incomingCall") {
          // add a new line to the sdp to take care of error
          const updatedSignal = {
            ...data.signalData,
            sdp: `${data.signalData.sdp}\n`,
          };

          this.videoCallParams.receivingCall = true;
          this.videoCallParams.caller = data.from;
          this.videoCallParams.callerSignal = updatedSignal;
        }
      });
    },
    async placeVideoCall(id, name) {
      this.callPlaced = true;
      this.callPartner = name;
      await this.getMediaPermission();
      this.videoCallParams.peer1 = new Peer({
        initiator: true,
        trickle: false,
        stream: this.videoCallParams.stream,
        config: {
          iceServers: [
            {
              urls: this.turn_url,
              username: this.turn_username,
              credential: this.turn_credential,
            },
          ],
        },
      });

      this.videoCallParams.peer1.on("signal", (data) => {
        // send user call signal
        axios
          .post("/video/call-user", {
            user_to_call: id,
            signal_data: data,
            from: this.authuserid,
          })
          .then(() => {})
          .catch((error) => {
            console.log(error);
          });
      });

      this.videoCallParams.peer1.on("stream", (stream) => {
        console.log("call streaming");
        if (this.$refs.partnerVideo) {
          this.$refs.partnerVideo.srcObject = stream;
        }
      });

      this.videoCallParams.peer1.on("connect", () => {
        console.log("peer connected");
      });

      this.videoCallParams.peer1.on("error", (err) => {
        console.log(err);
      });

      this.videoCallParams.peer1.on("close", () => {
        console.log("call closed caller");
      });

      this.videoCallParams.channel.listen("StartVideoChat", ({ data }) => {
        if (data.type === "callAccepted") {
          if (data.signal.renegotiate) {
            console.log("renegotating");
          }
          if (data.signal.sdp) {
            this.videoCallParams.callAccepted = true;
            const updatedSignal = {
              ...data.signal,
              sdp: `${data.signal.sdp}\n`,
            };
            this.videoCallParams.peer1.signal(updatedSignal);
          }
        }
      });
    },

    async acceptCall() {
      this.callPlaced = true;
      this.videoCallParams.callAccepted = true;
      await this.getMediaPermission();
      this.videoCallParams.peer2 = new Peer({
        initiator: false,
        trickle: false,
        stream: this.videoCallParams.stream,
        config: {
          iceServers: [
            {
              urls: this.turn_url,
              username: this.turn_username,
              credential: this.turn_credential,
            },
          ],
        },
      });
      this.videoCallParams.receivingCall = false;
      this.videoCallParams.peer2.on("signal", (data) => {
        axios
          .post("/video/accept-call", {
            signal: data,
            to: this.videoCallParams.caller,
          })
          .then(() => {})
          .catch((error) => {
            console.log(error);
          });
      });

      this.videoCallParams.peer2.on("stream", (stream) => {
        this.videoCallParams.callAccepted = true;
        this.$refs.partnerVideo.srcObject = stream;
      });

      this.videoCallParams.peer2.on("connect", () => {
        console.log("peer connected");
        this.videoCallParams.callAccepted = true;
      });

      this.videoCallParams.peer2.on("error", (err) => {
        console.log(err);
      });

      this.videoCallParams.peer2.on("close", () => {
        console.log("call closed accepter");
      });

      this.videoCallParams.peer2.signal(this.videoCallParams.callerSignal);
    },
    toggleCameraArea() {
      if (this.videoCallParams.callAccepted) {
        this.isFocusMyself = !this.isFocusMyself;
      }
    },
    getUserOnlineStatus(id) {
      const onlineUserIndex = this.videoCallParams.users.findIndex(
        (data) => data.id === id
      );
      if (onlineUserIndex < 0) {
        return "Offline";
      }
      return "Online";
    },
    declineCall() {
      this.videoCallParams.receivingCall = false;
    },

    toggleMuteAudio() {
      if (this.mutedAudio) {
        this.$refs.userVideo.srcObject.getAudioTracks()[0].enabled = true;
        this.mutedAudio = false;
      } else {
        this.$refs.userVideo.srcObject.getAudioTracks()[0].enabled = false;
        this.mutedAudio = true;
      }
    },

    toggleMuteVideo() {
      if (this.mutedVideo) {
        this.$refs.userVideo.srcObject.getVideoTracks()[0].enabled = true;
        this.mutedVideo = false;
      } else {
        this.$refs.userVideo.srcObject.getVideoTracks()[0].enabled = false;
        this.mutedVideo = true;
      }
    },

    stopStreamedVideo(videoElem) {
      const stream = videoElem.srcObject;
      const tracks = stream.getTracks();
      tracks.forEach((track) => {
        track.stop();
      });
      videoElem.srcObject = null;
    },
    endCall() {
      // if video or audio is muted, enable it so that the stopStreamedVideo method will work
      if (!this.mutedVideo) this.toggleMuteVideo();
      if (!this.mutedAudio) this.toggleMuteAudio();
      this.stopStreamedVideo(this.$refs.userVideo);
      if (this.authuserid === this.videoCallParams.caller) {
        this.videoCallParams.peer1.destroy();
      } else {
        this.videoCallParams.peer2.destroy();
      }
      this.videoCallParams.channel.pusher.channels.channels[
        "presence-presence-video-channel"
      ].disconnect();

      setTimeout(() => {
        this.callPlaced = false;
      }, 3000);
    },
  },
};
</script>

<style scoped>
#video-row {
  width: 700px;
  max-width: 90vw;
}

#incoming-call-card {
  border: 1px solid #0acf83;
}

.video-container {
  width: 700px;
  height: 500px;
  max-width: 90vw;
  max-height: 50vh;
  margin: 0 auto;
  border: 1px solid #0acf83;
  position: relative;
  box-shadow: 1px 1px 11px #9e9e9e;
  background-color: #fff;
}

.video-container .user-video {
  width: 30%;
  position: absolute;
  left: 10px;
  bottom: 10px;
  border: 1px solid #fff;
  border-radius: 6px;
  z-index: 2;
}

.video-container .partner-video {
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  top: 0;
  z-index: 1;
  margin: 0;
  padding: 0;
}

.video-container .action-btns {
  position: absolute;
  bottom: 20px;
  left: 50%;
  margin-left: -50px;
  z-index: 3;
  display: flex;
  flex-direction: row;
}

/* Mobiel Styles */
@media only screen and (max-width: 768px) {
  .video-container {
    height: 50vh;
  }
}
</style>
Enter fullscreen mode Exit fullscreen mode

Breakdown of the video-chat component.

  • We import Peer from simple-peer which is the package that makes interacting with webrtc easier for us.
  • The component accepts the following props:

    allusers: All registered users excluding the currently authenticated user. These users will be displayed. We don't want to permit the authenticated user to call oneself.


    authuserid: The id of the authenticated user.

    turn_url: The URL of your turn server to be used in an instance of simple-peer i.e Peer.


    turn_username: Username from TURN Server.

    turn_credential: The password for the turn_username.

  • When the component is mounted we subscribe to the presence-video-channel with the initializeChannel method. We use Laravel-echo for that.

  • We initializeCallListeners on the channel we subscribed to. There are methods provided by Laravel-echo to know how many users have subscribed to the channel, users who are joining and users leaving the channel. We also listen to the StartVideoChat event on the presence-video-channel for incoming calls.

  • We list all the users in the database from the allUsers prop and indicate whether they are online or not. Online means they have also subscribed to the presence-video-channel. This will take effect on whichever page you place the video-chat component. In this tutorial, we have a video-chat page where we place the component.

  • placeVideoCall is used to make a call. We pass the id and name of the user being called as parameters.
    We ask the user to grant the browser access to the microphone and camera with getMediaPermission method. The streaming data is displayed in the browser. The caller now sees their face in the browser.
    We create a Peer for the caller. When there is a signalling event, peer.on('signal',..) we send the signalling data to the /video/call-user endpoint on our backend.
    The recipient receives the incoming call notification and when they accept the call, we signal the caller with the caller with an answer signal.

    The peer.on('stream',...)listener receives the streaming data which is displayed on the recipients part in the browser.

  • acceptCall method is used to accept an incoming call. When a user sees an incoming call notification, they click on the accept button. We signal the receiver with the signal data received from the caller.

    We get permission to access the camera and microphone and display the streaming data on our UI.

    This creates a second instance of the Peer with the initiator property set to false to indicate that the new Peer is a receiver.

    We hit the accept-call endpoint and send our signalling data (an answer) to the caller.

When the streaming starts, we display the caller's stream in the browser as well and now communication continues without hitting our backend but through the websocket powered by pusher.

  • The remaining functions are used for muting audio, disable video stream and to end the call.

Video Chat

  • Register the VideoChat.vue component in resources/js/app.js
Vue.component("video-chat", require("./components/VideoChat.vue").default);
Enter fullscreen mode Exit fullscreen mode
  • Create the video chat view in resources/views/video-chat.blade.php
@extends('layouts.app')

@section('content')
    <video-chat :allusers="{{ $users }}" :authUserId="{{ auth()->id() }}" turn_url="{{ env('TURN_SERVER_URL') }}"
        turn_username="{{ env('TURN_SERVER_USERNAME') }}" turn_credential="{{ env('TURN_SERVER_CREDENTIAL') }}" />
@endsection
Enter fullscreen mode Exit fullscreen mode
  • Update env variables. Insert your Pusher API keys
BROADCAST_DRIVER=pusher

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=

TURN_SERVER_URL=
TURN_SERVER_USERNAME=
TURN_SERVER_CREDENTIAL=
Enter fullscreen mode Exit fullscreen mode

Credits

I found a lot of resources beneficial which I cannot share all over here, but the following YouTube videos helped in my understanding and arriving at this implementation.

  1. Coding With Chaim
  2. We Code

I'd like to hear your thoughts about how easy it is to follow through with this article.

Update

I just published a Live streaming implementation with WebRTC. Check it out over here:

Discussion (35)

Collapse
ruchitdarji profile image
Ruchit Darji • Edited on

Hello Kofi Mupati,

I have one question related to implementing this video call functionality...

Q. I need to display a video call notification and let the user choose between finish the current call and takes the new one or continue in the current call and don't take the new call...

Please Suggest the how to make this feature...

Collapse
mupati profile image
Kofi Mupati Author • Edited on

This will take some tweaking. You need to send an event to the caller that will inform them to cancel the call. I've not thought through the use case for multiple incoming calls. This is beyond the scope of what I can offer in a demo application.

Collapse
ruchitdarji profile image
Ruchit Darji • Edited on

So, All time refresh browser page of video call at both side ???

Because I need hangup first call and attend second call without page refresh...

But you suggest refresh both browsers to call again for same user second time calling...

Thread Thread
mupati profile image
Kofi Mupati Author

that is not necessary. Read more on how to do all that through websocket events and listeners.

Thread Thread
ruchitdarji profile image
Ruchit Darji

Hello Kofi Mupati,

Any reference to add a noise canceling filter to the video call...right now if the volume is high there is a lot of ecco...

Thread Thread
mupati profile image
Kofi Mupati Author

I've not looked into that yet.

Thread Thread
ruchitdarji profile image
Ruchit Darji

Let us know if you find anything about this. It will be very helpful and grateful for us.

Thread Thread
ruchitdarji profile image
Ruchit Darji

Hello Kofi Mupati,

did you find anything related to this echo issue in the video call ???

I have spent many days to solving this issue but not getting anything solution in this.

Please suggest any reference links where can i solve this issue in my video chat application.

Thread Thread
mupati profile image
Kofi Mupati Author

I've not had time to look into that yet.

Thread Thread
ruchitdarji profile image
Ruchit Darji

Hello Kofi Mupati,

I have fixed this issue in my video chat functionality..

I have added "muted" parameter in placed user video element div in vue component file.

Many many thanks for your guidance and documentation...

Thread Thread
mupati profile image
Kofi Mupati Author

That's good to know. All the best Ruchit Darji

Thread Thread
ruchitdarji profile image
Ruchit Darji

Hello Kofi Mupati,

I have one question related this case...

how to placed video call is automatically ended within 30 seconds if the receiver user cannot accept the incoming call ???

So, Please suggest how can i do this...

Thread Thread
mupati profile image
Kofi Mupati Author

when you place the call, you can set a timer which calls the endCall after the specified period. Play around with the setTimeout function in JavaScript.

Thread Thread
ruchitdarji profile image
Ruchit Darji

Thank you again for your guidance...

Collapse
nehasahu001 profile image
nehasahu001 • Edited on

Hello Kofi Mupati,

Big Thanks to you to help it out.

I followed this steps and I able to impletement on system. And It's working fine for me. But when i try in different device, it's failed to connect. and I got an error like 'Connection Failed'.

Can you please help me here?

Thank you again for your guidance.

Collapse
mupati profile image
Kofi Mupati Author

Are the devices on a different network?

Collapse
nehasahu001 profile image
nehasahu001

Yes, it's different network.

Thread Thread
mupati profile image
Kofi Mupati Author

This is where you need to set up a TURN server.

Thread Thread
nehasahu001 profile image
nehasahu001

Okay, is it reliable to use this numb.viagenie.ca/ free STUN/TURN server here?

Thread Thread
mupati profile image
Kofi Mupati Author

Not tried it before but you can go ahead and use it.

Thread Thread
nehasahu001 profile image
nehasahu001

Thanks a lot!

Thread Thread
nehasahu001 profile image
nehasahu001

I tried TURN Server also but still it's same error i am facing. As I found here (github.com/feross/simple-peer/issu...) that it's taking much time to paste the signals. don't you think so? If yes than, is there any improvement in time?

Collapse
naeemijaz profile image
Naeem Ijaz

Bro Thanks
I am facing a problem.
i'm Getting Error
DOMException: Failed to construct 'RTCPeerConnection': '' is not a valid URL.
Please Help Me ASAP Because it is my final Year Project
Here is the SS: i.imgur.com/S7AKqHg.png

Collapse
mupati profile image
Kofi Mupati Author

Maybe an error related to the absence of TURN Server

Collapse
naeemijaz profile image
Naeem Ijaz

Sir Thankyou So Much For Your Reply
How can i create TURN in my hosting panal.
I'm using shared hosting in NAMECHEAP

Thread Thread
mupati profile image
Kofi Mupati Author

get a digitalocean droplet and use the following tutorial: ourcodeworld.com/articles/read/117...

but before that, comment out the following block of code and check whether it will work.

        config: {
          iceServers: [
            {
              urls: this.turn_url,
              username: this.turn_username,
              credential: this.turn_credential,
            },
          ],
        },
Enter fullscreen mode Exit fullscreen mode
Collapse
dariochiappello profile image
Darío Chiappello

I have this problem when I click the call button.

{message: "Failed to connect to Pusher.", exception: "Illuminate\Broadcasting\BroadcastException",…}
exception: "Illuminate\Broadcasting\BroadcastException"
file:"\vendor\laravel\framework\src\Illuminate\Broadcasting\Broadcasters\PusherBroadcaster.php"
line: 122
message: "Failed to connect to Pusher."

Collapse
mupati profile image
Kofi Mupati Author

You must install and configure pusher. You must create an account and add the credentials.

Collapse
alvinluo666 profile image
Alvin Luo

Bro Thanks for your explanation.
I am facing a problem :
I implement it the same as you , I do it in localhost , I do not use turn server , when I call a user from another user , it works fine. But second time I call it and get an error say : Fails To set remote answer ! Pls help me

Collapse
mupati profile image
Kofi Mupati Author

Were you able to establish the call between the 2 participants? Note that this video call is limited to only 2 people and not a group call. At times you'd have to refresh both browsers to call again. Obviously, this is a demo and one has to consider a number of things to make it very production-ready.

Collapse
khophi profile image
KhoPhi

Such a detailed article. Thanks.
Going to check out the github project.

Collapse
hbakouane profile image
hbakouane

A great article, Thanks.

Collapse
stanliwise profile image
stanliwise

such a nice article!
Nice

Collapse
rameshphp profile image
Ramesh-php

Hi Mupati

I'm getting error, logged in different user and different browser also. But all the user to show offline only.

Collapse
crazeetee profile image
Timothy Kimemia

can someone explain/guide how to reduce or remove the echo from the video when connected?