async audioCheck()

in integration/js/pages/AppPage.js [669:829]


  async audioCheck(stepInfo, expectedState, checkStereoTones = false) {
    let res = undefined;
    try {
      res = await this.driver.executeAsyncScript(async (expectedState, checkStereoTones) => {
        let logs = [];
        let callback = arguments[arguments.length - 1];

        const sleep = (milliseconds) => {
          return new Promise(resolve => setTimeout(resolve, milliseconds))
        };

        const channelCount = checkStereoTones ? 2 : 1;

        const successfulToneChecks = Array(channelCount).fill(0);
        const totalToneChecks = Array(channelCount).fill(0);
        const audioContext = new (window.AudioContext || window.webkitAudioContext)();
        const minToneError = Array(channelCount).fill(Infinity);
        const maxToneError = Array(channelCount).fill(-Infinity);
        const percentages = Array(channelCount).fill(0);
        try {
          const stream = document.getElementById('meeting-audio').srcObject;
          const source = audioContext.createMediaStreamSource(stream);
          let analyser = [];
          for(let i=0; i<channelCount; i++) {
            analyser.push(audioContext.createAnalyser());
          }
          let byteFrequencyData = [];
          for(let i=0; i<channelCount; i++) {
            byteFrequencyData.push(new Uint8Array(analyser[i].frequencyBinCount));
          }
          let floatFrequencyData = [];
          for(let i=0; i<channelCount; i++) {
            floatFrequencyData.push(new Float32Array(analyser[i].frequencyBinCount));
          }

          if (checkStereoTones) {
            const splitterNode = audioContext.createChannelSplitter(2);
            source.connect(splitterNode);
            splitterNode.connect(analyser[0], 0);
            splitterNode.connect(analyser[1], 1);
          } else {
            source.connect(analyser[0]);
          }

          await sleep(5000);

          const getAverageVolume = (channelIndex) => {
            analyser[channelIndex].getByteFrequencyData(byteFrequencyData[channelIndex]);
            let values = 0;
            let average;
            const length = byteFrequencyData[channelIndex].length;
            // get all the frequency amplitudes
            for (let i = 0; i < length; i++) {
              values += byteFrequencyData[channelIndex][i];
            }
            average = values / length;
            return average;
          };

          const checkVolumeFor = async (runCount, channelIndex) => {
            for (let i = 0; i < runCount; i++) {
              totalToneChecks[channelIndex]++;
              const avgTestVolume = getAverageVolume(channelIndex);
              logs.push(`Resulting volume ${avgTestVolume}`);
              if (
                (expectedState === "AUDIO_ON" && avgTestVolume > 0) ||
                (expectedState === "AUDIO_OFF" && avgTestVolume === 0)
              ) {
                successfulToneChecks[channelIndex]++;
              }
              await sleep(100)
            }
          };

          const checkFrequency = (targetReceiveFrequency, channelIndex) => {
            analyser[channelIndex].getFloatFrequencyData(floatFrequencyData[channelIndex]);
            // logs.push(`frequency data : ${floatFrequencyData}`);
            let maxBinDb = -Infinity;
            let hotBinFrequency = 0;
            const binSize = audioContext.sampleRate / analyser[channelIndex].fftSize; // default fftSize is 2048
            for (let i = 0; i < floatFrequencyData[channelIndex].length; i++) {
              const v = floatFrequencyData[channelIndex][i];
              if (v > maxBinDb) {
                maxBinDb = v;
                hotBinFrequency = i * binSize;
              }
            }
            const error = Math.abs(hotBinFrequency - targetReceiveFrequency);
            if (maxBinDb > -Infinity) {
              if (error < minToneError[channelIndex]) {
                minToneError[channelIndex] = error;
              }
              if (error > maxToneError[channelIndex]) {
                maxToneError[channelIndex] = error;
              }
            }
            if (error <= 2 * binSize) {
              successfulToneChecks[channelIndex]++;
            }
            totalToneChecks[channelIndex]++;
            return hotBinFrequency
          };

          const checkFrequencyFor = async (runCount, freq, channelIndex) => {
            for (let i = 0; i < runCount; i++) {
              const testFrequency = checkFrequency(freq, channelIndex);
              logs.push(`Resulting Frequency ${testFrequency} for channel ${channelIndex}`);
              await sleep(100)
            }
          };

          if (expectedState === "AUDIO_OFF") {
            await checkVolumeFor(50, 0);
            if (checkStereoTones) {
              await checkVolumeFor(50, 1);
            }
          }

          if (expectedState === "AUDIO_ON") {
            if (checkStereoTones) {
              await checkFrequencyFor(50, 500, 0);
              await checkFrequencyFor(50, 1000, 1);
            } else {
              await checkFrequencyFor(50, 440, 0);
            }
          }

          for (let i=0; i<channelCount; i++) {
            percentages[i] = successfulToneChecks[i] / totalToneChecks[i];
          }
        } catch (e) {
          logs.push(`${e}`)
        } finally {
          logs.push(`test completed`);
          await audioContext.close();
          callback({
            percentages,
            logs
          });
        }
      }, expectedState, checkStereoTones);
    } catch (e) {
      this.logger(`Audio Check Failed ${e}`)
    } finally {
      if (res) {
        res.logs.forEach(l => {
          this.logger(l)
        })
      }
    }
    if (!res) {
      return false;
    }
    for (let i=0; i<res.percentages.length; i++) {
      this.logger(`Audio check success rate channel ${i}: ${res.percentages[i] * 100}%`);
      if (res.percentages[i] < 0.75) {
        return false;
      }
    }
    return true;
  }