jamalag / webrtc-part-5-React-Hooks

This is React Hooks version of webrtc-part-5

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

not working in android browsers

nawaabazmi opened this issue · comments

its working perfectly fine i have added few more functionalities like camera changing mute remote streams. but when i tried to connect thought chrome android socket get connected peer counts on desktop increases but on mobile it just show local stream only. but on refreshing/reload mobile page peer counts increases. Can you suggest what should i do? Do i have to implement adapter if yes please suggest me any link through which i can understand about implementation of webrtc-adapter

if find these errors
Something went wrong! pc not created!! DOMException: Track already exists. App.js:172

Uncaught TypeError: Cannot read property 'addStream' of null App.js:248

Uncaught (in promise) DOMException: Failed to execute 'addIceCandidate' on 'RTCPeerConnection': Error processing ICE candidate

I have cleared the errors... So far everything looks good. But major problem android not solved yet. I have traced that local media stream working and even generating answer and even getting connected but on the mobile all the remote stream are black it's video components are rendered but with no data..and on the other hand on computer all the video components are rendering with any error except android one it's rendering but with black screen... also on mobile after rendering data's are coming from server but it doesn't get update on on page like new new peers video component peer counts, until I just reload it

Hi nawaabazmi
I haven't really tried running the web application on android chrome browser. Seems like you are doing great progress. If I get time I'll look into it and if you manage to solve please share your experience.

thanks mate I got the solution . I even found the same issue in you video for react native . The reason for the this black screen is that turn server is expired i used xirsys stun and turn server for the ice and also i found that sometimes addstream is not triggering because connection is not extablished properly solution for that is to put delay of 3 sec in addstream

its working now i found another bug too. when a peer let say disconnect and reconnect then video components of 1st person repeats 2 times and repeat the same it get multiplied.
also found one more thing that online status for the first person keep showing waiting for peers to connect no matter how many of the peer get connected until page get reload. on same side I found the same bug on client to once it get connect then number which is showing at time of connect will remain same no matter how many peers get connect all these problems is in the mobile browser

ooh got it - by the way this code was an attempt to convert part 5 of the video series to using hooks and I have not covered mobile 1:N connections. So, mobile version only handles 1:1 hence you are facing all these problems. One of the next videos I am to convert the mobile 1:1 to 1:N
https://www.youtube.com/watch?v=DBhPUE9UjU8&list=PL_YW-znSZ_dK365WaVuiBUN6FYc9_1hni&index=5

But again if you want to PR and fork the project to accommodate your updates that will great.

BTW, did you solve the android part that is not rendering the video?

yes rendeing problem is solve but addstream loading 2 streams one for video and one for audio. also wanna inform that addstream is outdated so tried with addtrack but it didnt work

Yes, I think I fixed that in the YouTube video code already.
Did you find this github project from YouTube? I dont remember sharing it there.

i followed both . but it didn't work

I mean I fixed the addstream

no i didn't find any fix please suggest me where did you update

this is working fine if i use only video:true if i add audio:true it started showing twice stream for each peers

BTW, for ReactNative mobile, their api still uses addstream

Please if you dont mind kindly share your code so that it can benefit others like Rahul who is having same issues. Thanks in advance.

i am not working on react native i am working on reactjs only and for that i used exactly same code as of you . it just your code doesn't have audio constraint set true . i did that and streams geting twice

if he is having rendering issue ask him to use xirsys for stun and turn server most of his issue will be solved

import React, { Component } from 'react';

import io from 'socket.io-client'
//import adapter from 'webrtc-adapter';
import Video from './components/Video';
import Videos from './components/Videos';

class App extends Component {
constructor(props) {
super(props)

this.state = {
  localStream: null,    // used to hold local stream object to avoid recreating the stream everytime a new offer comes
  remoteStream: null,    // used to hold remote stream object that is displayed in the main screen

  remoteStreams: [],    // holds all Video Streams (all remote streams)
  peerConnections: {},  // holds all Peer Connections
  selectedVideo: null,

  status: 'Please wait...',

  pc_config: {
    "iceServers":  [// private ice server]
  },

  sdpConstraints: {
    'mandatory': {
        'OfferToReceiveAudio': true,
        'OfferToReceiveVideo': true
    }
  },
}

// DONT FORGET TO CHANGE TO YOUR URL
this.serviceIP = 'https://d11fcad8.ngrok.io/webrtcPeer'

// https://reactjs.org/docs/refs-and-the-dom.html
// this.localVideoref = React.createRef()
// this.remoteVideoref = React.createRef()

this.socket = null
// this.candidates = []

}

getLocalStream = () => {
// called when getUserMedia() successfully returns - see below
// getUserMedia() returns a MediaStream object (https://developer.mozilla.org/en-US/docs/Web/API/MediaStream)
const success = (stream) => {
window.localStream = stream
console.log("localstream/......"+stream);
// this.localVideoref.current.srcObject = stream
// this.pc.addStream(stream);
this.setState({
localStream: stream
})

  this.whoisOnline()
}

// called when getUserMedia() fails - see below
const failure = (e) => {
  console.log('getUserMedia Error: ', e)
}

// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
// see the above link for more constraint options
const constraints = {
  audio: true,
  video: true,
  //audio:{ echoCancellation: true },
  // video: {
  //   width: 1280,
  //   height: 720
  // },
  // video: {
  //   width: { min: 1280 },
  // }
  options: {
    mirror: true,
  }
}
if (navigator.mediaDevices === undefined) {
  navigator.mediaDevices = {};
}
if (navigator.mediaDevices.getUserMedia === undefined) {
  navigator.mediaDevices.getUserMedia = function(constraints) {

    // First get ahold of the legacy getUserMedia, if present
    var 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(function(resolve, reject) {
      getUserMedia.call(navigator, constraints, resolve, reject);
    });
  }
}

// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
navigator.mediaDevices.getUserMedia(constraints)
  .then(success)
  .catch(failure)

}

whoisOnline = () => {
// let all peers know I am joining
this.sendToPeer('onlinePeers', null, {local: this.socket.id})
}

sendToPeer = (messageType, payload, socketID) => {
this.socket.emit(messageType, {
socketID,
payload
})
}

createPeerConnection = (socketID, callback) => {

try {
  let pc = new RTCPeerConnection(this.state.pc_config)

  // add pc to peerConnections object
  const peerConnections = { ...this.state.peerConnections, [socketID]: pc }
  this.setState({
    peerConnections
  })

  pc.onicecandidate = (e) => {
    if (e.candidate) {
      this.sendToPeer('candidate', e.candidate, {
        local: this.socket.id,
        remote: socketID
      })
    }
  }

  pc.oniceconnectionstatechange = (e) => {
    

  }

  pc.ontrack = (e) => {
    const remoteVideo = {
      id: socketID,
      name: socketID,
      stream: e.streams[0]
    }

    this.setState(prevState => {
        console.log('streamers stream ',e.streams)
      // If we already have a stream in display let it stay the same, otherwise use the latest stream
      const remoteStream = prevState.remoteStreams.length > 0 ? {} : { remoteStream: e.streams[0] }
      console.log('remote stream ',remoteStream)
      // get currently selected video
      let selectedVideo = prevState.remoteStreams.filter(stream => stream.id === prevState.selectedVideo.id)
      // if the video is still in the list, then do nothing, otherwise set to new video stream
      selectedVideo = selectedVideo.length ? {} : { selectedVideo: remoteVideo }
      //s = Array.from(new Set(remoteStreams));
      return {
        // selectedVideo: remoteVideo,
        ...selectedVideo,
        // remoteStream: e.streams[0],
        ...remoteStream,
        remoteStreams: [...prevState.remoteStreams, remoteVideo]
      }
    })
  }

  pc.close = () => {
    // alert('GONE')
  }

  if (this.state.localStream)
    //pc.addStream(this.state.localStream)
    //var tracks=this.state.localStream.getTracks();
    var newStream=new MediaStream(this.state.localStream)
    pc.addStream(newStream)
    // var newStream=new MediaStream(this.state.localStream)
    // var tracks=newStream.getTracks();
    // //console.log(tracks);
    //   pc.addTrack(tracks[0],newStream)
    //this.state.localStream.getTrack().forEach(track=>pc.addTrack(track,this.state.localStream))

  // return pc
  callback(pc)

} catch(e) {
  console.log('Something went wrong! pc not created!!', e)
  // return;
  callback(null)
}

}

componentDidMount = () => {

this.socket = io.connect(
  this.serviceIP,
  {
    path: '/io/webrtc',
    query: {}
  }
)

this.socket.on('connection-success', data => {

  const status = data.peerCount > 1 ? `Total Connected Peers: ${data.peerCount}` : 'Waiting for other peers to connect'

  this.setState({
    status: status
  })
  this.getLocalStream()

  console.log("peerCount:"+data.peerCount)

  
})

this.socket.on('peer-disconnected', data => {
  console.log('peer-disconnected', data)

  const remoteStreams = this.state.remoteStreams.filter(stream => stream.id !== data.socketID)

  this.setState(prevState => {
    console.log("state-data",prevState)
    // check if disconnected peer is the selected video and if there still connected peers, then select the first
    const selectedVideo = prevState.selectedVideo.id === data.socketID && remoteStreams.length ? { selectedVideo: remoteStreams[0] } : null

    return {
      // remoteStream: remoteStreams.length > 0 && remoteStreams[0].stream || null,
      remoteStreams,
      ...selectedVideo,
    }
    }
  )
})

// this.socket.on('offerOrAnswer', (sdp) => {

//   this.textref.value = JSON.stringify(sdp)

//   // set sdp as remote description
//   this.pc.setRemoteDescription(new RTCSessionDescription(sdp))
// })

this.socket.on('online-peer', socketID => {
  console.log('connected peers ...', socketID)

  // create and send offer to the peer (data.socketID)
  // 1. Create new pc
  this.createPeerConnection(socketID, pc => {
    // 2. Create Offer
      if (pc)
        pc.createOffer(this.state.sdpConstraints)
          .then(sdp => {
            

            this.sendToPeer('offer', sdp, {
              local: this.socket.id,
              remote: socketID
            })
            pc.setLocalDescription(sdp)
      })
    })
})

this.socket.on('offer', data => {
  this.createPeerConnection(data.socketID, pc => {
  //  var tracks=this.state.localStream.getTracks();
    var newStream=new MediaStream(this.state.localStream)
    pc.addStream(newStream)
    //pc.addStream(this.state.localStream)
    // var newStream=new MediaStream(this.state.localStream)
    // var tracks=newStream.getTracks();
    // //console.log(tracks);
    //   pc.addTrack(tracks[0],newStream)
    //var tracks=this.state.localStream.getTracks();
    //console.log("audio  "+tracks[0]);
    //pc.addTrack(tracks[0],this.state.localStream)
    //this.state.localStream.getTrack().forEach(track=>pc.addTrack(track,this.state.localStream))
    pc.setRemoteDescription(new RTCSessionDescription(data.sdp)).then(() => {
      // 2. Create Answer
      pc.createAnswer(this.state.sdpConstraints)
        .then(sdp => {
         // console.log("socketid testing"+this.socket.id)
          this.sendToPeer('answer', sdp, {
            local: this.socket.id,
            remote: data.socketID
          })
          pc.setLocalDescription(sdp)
        })
    })
  })
})

this.socket.on('answer', data => {
  // get remote's peerConnection
  const pc = this.state.peerConnections[data.socketID]
  //console.log(data.sdp)
  pc.setRemoteDescription(new RTCSessionDescription(data.sdp)).then(()=>{})
})

this.socket.on('candidate', (data) => {
  // get remote's peerConnection
  const pc = this.state.peerConnections[data.socketID]

  if (pc)
    pc.addIceCandidate(new RTCIceCandidate(data.candidate))
})

// const pc_config = null

// const pc_config = {
//   "iceServers": [
//     // {
//     //   urls: 'stun:[STUN_IP]:[PORT]',
//     //   'credentials': '[YOR CREDENTIALS]',
//     //   'username': '[USERNAME]'
//     // },
//     {
//       urls : 'stun:stun.l.google.com:19302'
//     }
//   ]
// }

// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection
// create an instance of RTCPeerConnection
// this.pc = new RTCPeerConnection(this.state.pc_config)

// triggered when a new candidate is returned
// this.pc.onicecandidate = (e) => {
//   // send the candidates to the remote peer
//   // see addCandidate below to be triggered on the remote peer
//   if (e.candidate) {
//     // console.log(JSON.stringify(e.candidate))
//     this.sendToPeer('candidate', e.candidate)
//   }
// }

// triggered when there is a change in connection state
// this.pc.oniceconnectionstatechange = (e) => {
//   console.log(e)
// }

// triggered when a stream is added to pc, see below - this.pc.addStream(stream)
// this.pc.onaddstream = (e) => {
//   this.remoteVideoref.current.srcObject = e.stream
// }

// this.pc.ontrack = (e) => {
//   debugger
//   // this.remoteVideoref.current.srcObject = e.streams[0]

//   this.setState({
//     remoteStream: e.streams[0]
//   })
// }

}

switchVideo = (_video) => {
//console.log(_video)
this.setState({
selectedVideo: _video
})
}

render() {

//console.log(this.state.localStream)

const statusText = <div style={{ color: 'yellow', padding: 5 }}>{this.state.status}</div>

return (
  <div>
    <Video
      videoStyles={{
        zIndex:2,
        position: 'absolute',
        right:0,
        width: 200,
        height: 200,
        margin: 5,
        backgroundColor: 'black'
      }}
      // ref={this.localVideoref}
      videoStream={this.state.localStream}
      autoPlay>
    </Video>
    <Video
      videoStyles={{
        zIndex: 1,
        position: 'fixed',
        bottom: 0,
        minWidth: '100%',
        minHeight: '100%',
        backgroundColor: 'black'
      }}
      // ref={ this.remoteVideoref }
      videoStream={this.state.selectedVideo && this.state.selectedVideo.stream}
      autoPlay>
    </Video>
    <br />
    <div style={{
      zIndex: 3,
      position: 'absolute',
      margin: 10,
      backgroundColor: '#cdc4ff4f',
      padding: 10,
      borderRadius: 5,
    }}>
      { statusText }
    </div>
    <div>
      <Videos
        switchVideo={this.switchVideo}
        remoteStreams={this.state.remoteStreams}
      ></Videos>
    </div>
    <br />

    {/* <div style={{zIndex: 1, position: 'fixed'}} >
      <button onClick={this.createOffer}>Offer</button>
      <button onClick={this.createAnswer}>Answer</button>

      <br />
      <textarea style={{ width: 450, height:40 }} ref={ref => { this.textref = ref }} />
    </div> */}
    {/* <br />
    <button onClick={this.setRemoteDescription}>Set Remote Desc</button>
    <button onClick={this.addCandidate}>Add Candidate</button> */}
  </div>
)

}
}

export default App;

take a look on yourself .. problem that i found is that if i set both audio and video constraint true then two mediastream will be generated one for audio and one for video. and when this pc.addstream calling while generating pc it calls both mediastream(audio and video) and that is the reason for twice rendering of remotevideo.......

Thanks Ahmad

take a look on yourself .. problem that i found is that if i set both audio and video constraint true then two mediastream will be generated one for audio and one for video. and when this pc.addstream calling while generating pc it calls both mediastream(audio and video) and that is the reason for twice rendering of remotevideo.......

Thanks, I will look into it - highly appreciated.

I will also compare and update with you code.

thanks . i know the problem the problem and i am working on the solution too

i have tried alot but couldn't find any solution for that twice video rendering

please let me know as soon as you find the solution

Sure will do.

Have you found any error on that? or you suggest any changes?

hello i have resolved my issues just by adding filter in videos component and checking for the repeated media streams.
i found another issue of echo can you suggest me any better way for doing so. i have used echoCancellation :true constraint but it didn't work

I am facing an issue with your code, can anyone please help me. I am working with react native, nodes, webrtc. But I am unable to see the video of another person, it shows a black screen only.

Hi nawaabazmi, I have also reworked the code to avoid repeated video thumbnails. The reason for that was due to audio and video tracks added separately. I will include this change in my next video any time soon may be in a couple of days.

  let _rVideos = nextProps.remoteStreams.map((rVideo, index) => {

    const _videoTrack = rVideo.stream.getTracks().filter(track => track.kind === 'video')
    console.log('tracks...', index, _videoTrack, rVideo.stream.getTracks())

    // let video = rVideo.stream.getTracks()[0].kind === 'video' && (
    let video = _videoTrack && (
      <Video
        videoStream={rVideo.stream}
        frameStyle={{ width: 120, float: 'left', padding: '0 3px' }}
        videoStyles={{
          cursor: 'pointer',
          objectFit: 'cover',
          borderRadius: 3,
          width: '100%',
        }}
        // muted // <--- remove
        autoplay
      />
    ) || <div></div>

    return (
      <div
        id={rVideo.name}
        onClick={() => this.props.switchVideo(rVideo)}
        style={{ display: 'inline-block' }}
        key={index}
      >
        {video}
      </div>
    )
  })

I am facing an issue with your code, can anyone please help me. I am working with react native, nodes, webrtc. But I am unable to see the video of another person, it shows a black screen only.

Hi alenthomas343, are you having this problem...

  • within two tabs/browsers on the same computer?
  • within two browsers on two computers on the same network (LAN)?
  • within two browsers on two computers on different networks?

did you follow my tutorials from scratch on YouTube? Because this code was an attempt to rewrite the code in YouTube to use React Hooks.
https://www.youtube.com/watch?v=h2WkZ0h0-Rc&list=PL_YW-znSZ_dK365WaVuiBUN6FYc9_1hni&index=1

I want to run the app on my android phone

On Mon, 18 May, 2020, 10:57 PM AMIR ESHAQ, @.***> wrote: I am facing an issue with your code, can anyone please help me. I am working with react native, nodes, webrtc. But I am unable to see the video of another person, it shows a black screen only. Hi alenthomas343, are you having this problem... - within two tabs/browsers on the same computer? - within two browsers on two computers on the same network (LAN)? - within two browsers on two computers on different networks? did you follow my tutorials from scratch on YouTube? Because this code was an attempt to rewrite the code in YouTube to use React Hooks. https://www.youtube.com/watch?v=h2WkZ0h0-Rc&list=PL_YW-znSZ_dK365WaVuiBUN6FYc9_1hni&index=1 — You are receiving this because you commented. Reply to this email directly, view it on GitHub <#2 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/APTLB7FLDUXWHPF34BKUXFDRSFVZJANCNFSM4MS4KSQQ .

I have not tried the above fixes in android yet.

Hello alenthomas343. I had this issue before check for 3 things
1- use turn for the ice candidate generation
2- check whether pc actually generated or not
3- const selectedVideo = prevState.selectedVideo.id === data.socketID && remoteStreams.length ? { selectedVideo: remoteStreams[0] } : null
In this line sometimes prevState.selectedVideo is set null so here code is giving error

Can you please show me your code which is working on android phones.

On Mon, 18 May, 2020, 11:20 PM Ahmad azmi, @.***> wrote: Hello alenthomas343. I had this issue before check for 3 things 1- use turn for the ice candidate generation 2- check whether pc actually generated or not 3- const selectedVideo = prevState.selectedVideo.id === data.socketID && remoteStreams.length ? { selectedVideo: remoteStreams[0] } : null In this line sometimes prevState.selectedVideo is set null so here code is giving error — You are receiving this because you commented. Reply to this email directly, view it on GitHub <#2 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/APTLB7DYSBCSWDCQBDLSBXDRSFYNZANCNFSM4MS4KSQQ .

Please look at this link from Rahul who managed to run the App on Android and then come back to this thread to resolve the issue that Rahul face.
https://github.com/rahulbtops/webrtc

I used this code, but mine is not working on my android phones. Is your app working on android devices perfectly, sir?

On Tue, 19 May, 2020, 8:21 AM AMIR ESHAQ, @.> wrote: Can you please show me your code which is working on android phones. … <#m_7935601247840947230_> On Mon, 18 May, 2020, 11:20 PM Ahmad azmi, @.> wrote: Hello alenthomas343. I had this issue before check for 3 things 1- use turn for the ice candidate generation 2- check whether pc actually generated or not 3- const selectedVideo = prevState.selectedVideo.id === data.socketID && remoteStreams.length ? { selectedVideo: remoteStreams[0] } : null In this line sometimes prevState.selectedVideo is set null so here code is giving error — You are receiving this because you commented. Reply to this email directly, view it on GitHub <#2 (comment) <#2 (comment)>>, or unsubscribe https://github.com/notifications/unsubscribe-auth/APTLB7DYSBCSWDCQBDLSBXDRSFYNZANCNFSM4MS4KSQQ . Please look at this link from Rahul who managed to run the App on Android and then come back to this thread to resolve the issue that Rahul face. https://github.com/rahulbtops/webrtc — You are receiving this because you commented. Reply to this email directly, view it on GitHub <#2 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/APTLB7HRLBFUL7Z7KOSB2CLRSHX3VANCNFSM4MS4KSQQ .

I haven't tried it myself - but if you give me time to finish other stuff, I'll give it a shot soon.

BTW, when you say Android do you mean ReactNative App or chrome browser in Android?

Hello Amir Eshaq, I used your code for coding my webrtc app, and Its totally working fine. And I am thankful to you. But I now need your help, can you please help me add peer real names along with their remote videos. If a new peer joins in, his name will also be shown in our screen along with his remote video. Can you please help me to implement that because I know you are an expert in this. I request your concern.

Hello alen, you can do this easily by passing query also on server side store the query in array along with peer I'd then you can use these data in any manner you want

Thank you for your help Ahmad Azmi, it worked well.

That's great Ahmad Azmi, with a few words made it easy for Alen Thomas to implement a needed feature.

Thank you amir you taught me webrtc

Yeah we all are thankful to Amir. And we request Amir to do more and more videos on YouTube too.

I am humbled. You guys are also teaching me, so thank you too!

I have an issue, even though no users are connected, there shows peer numbers on screen, ie., If a total of 2 peers are connected, the no. of connected peers on screen is sometimes 3 or more than that. I don't find a solution for this, guys please help me.

I remember there was such issue and it may be due to no disconnected functionality, especially when mobile connects and disconnects - the peers number increases. If you haven't progressed in the videos to part 7, then I suggest you watch those videos because somewhere in the videos I handle the disconnections.

Ok Thanks, I will check it🤩

Sir, Is it possible to implement Janus webrtc with your code.

Janus is a bit involving but I am working on mediasoup SFU

Sir, Can you please help me to implement mediasoup SFU with my webrtc application. From the beginning onwards, your teaching videos helped me a lot and I understand your teaching very well. I request your concern for this. Please..🙏

I am spending a few hours daily to start from scratch. is your environment on Windows or Linux?

Mine is windows

I don't think mediasoup can run on windows but with WSL you can do it without installing VM - I will demo that as this time I will be on windows

Sir, I want to learn it, please then is it okay if I am on windows.

yes, if you can't setup WSL, then you will need to have virtual environment like VirtualBox. But with Windows 10, latest build should be ok. I suggest you look at configuring WSL on your Windows 10 - you will be able to install Ubuntu.

check this link for instructions ...
https://www.youtube.com/watch?v=uWNpXOT-Zbo&list=PLhfrWIlLOoKNMHhB39bh3XBpoLxV3f0V9&index=4

Ok I will surely install it. Thank you sir.☺️☺️😍

Sir, I have setup the WSL installation and now my windows 10 has WSL. It's working fine. What I want to do after that.

Sir, can you give me your email id.

Alenthomas, it's best we wait for the video as I am still researching for the video especially SFU. To install Ubuntu simply go to Windows store and search for it and install.

you can connect via linkedIn

Ok, when you start working with it, will you help me also implementing that. My system is setup with Ubuntu. Till then, can you share me some resources which you are referring for its implementation. Please, don't feel that I am disturbing you, only because I want to make that. Kindly please do concern.

Can we connect via WhatsApp or telegram please this is my number +919207602683.
Please🙏

It's not disturbing but it's difficult to focus on two separate ends. Are you not able to connect via LinkedIn? many YouTube viewers are connecting with me via LinkedIn. Simply search for my name Amir Eshaq

Yeah I connect with you now actually on LinkedIn and Twitter but I am not able to message you.

I saw you just liked my video on LinkedIn

Please can I connect you via WhatsApp

I just sent you a connect request

I really dont look at WhatsApp msg, mostly once a day. Too many messages there

Yeah it's true, Thank you. I am connected with you🤗🤗