您现在的位置是:亿华云 > 数据库

HarmonyOS Sample 之 DistributedMusicPlayer分布式音乐播放器

亿华云2025-10-09 07:00:16【数据库】3人已围观

简介想了解更多内容,请访问:和华为官方合作共建的鸿蒙技术社区https://harmonyos.51cto.comDistributedMusicPlayer分布式音乐播放器介绍本示例主要演示了如何通过迁

想了解更多内容,分布放器请访问:

和华为官方合作共建的式音鸿蒙技术社区

https://harmonyos.51cto.com

DistributedMusicPlayer分布式音乐播放器

介绍

本示例主要演示了如何通过迁移数据进行音乐的分布式播放。实现了音乐播放的乐播跨设备迁移,包括:播放哪首歌曲、分布放器播放进度、式音以及播放状态的乐播保持。

效果展示

搭建环境

安装DevEco Studio,分布放器详情请参考DevEco Studio下载。式音

设置DevEco Studio开发环境,乐播DevEco Studio开发环境需要依赖于网络环境,分布放器需要连接上网络才能确保工具的式音正常使用,可以根据如下两种情况来配置开发环境:

如果可以直接访问Internet,乐播只需进行下载HarmonyOS SDK操作。分布放器

如果网络不能直接访问Internet,式音需要通过代理服务器才可以访问,乐播请参考配置开发环境。

下载源码,导入项目。

代码结构

  config.json #全局配置文件 │ ├─java │  └─ohos │      └─samples │          └─distributedmusicplayer │              │  MainAbility.java │              │ │              ├─slice │              │      MainAbilitySlice.java     #播放器主能力Slice │              │ │              └─utils │                      LogUtil.java     #日志工具类 │                      PlayerManager.java   #播放器管理者 │                      PlayerStateListener.java #播放器状态监听器 │ └─resources     ├─base     │  ├─element     │  │      string.json     │  │     │  ├─graphic     │  │      button_bg.xml     │  │     │  ├─layout     │  │      main_ability_slice.xml        #播放器页面布局     │  │     │  └─media                  #海报、按钮图片资源     │          album.png     │          album2.png     │          bg_blurry.png     │          icon.png     │          ic_himusic_next.png     │          ic_himusic_pause.png     │          ic_himusic_play.png     │          ic_himusic_previous.png     │          remote_play_selected.png     │     └─rawfile                   #歌曲媒体资源             Homey.mp3             Homey.wav             Technology.mp3             Technology.wav 

实现步骤

1.实现跨设备迁移标准步骤,参见HarmonyOS Sample 之 AbilityInteraction设备迁移

2.实现一个播放器管理者PlayerManager

2.1.定义播放器的状态,包括: 播放、暂停、完成、播放中

private static final int PLAY_STATE_PLAY = 0x0000001; private static final int PLAY_STATE_PAUSE = 0x0000002; private static final int PLAY_STATE_FINISH = 0x0000003; private static final int PLAY_STATE_PROGRESS = 0x0000004; 

2.2.实现基本的方法,包括:播放、暂停、云服务器提供商切换歌曲、更新播放进度方法

还有一些辅助方法,包括:设置媒体资源、定时更新播放进度、获取播放总时长、

要用到Player/Timer/自定义的PlayerStateListener/EventHandler事件处理/PlayCallBack播放器回调类

/**  * play  */ public void play() {      try {          if (!isPrepared) {              LogUtil.error(TAG, "prepare fail");             return;         }         //如果开始播放则返回真; 否则返回 false。         if (!musicPlayer.play()) {              LogUtil.error(TAG, "play fail");             return;         }         startTask();         handler.sendEvent(PLAY_STATE_PLAY);     } catch (IllegalArgumentException e) {          LogUtil.error(TAG, e.getMessage());         e.printStackTrace();     } } /**  * pause  */ public void pause() {      if (!musicPlayer.pause()) {          LogUtil.info(TAG, "pause fail");         return;     }     //停止计时     finishTask();     //     handler.sendEvent(PLAY_STATE_PAUSE); } /**  * switch music  *  * @param uri music uri  */ public void switchMusic(String uri) {      currentUri = uri;     //设置资源     setResource(currentUri);     //播放     play(); } /**  * changes the playback position  * 更新当前播放进度  *  * @param currentTime current time  */ public void rewindTo(int currentTime) {      musicPlayer.rewindTo(currentTime * 1000); } /**  * set source  *  * @param uri music uri  */ public void setResource(String uri) {      LogUtil.info(TAG, "setResource,uri:  " + uri);     try {          RawFileEntry rawFileEntry = context.getResourceManager().getRawFileEntry(uri);         BaseFileDescriptor baseFileDescriptor = rawFileEntry.openRawFileDescriptor();         //LogUtil.info(TAG, "setResource,baseFileDescriptor :  " + baseFileDescriptor);         if (!musicPlayer.setSource(baseFileDescriptor)) {              LogUtil.info(TAG, "uri is invalid");             return;         }         //准备播放环境并缓冲媒体数据。         isPrepared = musicPlayer.prepare();         LogUtil.info(TAG, "setResource,isPrepared:  " + isPrepared);         //歌曲名称         String listenerUri = currentUri.substring(currentUri.lastIndexOf("/") + 1, currentUri.lastIndexOf("."));         playerStateListener.onUriSet(listenerUri);         LogUtil.info(TAG, "setResource,listenerUri:  " + listenerUri);     } catch (IOException e) {          LogUtil.error(TAG, "io exception");     } } /**  * 定时事件通知更新进度条  */ private void startTask() {      LogUtil.debug(TAG, "startTask");     finishTask();     timerTask = new TimerTask() {          @Override         public void run() {              handler.sendEvent(PLAY_STATE_PROGRESS);         }     };     timer = new Timer();     timer.schedule(timerTask, DELAY_TIME, PERIOD); } private void finishTask() {      LogUtil.debug(TAG, "finishTask");     if (timer != null && timerTask != null) {          timer.cancel();         timer = null;         timerTask = null;     } }  

2.3.PlayerStateListener播放器状态监听器有如下方法:

onPlaySuccess播放成功时被调用

onPauseSuccess暂停时被调用

onPositionChange进度发生变化时被调用

onMusicFinished音乐播放完成时被调用

onUriSet资源被设置时被调用

/**  * PlayerStateListener  */ public interface PlayerStateListener {      void onPlaySuccess(int totalTime);     void onPauseSuccess();     void onPositionChange(int currentTime);     void onMusicFinished();     void onUriSet(String name); } 

2.4.PlayCallBack播放器回调类实现了Player.IPlayerCallback接口,实现了如下方法:

onPrepared 当媒体文件准备好播放时调用。

onMessage当收到播放器消息或警报时调用。

onError收到播放器错误消息时调用。

onResolutionChanged当视频大小改变时调用。

onPlayBackComplete播放完成时调用。

onRewindToComplete 当播放位置被 Player.rewindTo(long) 改变时调用。

onBufferingChange当缓冲百分比更新时调用。

onNewTimedMetaData当有新的定时元数据可用时调用。

onMediaTimeIncontinuity当媒体时间连续性中断时调用,例如播放过程中出现错误,播放位置被Player.rewindTo(long)改变,或者播放速度突然改变。

/**  * 在播放完成、播放位置更改和视频大小更改时提供媒体播放器回调。  */ private class PlayCallBack implements Player.IPlayerCallback {      /**      * 当媒体文件准备好播放时调用。      */     @Override     public void onPrepared() {          LogUtil.info(TAG, "onPrepared");     }     /**      * 当收到播放器消息或警报时调用。      *      * @param type      * @param extra      */     @Override     public void onMessage(int type, int extra) {          LogUtil.info(TAG, "onMessage  " + type + "-" + extra);     }     /**      * 收到播放器错误消息时调用。      *      * @param errorType      * @param errorCode      */     @Override     public void onError(int errorType, int errorCode) {          LogUtil.info(TAG, "onError  " + errorType + "-" + errorCode);     }     /**      * 当视频大小改变时调用。      *      * @param width      * @param height      */     @Override     public void onResolutionChanged(int width, int height) {          LogUtil.info(TAG, "onResolutionChanged  " + width + "-" + height);     }     /**      * 播放完成时调用。      */     @Override     public void onPlayBackComplete() {          //不会自动被调用????         LogUtil.info(TAG, "onPlayBackComplete----------------");         handler.sendEvent(PLAY_STATE_FINISH);     }     /**      * 当播放位置被 Player.rewindTo(long) 改变时调用。      */     @Override     public void onRewindToComplete() {          LogUtil.info(TAG, "onRewindToComplete");     }     /**      * 当缓冲百分比更新时调用。      *      * @param percent      */     @Override     public void onBufferingChange(int percent) {          LogUtil.info(TAG, "onBufferingChange:" + percent);     }     /**      * 当有新的定时元数据可用时调用。亿华云计算      *      * @param mediaTimedMetaData      */     @Override     public void onNewTimedMetaData(Player.MediaTimedMetaData mediaTimedMetaData) {          LogUtil.info(TAG, "onNewTimedMetaData");     }     /**      * 当媒体时间连续性中断时调用,例如播放过程中出现错误,播放位置被Player.rewindTo(long)改变,或者播放速度突然改变。      *      * @param mediaTimeInfo      */     @Override     public void onMediaTimeIncontinuity(Player.MediaTimeInfo mediaTimeInfo) {          LogUtil.info(TAG, "onNewTimedMetaData");     } } 

3.MainAbilitySlice 中 implements PlayerStateListener , IAbilityContinuation接口

public class MainAbilitySlice extends AbilitySlice implements PlayerStateListener, IAbilityContinuation {  ... 

3.1.实现PlayerStateListener接口方法

@Override public void onPlaySuccess(int totalTime) {      LogUtil.debug(TAG, "onPlaySuccess");     //设置图标     musicPlayButton.setPixelMap(ResourceTable.Media_ic_himusic_pause);     //设置总时长文本     this.totalTimeText.setText(getTime(totalTime));     //设置进度条     slider.setMaxValue(totalTime);     //设置当前歌曲海报     musicPosters.setPixelMap(posters[currentPos]); } @Override public void onPauseSuccess() {      LogUtil.debug(TAG, "onPauseSuccess");     //设置图标     musicPlayButton.setPixelMap(ResourceTable.Media_ic_himusic_play); } @Override public void onUriSet(String name) {      LogUtil.debug(TAG, "onUriSet");     //设置歌曲名称     musicNameText.setText(name); } @Override public void onPositionChange(int currentTime) {     if(currentTime < totalTime){         LogUtil.info(TAG, "onPositionChange currentTime = " + currentTime+",totalTime="+totalTime);        this.currentTime = currentTime;         //设置播放时间文本         this.currentTimeText.setText(getTime(currentTime));         //设置进度条的当前播放时间         slider.setProgressValue(currentTime);     }else{         LogUtil.info(TAG, "onPositionChange, current song end");        //设置播放器图标         musicPlayButton.setPixelMap(ResourceTable.Media_ic_himusic_play);     } } /**  *音乐播放完成时应该被调用,但是没被调用  */ @Override public void onMusicFinished() {      //TODO???????????     LogUtil.debug(TAG, "onMusicFinished");     currentPos = currentPos == 0 ? 1 : 0;     currentUri = musics[currentPos];     //切换歌曲     playerManager.switchMusic(currentUri);     //总时长     totalTime=playerManager.getTotalTime(); } 

3.2.实现IAbilityContinuation接口方法

@Override public boolean onStartContinuation() {      LogUtil.debug(TAG, "onStartContinuation");     return true; } @Override public boolean onSaveData(IntentParams intentParams) {      LogUtil.debug(TAG, "onSaveData");     //     intentParams.setParam(KEY_CURRENT_TIME, currentTime);     intentParams.setParam(KEY_POSITION, currentPos);     intentParams.setParam(KEY_PLAY_STATE, String.valueOf(playerManager.isPlaying()));     LogUtil.info(TAG, "onSaveData:" + currentTime);     return true; } @Override public boolean onRestoreData(IntentParams intentParams) {      LogUtil.debug(TAG, "onRestoreData");     if (!(intentParams.getParam(KEY_POSITION) instanceof Integer)) {          return false;     }     if (!(intentParams.getParam(KEY_CURRENT_TIME) instanceof Integer)) {          return false;     }     if (!(intentParams.getParam(KEY_PLAY_STATE) instanceof String)) {          return false;     }     //恢复数据,获取迁移过来的参数:播放位置、时间和播放状态     currentPos = (int) intentParams.getParam(KEY_POSITION);     currentTime = (int) intentParams.getParam(KEY_CURRENT_TIME);     Object object = intentParams.getParam(KEY_PLAY_STATE);     if (object instanceof String) {          isPlaying = Boolean.parseBoolean((String) object);     }     isInteractionPlay = true;     LogUtil.info(TAG, "onRestoreData:" + currentTime);     return true; } @Override public void onCompleteContinuation(int i) {      terminate(); } 

3.3.定义ValueChangedListenerImpl进度值变化的监听事件

实现 Slider.ValueChangedListener 接口方法

/**  *进度条值变化的监听事件  */ private class ValueChangedListenerImpl implements Slider.ValueChangedListener {      @Override     public void onProgressUpdated(Slider slider, int progress, boolean fromUser) {          currentTime = progress;     }     @Override     public void onTouchStart(Slider slider) {          LogUtil.debug(TAG, "onTouchStart");     }     @Override     public void onTouchEnd(Slider slider) {          LogUtil.debug(TAG, "onTouchEnd");         //快速更改播放进度         playerManager.rewindTo(currentTime);         //当前播放时间         currentTimeText.setText(getTime(currentTime));     } } 

3.4.定义迁移数据的KEY,音乐当前的播放时间、播放的歌曲索引(位置)、播放状态

private static final String KEY_CURRENT_TIME = "main_ability_slice_current_time"; private static final String KEY_POSITION = "main_ability_slice_position"; private static final String KEY_PLAY_STATE = "main_ability_slice_play_state"; private int currentPos = 0; private String currentUri; //是否是互动播放,true表示远端迁移恢复的 private boolean isInteractionPlay; private int currentTime; //当前播放歌曲总时长 private int totalTime; private boolean isPlaying; 

 3.5.定义播放的音乐URI,这里准备了2首,还有对应的海报

private static final String URI1 = "resources/rawfile/Technology.wav"; private static final String URI2 = "resources/rawfile/Homey.wav"; private final String[] musics = { URI1, URI2}; private final int[] posters = { ResourceTable.Media_album, ResourceTable.Media_album2}; 

 3.6.onStart完成数据的初始化

@Override public void onStart(Intent intent) {      super.onStart(intent);     super.setUIContent(ResourceTable.Layout_main_ability_slice);     initComponents();     initMedia();     updateUI(); } 

初始化界面组件,实现对应按钮的监听事件

播放或暂停、上一首、下一首、迁移以及进度条的进度变化事件的b2b信息网监听

/**  * 初始化界面组件,实现对应按钮的监听事件  * 播放或暂停、上一首、下一首、迁移以及进度条的进度变化事件的监听  */ private void initComponents() {      LogUtil.debug(TAG, "initComponents");     musicNameText = (Text) findComponentById(ResourceTable.Id_music_name);     currentTimeText = (Text) findComponentById(ResourceTable.Id_play_progress_time);     totalTimeText = (Text) findComponentById(ResourceTable.Id_play_total_time);     musicPosters = (Image) findComponentById(ResourceTable.Id_music_posters);     musicPlayButton = (Image) findComponentById(ResourceTable.Id_music_play_btn);     findComponentById(ResourceTable.Id_remote_play).setClickedListener(this::continueAbility);     findComponentById(ResourceTable.Id_music_play_prev_btn).setClickedListener(this::prevMusic);     findComponentById(ResourceTable.Id_music_play_next_btn).setClickedListener(this::nextMusic);     musicPlayButton.setClickedListener(this::playOrPauseMusic);     //     slider = (Slider) findComponentById(ResourceTable.Id_play_progress_bar);     slider.setValueChangedListener(new ValueChangedListenerImpl()); } private void continueAbility(Component component) {      try {          continueAbility();     } catch (IllegalStateException e) {          LogUtil.info(TAG, e.getMessage());     } } /**  * 上一首  * @param component  */ private void prevMusic(Component component) {      currentPos = currentPos == 0 ? 1 : 0;     currentUri = musics[currentPos];     //     playerManager.switchMusic(currentUri);     //总时长     totalTime=playerManager.getTotalTime(); } /**  * 下一首  * @param component  */ private void nextMusic(Component component) {      currentPos = currentPos == 0 ? 1 : 0;     currentUri = musics[currentPos];     //切换音乐     playerManager.switchMusic(currentUri);     //总时长     totalTime=playerManager.getTotalTime(); } /**  * 播放或暂停音乐  * @param component  */ private void playOrPauseMusic(Component component) {      //     playOrPause(); } /**  * 播放或暂停  */ private void playOrPause() {      LogUtil.debug(TAG, "playOrPause,playerManager:"+playerManager);     try {          //         if (playerManager.isPlaying()) {              LogUtil.debug(TAG, "playOrPause pause");             playerManager.pause();         }else{              //设置资源             playerManager.setResource(currentUri);             //设置进度             playerManager.rewindTo(currentTime);             playerManager.play();             LogUtil.debug(TAG, "playOrPause play");         }     } catch (Exception e) {          LogUtil.error(TAG, "playOrPause");         e.printStackTrace();     } } 

3.7.初始化媒体对象

当前播放歌曲资源,播放器管理者

/**  * 初始化媒体对象  * 当前播放歌曲资源  * 播放器管理者  */ private void initMedia() {      LogUtil.debug(TAG, "initMedia");     //当前媒体URI     currentUri = musics[currentPos];     LogUtil.debug(TAG, "initMedia,currentUri:"+currentUri);     //初始化playerManager     playerManager = new PlayerManager(getApplicationContext(), currentUri);     //弱引用对象,不会阻止它们的引用对象被终结、终结和回收。 弱引用最常用于实现规范化映射。     WeakReference<PlayerStateListener> playerStateListener = new WeakReference<>(this);     //设置状态监听器     playerManager.setPlayerStateListener(playerStateListener.get());     //初始化播放器信息     playerManager.init();     LogUtil.debug(TAG, "initMedia FINISH"); } 

3.8.远端迁移后恢复播放界面

恢复播放器的播放进度、播放状态、海报、当前时间和总时长、slider播放进度

/**  * 远端迁移后恢复的播放,恢复播放器的播放进度  * 更新UI界面  */ private void updateUI() {      LogUtil.debug(TAG, "updateUI");     //海报     musicPosters.setPixelMap(posters[currentPos]);     //当前时间和总时长     currentTimeText.setText(getTime(currentTime));     totalTimeText.setText(getTime(playerManager.getTotalTime()));     //播放进度     slider.setMaxValue(playerManager.getTotalTime());     slider.setProgressValue(currentTime);     //总时长     totalTime=playerManager.getTotalTime();     //远端迁移恢复     if (isInteractionPlay) {          LogUtil.debug(TAG, "remotePlay,rewindTo:"+currentTime);         playerManager.rewindTo(currentTime);         if (!isPlaying) {              return;         }         //播放         playerManager.play();     } } 

问题总结

1.onMusicFinished 音乐播放完成时应该被调用,但是多数没被调用,只是偶尔会调用,难道是我电脑性能跟不上了?

2.优化了源码中应用启动后,点击播放无法播放的问题

3.优化了播放器播放完当前歌曲更新播放图标

4.增加了相关的注释说明

附件直接下载DistributedMusicPlayer.zip

想了解更多内容,请访问:

和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

很赞哦!(8)