DEV Community

HarmonyOS
HarmonyOS

Posted on

Record and play sound using AVRecorder and AVPlayer

Read the original article:Record and play sound using AVRecorder and AVPlayer

Context

AVRecorder can be used to record audio files. AVPlayer can be used to play audio files.

Description

AVRecorder and AVPlayer can be used together to record and play audio files.

Solution / Approach

Important Notice: AVRecorder needs microphone permission.

Recorder
! Important: Recorder functions should be called with respect to the current recorder state.

import { media } from '@kit.MediaKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Observed
export default class Recorder {
  private recorder: media.AVRecorder | undefined = undefined;
  private fileName: string = ''
  recorderState: media.AVRecorderState = 'idle'

  // to inform the UI
  get readyToRecord(): boolean {
    return this.recorderState === 'idle' || this.recorderState === 'released'
  }

  // to inform the UI
  get recording(): boolean {
    return this.recorderState === 'started'
  }

  async start(context: Context) {
    // initialize recorder
    try {
      this.recorder = await media.createAVRecorder()

      // Callback function for state changes.
      this.recorder.on('stateChange', (state: media.AVRecorderState, reason: media.StateChangeReason) => {
        this.recorderState = state // track state changes
      })

      // Callback function for errors.
      this.recorder.on('error', (err: BusinessError) => {
        console.error(`avRecorder failed, code is ${err.code}, message is ${err.message}`);
      })

      let avProfile: media.AVRecorderProfile = {
        audioBitrate: 100000, // Audio bit rate.
        audioChannels: 2, // Number of audio channels.
        audioCodec: media.CodecMimeType.AUDIO_AAC, // Audio encoding format.
        audioSampleRate: 48000, // Audio sampling rate.
        fileFormat: media.ContainerFormatType.CFT_MPEG_4A, // Container format.
      };

      // save file to the app files
      let filePath: string = context.filesDir + `/${Date.now()}.mp3`;
      this.fileName = filePath.split('/').pop() as string
      let audioFile: fs.File = fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
      let fileFd = audioFile.fd; // Obtain the file FD.

      let avConfig: media.AVRecorderConfig = {
        audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, // Audio input source.
        profile: avProfile,
        url: 'fd://' + fileFd.toString(), // Obtain the file descriptor of the created audio file
      };

      await this.recorder.prepare(avConfig)
      await this.recorder.start();
    } catch (e) {
      console.log('start err:', e.code, e.message)
    }
  }

  async stopAndSave() {
    try {
      await this.recorder?.stop()
      await this.recorder?.reset()
      await this.recorder?.release();
      this.recorder = undefined;

      return this.fileName
    } catch (e) {
      console.log('stop err:', e.code, e.message)
      throw new Error(e.message)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Player
! Important: Player functions should be called with respect to the current player state.

import { media } from "@kit.MediaKit";
import { audio } from "@kit.AudioKit";
import { fileIo as fs } from '@kit.CoreFileKit';

@Observed
export default class Player {
  private player: media.AVPlayer | undefined = undefined
  playerState: string = "idle"

  // to inform the UI
  get playing() {
    return this.playerState === 'playing'
  }

  private async init(filePath: string) {
    try {
      this.player = await media.createAVPlayer()

      this.player.on('error', (e) => {
        console.log('player on error:', e.code, e.message)
      })

      this.player.on('stateChange', async (state: string) => {
        this.playerState = state;

        switch (state) {
          case 'idle':
            console.log('state: idle');
            break;
          case 'initialized':
            console.log('state: initialized');
            this.player!.audioRendererInfo = {
              usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
              rendererFlags: 0
            };
            this.player!.prepare();
            break;
          case 'prepared':
            console.log('state: prepared');
            this.player!.play();
            break;
          case 'playing':
            console.log('state: playing');
            break;
          case 'paused':
            console.log('state: paused');
            break;
          case 'completed':
            console.log('state: completed');
            break;
          case 'stopped':
            console.log('state: stopped');
            break;
          case 'released':
            console.log('state: released');
            this.player = undefined
            break;
          default:
            console.log('state: should not happen', state);
            break;
        }
      })

      let file = await fs.open(filePath)
      let fdPath = 'fd://' + file.fd.toString()
      this.player!.url = fdPath

    } catch (e) {
      console.log('init err:', e)
    }
  }

  async start(filePath: string) {
    try {
      if (!this.player) {
        await this.init(filePath)
      } else {
        await this.player.play()
      }
    } catch (e) {
      console.log('start err:', e)
    }
  }

  async pause() {
    this.player?.pause()
  }

  async stop() {
    if (this.playerState === 'prepared' || this.playerState === 'playing' || this.playerState === 'paused' ||
      this.playerState === 'completed') {
      await this.player?.stop()
      await this.player?.reset()
      await this.player?.release()
      this.player = undefined
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Use AVRecorder to save audio files.
  • Use AVPLayer to play audio files.
  • Be sure to get microphone permission.

Written by Mehmet Karaaslan

Top comments (0)