DEV Community

HarmonyOS
HarmonyOS

Posted on

How to download and play voice data in string format?

Read the original article:How to download and play voice data in string format?

Requirement Description

How to properly play the generated voice string obtained from the server?

Background Knowledge

  • Applications can initiate an HTTP data request using HttpRequest, supporting common methods such as GET, POST, OPTIONS, HEAD, PUT, DELETE, TRACE, and CONNECT.
  • AVPlayer can transcode audio/video media resources (e.g., mp4, mp3, mkv, mpeg-ts, etc.) into renderable images and audible audio analog signals, and play them through output devices. AVPlayer provides a comprehensive and integrated playback capability; applications only need to provide the source of streaming media and do not need to handle data parsing or decoding to achieve playback.

Implementation Steps

1.Use the HttpRequest request method, passing the request URL and optional parameters, to initiate an HTTP data request. In the callback, parse the server response content based on actual business needs.

private async postHttp() {
  let httpRequest = http.createHttp();
  httpRequest.on('headersReceive', (header) => {
    hilog.info(0x0000, 'testTag', '---httpRequestSucess%{public}s', header)
  });
  let avPlayer: media.AVPlayer = await media.createAVPlayer();
  httpRequest.request(
    this.url,
    {
      method: http.RequestMethod.POST,
      header: {
        'contentType': 'application/json'
      },
      extraData: 'data to send',
      expectDataType: http.HttpDataType.STRING,
      usingCache: true,
      priority: 1,
      connectTimeout: 60000,
      readTimeout: 60000,
      usingProtocol: http.HttpProtocol.HTTP1_1,
      usingProxy: false
    }, async (err: BusinessError, data: http.HttpResponse) => {
    if (!err) {
      let strr = data.result as string;
      let lines = strr.split('\n');
      this.musics = '';
      for (let i = 0; i < lines.length; i++) {
        if (lines[i] && lines[i] !== '' && lines[i].startsWith('data:')) {
          let linei = lines[i].slice(5);
          let dataEntry: DataEntry = JSON.parse(linei) as DataEntry;
          let resultData: ResultData = dataEntry.data;
          let music = resultData.audio;
          if (music && music !== '') {
            this.musics+= music;
          }
        }
      }
      let fileName = this.save(this.musics)
      this.avPlayerDataSrcNoSeekDemo(avPlayer, fileName);
      httpRequest.destroy();
    } else {
      httpRequest.off('headersReceive');
      httpRequest.destroy();
    }
  }
  );
}
Enter fullscreen mode Exit fullscreen mode

2.Convert the string-formatted data received from the server into MP3 format and store it in the local app sandbox directory.

save(music: string) {
  const arraybuffer = this.hexStringToArrayBuffer(music);
  let fileName = `temp_audio_${Date.now()}.mp3`
  const filePath = `${this.context?.filesDir}/${fileName}`;
  const file = fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
  try {
    fs.writeSync(file.fd, arraybuffer);
  }finally {
    fs.closeSync(file);
  }
  return fileName;
}

hexStringToArrayBuffer(hex: string): ArrayBuffer {
  const len = hex.length / 2;
  const buffer = new ArrayBuffer(len);
  const uint8Array = new Uint8Array(buffer);
  for (let i = 0; i < len; i++) {
    uint8Array[i] = parseInt(hex.substr(i * 2, 2), 16)
  }
  return buffer;
}

Enter fullscreen mode Exit fullscreen mode

3.Use AVPlayer to play MP3 audio files stored in the local app sandbox directory.

async avPlayerDataSrcNoSeekDemo(avPlayer: media.AVPlayer, fileName: string) {
  this.setAVPlayerCallback(avPlayer);
  let src: media.AVDataSrcDescriptor = {
    fileSize: -1,
    callback: (buf: ArrayBuffer, length: number) => {
      let num = 0;
      if (buf === undefined || length === undefined) {
        return -1;
      }
      num = fs.readSync(this.fd, buf);
      if (num > 0) {
        return num;
      }
      return -1;
    }
  };
  if (this.context !== undefined) {
    let pathDir = this.context.filesDir;
    let path = pathDir + `/${fileName}`;
    let file = fs.openSync(path)
    this.fd = file.fd;
    avPlayer.dataSrc = src;
  }
}
Enter fullscreen mode Exit fullscreen mode

4.The complete demo is as follows.

import { http } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { audio } from '@kit.AudioKit';
import { media } from '@kit.MediaKit';

@Entry
@Component
export struct Player {
// Online link, returns the hexadecimal data stream of the audio.
  private url: string = 'www.example.com';
  private context:Context|undefined = this.getUIContext().getHostContext()
  @State musics: string = '';
  fd: number = 0;

  build() {
    Column() {
      Button(`Download and Play`).onClick(() => {
        this.postHttp();
      })
    }.width('100%')

  }

  private async postHttp() {
    let httpRequest = http.createHttp();
    httpRequest.on('headersReceive', (header) => {
      hilog.info(0x0000, 'testTag', '---httpRequestSucess%{public}s', header)
    });

    let avPlayer: media.AVPlayer = await media.createAVPlayer();

    httpRequest.request(
      this.url,
      {
        method: http.RequestMethod.POST,
        header: {
          'contentType': 'application/json'
        },
        extraData: 'data to send',
        expectDataType: http.HttpDataType.STRING,
        usingCache: true,
        priority: 1,
        connectTimeout: 60000,
        readTimeout: 60000,
        usingProtocol: http.HttpProtocol.HTTP1_1,
        usingProxy: false
      }, async (err: BusinessError, data: http.HttpResponse) => {
      if (!err) {
        let strr = data.result as string;
        let lines = strr.split('\n');
        this.musics = '';
        for (let i = 0; i < lines.length; i++) {
          if (lines[i] && lines[i] !== '' && lines[i].startsWith('data:')) {
            let linei = lines[i].slice(5);
            let dataEntry: DataEntry = JSON.parse(linei) as DataEntry;
            let resultData: ResultData = dataEntry.data;
            let music = resultData.audio;
            if (music && music !== '') {
              this.musics+= music;
            }
          }
        }
        let fileName = this.save(this.musics)
        this.avPlayerDataSrcNoSeekDemo(avPlayer, fileName);
        httpRequest.destroy();
      } else {
        httpRequest.off('headersReceive');
        httpRequest.destroy();
      }
    }
    );
  }

  save(music: string) {
    const arraybuffer = this.hexStringToArrayBuffer(music);
    let fileName = `temp_audio_${Date.now()}.mp3`
    const filePath = `${this.context?.filesDir}/${fileName}`;
    const file = fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
    try {
      fs.writeSync(file.fd, arraybuffer);
    }finally {
      fs.closeSync(file);
    }
    return fileName;
  }

  hexStringToArrayBuffer(hex: string): ArrayBuffer {
    const len = hex.length / 2;
    const buffer = new ArrayBuffer(len);
    const uint8Array = new Uint8Array(buffer);
    for (let i = 0; i < len; i++) {
      uint8Array[i] = parseInt(hex.substr(i * 2, 2), 16)
    }
    return buffer;
  }

  async avPlayerDataSrcNoSeekDemo(avPlayer: media.AVPlayer, fileName: string) {
    this.setAVPlayerCallback(avPlayer);
    let src: media.AVDataSrcDescriptor = {
      fileSize: -1,
      callback: (buf: ArrayBuffer, length: number) => {
        let num = 0;
        if (buf === undefined || length === undefined) {
          return -1;
        }
        num = fs.readSync(this.fd, buf);
        if (num > 0) {
          return num;
        }
        return -1;
      }
    };
    if (this.context !== undefined) {
      let pathDir = this.context.filesDir;
      let path = pathDir + `/${fileName}`;
      let file = fs.openSync(path)
      this.fd = file.fd;
      avPlayer.dataSrc = src;
    }
  }

  setAVPlayerCallback(avPlayer: media.AVPlayer) {
    avPlayer.on('seekDone', (seekDoneTime: number) => {
      console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
    });
    avPlayer.on('error', (err: BusinessError) => {
      console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
      avPlayer.reset();
    });
    avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
      switch (state) {
        case 'idle':
          console.info('AVPlayer state idle called.');
          break;
        case 'initialized':
          console.info('AVPlayer state initialized called.');
          avPlayer.audioRendererInfo = {
            usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
            rendererFlags: 0
          };
          avPlayer.prepare();
          break;
        case 'prepared':
          console.info('AVPlayer state prepared called.');
          avPlayer.play();
          break;
        case 'completed':
          console.info('AVPlayer state completed called.');
          avPlayer.stop();
          break;
        case 'stopped':
          console.info('AVPlayer state stopped called.');
          avPlayer.reset();
          break;
        case 'released':
          console.info('AVPlayer state released called.');
          break;
        default:
          console.info('AVPlayer state unknown called.');
          break;
      }
    });
  }
}

class DataEntry {
  data: ResultData = new ResultData;
}

class ResultData {
  audio: string = '';
}
Enter fullscreen mode Exit fullscreen mode

Code Running Effect Image

The file exists in the sandbox directory and plays normally.

cke_2689.png

Code Check

cke_3767.png

Restrictions and Limitations

  • This example supports API Version 20 Release and above.
  • This example supports HarmonyOS SDK Version 6.0.0 Release and above.
  • This example requires DevEco Studio Version 6.0.0 Release and above to compile and run.

Written by Merve Yonetci

Top comments (0)