Electronで簡易なデスクトップボイスチャットアプリを作ってみた

テレワークをしていると、軽めの会話に課題が出てきます。わざわざ相手の予定を確認したり、会議URLを発行したりと煩わしさがあります。
この記事ではAgora.io SDK For Electronを用いて簡易なデスクトップボイスチャットアプリの作成を紹介します。
ElectronSDKはWindows/MacのNativeSDKをラップしている為、[高品質オーディオ体験](https://qiita.com/v-cube/items/e0a0787d482022ca76a0)が有効になります。

[サンプル](https://github.com/fu-jimoto/AgoraIO-Electron-VoiceChat)はに公開しています。

##実装内容
実装内容を詳しく見ていきます。

###STEP1
まずは入室の処理です。
ボイスチャットアプリの為、カメラ映像は取得しないようにします。

```javascript:App.js
  handleJoin = () => {
    if(!this.state.channel){
      alert("Please enter channel")
      return
    }
    let rtcEngine = this.getRtcEngine()
    rtcEngine.setChannelProfile(1)
    rtcEngine.setClientRole(1)
    rtcEngine.setAudioProfile(0, 1)
    rtcEngine.disableVideo();//今回は音声のみなのでビデオを利用しない

    if(this.state.audioDevices.length > 0) {
      rtcEngine.setAudioRecordingDevice(this.state.audioDevices[this.state.mic].deviceid);
    }
    if(this.state.audioPlaybackDevices.length > 0) {
      rtcEngine.setAudioPlaybackDevice(this.state.audioDevices[this.state.speaker].deviceid);
    }

    rtcEngine.enableAudioVolumeIndication(1000, 3, false)
    rtcEngine.joinChannel(this.state.token || null, this.state.channel, '',  Number(`${new Date().getTime()}`.slice(7)))
  }
```

###STEP2
次に他拠点の入室の処理です。

```javascript:App.js
  subscribeEvents = (rtcEngine) => {
    rtcEngine.on('joinedchannel', (channel, uid, elapsed) => {
      console.log(`onJoinChannel channel: ${channel}  uid: ${uid}  version: ${JSON.stringify(rtcEngine.getVersion())})`)
      this.setState({
        local: uid
      });
    });
    rtcEngine.on('userjoined', (uid, elapsed) => {
      console.log(`userJoined ---- ${uid}`)
      rtcEngine.muteRemoteVideoStream(uid, false)
      this.setState({
        users: this.state.users.concat([uid])
      })
    })
    rtcEngine.on('removestream', (uid, reason) => {
      console.log(`useroffline ${uid}`)
      this.setState({
        users: this.state.users.filter(u => u != uid)
      })
    })
    rtcEngine.on('leavechannel', (rtcStats) => {
      console.log(`onleaveChannel----`)
      this.sharingPrepared = false
      this.setState({
        local: '',
        users: []
      })
    })
    rtcEngine.on('audiodevicestatechanged', () => {
      this.setState({
        audioDevices: rtcEngine.getAudioRecordingDevices(),
        audioPlaybackDevices: rtcEngine.getAudioPlaybackDevices()
      })
    })

    rtcEngine.on('audiovolumeindication', (
      uid,
      volume,
      speakerNumber,
      totalVolume
    ) => {
      console.log(`uid${uid} volume${volume} speakerNumber${speakerNumber} totalVolume${totalVolume}`)
    })
    rtcEngine.on('error', err => {
      console.error(err)
    })
    rtcEngine.on('executefailed', funcName => {
      console.error(funcName, 'failed to execute')
    })
  }

  //中略

  componentDidMount() {
    let dom = document.querySelector(`#video-${this.props.uid}`)
    if (this.props.role === 'local') {
      dom && this.props.rtcEngine.setupLocalVideo(dom)
      this.props.rtcEngine.setupViewContentMode("local", 1)
    } else if (this.props.role === 'localVideoSource') {
      dom && this.props.rtcEngine.setupLocalVideoSource(dom)
      this.props.rtcEngine.setupViewContentMode('videosource', 1);
    } else if (this.props.role === 'remote') {
      dom && this.props.rtcEngine.subscribe(this.props.uid, dom)
      this.props.rtcEngine.setupViewContentMode(this.props.uid, 1);
    } else if (this.props.role === 'remoteVideoSource') {
      dom && this.props.rtcEngine.subscribe(this.props.uid, dom)
      this.props.rtcEngine.setupViewContentMode(String(SHARE_ID), 1);
    }
  }

```

今回はシンプルに入室している人数分だけ青い枠がでるようにしています。


##実行画面

###入室前
![スクリーンショット 0003-11-22 11.52.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300377/f8835215-38a9-45e5-f659-03959b354a74.png)

###入室後
![スクリーンショット 0003-11-22 11.52.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300377/157edadf-f3c7-b5e2-6856-203d2d06033d.png)

###他拠点入室後
![スクリーンショット 0003-11-22 11.52.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300377/585719f8-60f2-02f2-e4f1-a8bbaac52056.png)

##補足
実際のアプリでは名前やアイコンの表示にするのが良いと思います。
作業をしている間はmuteをかけておいて、会話したい際にmuteを解除して他拠点が反応してくれる運用を想定するアプリになります。
又、ビデオ付きのサンプル[AgoraCommunityのGithub](https://github.com/AgoraIO-Community/Agora-Electron-Quickstart/tree/master/Other/Agora-Electron-Premium)に公開されています。



##最後に                                                                                                    
[**agora.ioに関するお問い合わせはこちらから**](https://jp.vcube.com/lp/agora?utm_campaign=Agora&utm_source=Qiita&arc_id=9b437e6fcc3740b85a14)   
[<img width="912" alt="スクリーンショット 0001-08-15 13.41.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300377/5d815376-9b89-08fa-9434-6fa7e8e67bcf.png">](https://jp.vcube.com/lp/agora?utm_campaign=Agora&utm_source=Qiita&arc_id=9b437e6fcc3740b85a14)