1. 介绍 音频播放相关管理应用包括音频播放、声音采集和音频相关管理(快进、快退、暂停、播放、音量控制)等功能,本篇Codelab旨在帮助开发者快速了解HarmonyOS应用开发、播放器应用以及音频收集和系统音量控制的用法。
本篇Codelab将实现的内容您将构建一个应用程序实现以下内容:
- 基于HarmonyOS Player播放本地音频资源或从Internet获得的音频资源。
- 通过音频采集器采集现场音频流并保存到本地,以读取流的方式播放录音。
- 分别对通知音量、媒体音量、通话音量的控制。
你将会学到什么- 如何使用Player播放包括mp3、m4a、aac等主流音频格式音频。
- 如何使用Player对音频播放控制。
- 如何使用AudioRecorder采集音频。
- 如何使用AudioRender播放音频。
- 如何使用SoundPlayer播放系统短音。
- 如何使用AudioManager设置通知音量、媒体音量和通话音量。
2. 搭建HarmonyOS环境
我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。
- 安装DevEco Studio,详情请参考下载和安装软件。
- 设置DevEco Studio开发环境,DevEco Studio开发环境依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
- 如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
- 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境。
- 开发者可以参考以下链接,完成设备调试的相关配置:
3. 代码结构
本篇Codelab只对核心代码进行讲解,对于完整代码,我们在9 参考章节提供下载方式。接下来我们会讲解整个工程的代码结构,如下图:
- api:音频播放器及媒体接口文件。
- constant:代码中使用到的常量。
- factoty:SourceFactory构建媒体资源。
- manager:HmPlayerLifecycle处理播放器生命周期。
- view:PlayerLoading,SimplePlayerController分别为音频加载状态及进度条控制类文件。
- HmPlayer:封装播放器的主要功能方法。
- slice:MainAbilitySlice主程序页面和其他相关功能页面。
- utils:存放所有封装好的公共方法,如DateUtils,LogUtils等。
- resources:存放工程使用到的资源文件,其中resourcesbaselayout下存放xml布局文件;resourcesbasemedia下存放音频文件。
- config.json:配置文件,ability声明及权限配置。
4. 音频播放器 音频播放业务逻辑步骤 1 - 创建Player,设置状态回调,HmPlayerCallback继承Player.IPLayerCallback。
- Player player = new Player(Context);
- player.setPlayerCallback(new HmPlayerCallback());
步骤 2 - 准备媒体资源,封装了SourceFactory,根据传入播放源类型。
- private void initSourceType(Context context, String path) throws IOException {
- if (context == null || path == null) {
- return;
- }
- if (path.substring(0, NET_HTTP_MATCH.length()).equalsIgnoreCase(NET_HTTP_MATCH)
- || path.substring(0, NET_RTMP_MATCH.length()).equalsIgnoreCase(NET_RTMP_MATCH)
- || path.substring(0, NET_RTSP_MATCH.length()).equalsIgnoreCase(NET_RTSP_MATCH)) {
- mPlayerSource = new Source(path);
- } else if (path.startsWith(STORAGE_MATCH)) {
- File file = new File(path);
- if (file.exists()) {
- FileInputStream fileInputStream = new FileInputStream(file);
- FileDescriptor fileDescriptor = fileInputStream.getFD();
- mPlayerSource = new Source(fileDescriptor);
- }
- } else {
- RawFileDescriptor fd = context.getResourceManager().getRawFileEntry(path).openRawFileDescriptor();
- mPlayerSource = new Source(fd.getFileDescriptor(), fd.getStartPosition(), fd.getFileSize());
- }
- }
步骤 3 - 播放。
- private void start() {
- if (mPlayer != null) {
- mBuilder.mContext.getGlobalTaskDispatcher(TaskPriority.DEFAULT).asyncDispatch(() -> {
- if (surface != null) {
- mPlayer.setVideoSurface(surface);
- } else {
- LogUtil.error(TAG, "The surface has not been initialized.");
- }
- mPlayer.prepare();
- if (mBuilder.startMillisecond > 0) {
- int microsecond = mBuilder.startMillisecond * MICRO_MILLI_RATE;
- mPlayer.rewindTo(microsecond);
- }
- mPlayer.play();
- });
- }
- }
步骤 4 - 状态回调,步骤1中设置播放器的状态回调,进度条栏目通过HmPlayer接口addPlayerStatuCallback添加状态回调实现UI更新。
- @Override
- public void onMediaTimeIncontinuity(Player.MediaTimeInfo mediaTimeInfo) {
- LogUtil.info(TAG, "onMediaTimeIncontinuity is called");
- for (Player.StreamInfo streanInfo : mPlayer.getStreamInfo()) {
- int streamType = streanInfo.getStreamType();
- if (streamType == Player.StreamInfo.MEDIA_STREAM_TYPE_AUDIO && mStatu == PlayerStatu.PREPARED) {
- for (StatuChangeListener callback : statuChangeCallbacks) {
- mStatu = PlayerStatu.PLAY;
- callback.statuCallback(PlayerStatu.PLAY);
- }
- if (mBuilder.isPause) {
- pause();
- }
- }
- }
- }
-
- private StatuChangeListener mStatuChangeListener = new StatuChangeListener() {
- [url=home.php?mod=space&uid=2735960]@Override[/url]
- public void statuCallback(PlayerStatu statu) {
- mContext.getUITaskDispatcher().asyncDispatch(() -> {
- switch (statu) {
- case PREPARING:
- mPlayToogle.setClickable(false);
- mProgressBar.setEnabled(false);
- mProgressBar.setProgressValue(0);
- break;
- case PREPARED:
- mProgressBar.setMaxValue(mPlayer.getDuration());
- mTotleTime.setText(DateUtils.msToString(mPlayer.getDuration()));
- break;
- case PLAY:
- showController(false);
- mPlayToogle.setPixelMap(ResourceTable.Media_ic_music_stop);
- mPlayToogle.setClickable(true);
- mProgressBar.setEnabled(true);
- break;
- case PAUSE:
- mPlayToogle.setPixelMap(ResourceTable.Media_ic_music_play);
- break;
- case STOP:
- case COMPLETE:
- mPlayToogle.setPixelMap(ResourceTable.Media_ic_update);
- mProgressBar.setEnabled(false);
- break;
- default:
- break;
- }
- });
- }
- };
使用方法公共业务流程以封装成HmPlayer,开发者可以更快捷方便的使用播放器。
- 通过HmPlayer.Builder设置播放器参数并使用creater方法构造HmPlayer
- player = new HmPlayer.Builder(this).setFilePath(mUrl).create();
- PlayerModule控件绑定HmPlayer
- playerView.bind(player);
- playerLoading.bind(player);
- controllerView.bind(player);
- 播放
- getGlobalTaskDispatcher(TaskPriority.DEFAULT).delayDispatch(() -> player.play(), Const.NUMBER_100);
- 同步播放器生命周期到AbilitySlice
- @Override
- public void onStart(Intent intent) {
- super.onStart(intent);
- player.getLifecycle().onStart();
- }
-
- @Override
- public void onForeground(Intent intent) {
- player.getLifecycle().onForeground();
- super.onForeground(intent);
- }
-
- @Override
- protected void onBackground() {
- player.getLifecycle().onBackground();
- super.onBackground();
- }
-
- @Override
- protected void onStop() {
- player.getLifecycle().onStop();
- super.onStop();
- }
5. 系统短音 使用方法步骤 1 - 创建SoundPlayer
- SoundPlayer soundPlayer = new SoundPlayer();
步骤 2 - 初始化系统短音,第一个参数为短音类型,第个二参数为播放时长
- soundPlayer.createSound(ToneDescriptor.ToneType.DTMF_0, 500);
步骤 3 - 播放
步骤 4 - 释放资源
- if (soundPlayer != null) {
- soundPlayer.release();
- soundPlayer = null;
- }
6. 录音&播放录音 使用AudioRecorder和AudioRender分别实现录音和播放录音功能,提供了录音、停止录音、播放录音、释放资源等接口,支持录音实时播放和完整录音文件播放模式,按住录音按钮开始录音,松手录音停止并把录音文件保存到本地,点击播放按钮播放录音。
录音业务逻辑步骤 1 - 设置录入流相关配置AudioStreamInfo并初始化AudioCapturer以调用底层录音功能
- private void initRecord() {
- if (audioStreamInfo == null) {
- audioStreamInfo = new AudioStreamInfo.Builder()
- .encodingFormat(builder.encodingFormat)
- .channelMask(builder.channelMask)
- .sampleRate(builder.inputSampleRate)
- .build();
- AudioCapturerInfo audioCapturerInfo = new AudioCapturerInfo.Builder()
- .audioStreamInfo(audioStreamInfo)
- .audioInputSource(builder.inputSource)
- .build();
- audioCapturer = new AudioCapturer(audioCapturerInfo);
- }
- }
步骤 2 - 设置录音保存路径,构建AudioRecorder
- savefilePath = getExternalFilesDir(Environment.DIRECTORY_MUSIC) + File.separator + "AudioTest.mp3";
- audioRecorder = new AudioRecorder.Builder(this).setSaveFilePath(savefilePath).create();
步骤 3 - 使用AudioRecorder开始录音并写入文件
- private void beginRecord() {
- if (!audioRecorder.isRecording()) {
- recordTag = isRealTimePlay ? 0 : 1;
- audioRecorder.record();
- }
- }
播放录音业务逻辑步骤 1 - 初始化AudioRender设置播放音频流播放格式和
- private void initRender() {
- AudioStreamInfo asi = new AudioStreamInfo.Builder()
- .encodingFormat(builder.encodingFormat)
- .channelMask(builder.channelMask)
- .sampleRate(builder.inputSampleRate)
- .audioStreamFlag(builder.streamFlag)
- .streamUsage(builder.streamUsage)
- .build();
- audioRenderInfo = new AudioRendererInfo.Builder()
- .audioStreamInfo(asi)
- .audioStreamOutputFlag(builder.streamOutputFlag)
- .bufferSizeInBytes(bufferSize)
- .isOffload(builder.isOneOffLoad) // false表示分段传输buffer并播放,true表示整个音频流一次性传输到HAL层播放
- .build();
- audioRender = new AudioRenderer(audioRenderInfo, AudioRenderer.PlayMode.MODE_STREAM);
- }
步骤 2 - 设置音频流播放结束监听器
- audioRender.setFrameIntervalObserver(() -> {
- if (audioRender.getAudioTime().getFramePosition() != 0) {
- if (audioPlayListener != null) {
- audioPlayListener.onComplete();
- }
- release();
- }
- }, MediaConst.READ_RENDER_INTERVAL, audioRenderHandler);
步骤 3 - 启动播放录音并往AudioRender中写入录音流
- public void play(byte[] bytes, int length) {
- if (audioRenderInfo == null) {
- initRender();
- }
- start();
- audioRenderHandler.postTask(() -> {
- byte[] datas = new byte[length];
- System.arraycopy(bytes, 0, datas, 0, datas.length);
- audioRender.write(datas, 0, datas.length);
- });
- }
7. 音量管理 音量管理功能分为通知音量、媒体音量和通话音量的控制,此项目中点击"音量管理",通过Slider左右滑动设置音量。
使用方法步骤 1 - 初始化音量管理者AudioManager
AudioManager audioManager = new AudioManager();
步骤 2 - 根据Slider返回数值设置音量,STREAM_DTMF代表通知音量、STREAM_MUSIC代表媒体音量、STREAM_VOICE_CALL代表通话音量。
- @Override
- public void onTouchEnd(Slider slider) {
- int progress = slider.getProgress();
- switch (slider.getId()) {
- case ResourceTable.Id_sound_volume_bar:
- try {
- if (audioManager.setVolume(AudioManager.AudioVolumeType.STREAM_DTMF, progress)) {
- dtmfVolume = progress;
- } else {
- audioManager.setVolume(AudioManager.AudioVolumeType.STREAM_DTMF, dtmfVolume);
- }
- } catch (SecurityException e) {
- LogUtil.error(TAG, e.getMessage());
- }
- break;
- case ResourceTable.Id_sound_volume_bar2:
- try {
- if (audioManager.setVolume(AudioManager.AudioVolumeType.STREAM_MUSIC, progress)) {
- musicVolume = progress;
- } else {
- audioManager.setVolume(AudioManager.AudioVolumeType.STREAM_MUSIC, musicVolume);
- }
- } catch (SecurityException e) {
- LogUtil.error(TAG, e.getMessage());
- }
- break;
- case ResourceTable.Id_sound_volume_bar3:
- try {
- if (audioManager.setVolume(AudioManager.AudioVolumeType.STREAM_VOICE_CALL, progress)) {
- callVolume = progress;
- } else {
- audioManager.setVolume(AudioManager.AudioVolumeType.STREAM_VOICE_CALL, callVolume);
- }
- } catch (SecurityException e) {
- LogUtil.error(TAG, e.getMessage());
- }
- break;
- default:
- break;
- }
- }
8. 恭喜你 目前你已经成功完成了Codelab并且学到了:
- 使用player播放本地音频资源或从Internet获得的音频资源。
- 录音功能、保存录音文件和实时播放录音。
- 系统音量的控制
9. 参考