栏目分类:
子分类:
返回
终身学习网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
终身学习网 > IT > 前沿技术 > 云计算 > Docker/k8s

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

Docker/k8s 更新时间:发布时间: 百科书网 趣学号



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

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

https://harmonyos.51cto.com

DistributedMusicPlayer分布式音乐播放器 介绍

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

效果展示



搭建环境

安装DevEco Studio,详情请参考DevEco Studio下载。

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

如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。

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

下载源码,导入项目。

代码结构
  1.   config.json #全局配置文件 │ 
  2. ├─java │  └─ohos 
  3. │      └─samples │          └─distributedmusicplayer 
  4. │              │  MainAbility.java │              │ 
  5. │              ├─slice │              │      MainAbilitySlice.java     #播放器主能力Slice 
  6. │              │ │              └─utils 
  7. │                      LogUtil.java     #日志工具类 │                      PlayerManager.java   #播放器管理者 
  8. │                      PlayerStateListener.java #播放器状态监听器 │ 
  9. └─resources     ├─base 
  10.     │  ├─element     │  │      string.json 
  11.     │  │     │  ├─graphic 
  12.     │  │      button_bg.xml     │  │ 
  13.     │  ├─layout     │  │      main_ability_slice.xml        #播放器页面布局 
  14.     │  │     │  └─media                  #海报、按钮图片资源 
  15.     │          album.png     │          album2.png 
  16.     │          bg_blurry.png     │          icon.png 
  17.     │          ic_himusic_next.png     │          ic_himusic_pause.png 
  18.     │          ic_himusic_play.png     │          ic_himusic_previous.png 
  19.     │          remote_play_selected.png     │ 
  20.     └─rawfile                   #歌曲媒体资源             Homey.mp3 
  21.             Homey.wav             Technology.mp3 
  22.             Technology.wav 
实现步骤

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

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

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

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

2.2.实现基本的方法,包括:播放、暂停、切换歌曲、更新播放进度方法

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

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

  1.  public void play() { 
  2.     try {         if (!isPrepared) { 
  3.             LogUtil.error(TAG, "prepare fail");             return; 
  4.         }         //如果开始播放则返回真; 否则返回 false。 
  5.         if (!musicPlayer.play()) {             LogUtil.error(TAG, "play fail"); 
  6.             return;         } 
  7.         startTask();         handler.sendEvent(PLAY_STATE_PLAY); 
  8.     } catch (IllegalArgumentException e) {         LogUtil.error(TAG, e.getMessage()); 
  9.         e.printStackTrace();     } 
  10. }  
  11.  public void pause() { 
  12.     if (!musicPlayer.pause()) {         LogUtil.info(TAG, "pause fail"); 
  13.         return;     } 
  14.     //停止计时     finishTask(); 
  15.     //     handler.sendEvent(PLAY_STATE_PAUSE); 
  16. }  
  17. public void switchMusic(String uri) {     currentUri = uri; 
  18.     //设置资源     setResource(currentUri); 
  19.     //播放     play(); 
  20. }  
  21.  
  22. public void rewindTo(int currentTime) {     musicPlayer.rewindTo(currentTime * 1000); 
  23. }  
  24.  public void setResource(String uri) { 
  25.     LogUtil.info(TAG, "setResource,uri:  " + uri);     try { 
  26.         RawFileEntry rawFileEntry = context.getResourceManager().getRawFileEntry(uri);         baseFileDescriptor baseFileDescriptor = rawFileEntry.openRawFileDescriptor(); 
  27.         //LogUtil.info(TAG, "setResource,baseFileDescriptor :  " + baseFileDescriptor);         if (!musicPlayer.setSource(baseFileDescriptor)) { 
  28.             LogUtil.info(TAG, "uri is invalid");             return; 
  29.         }         //准备播放环境并缓冲媒体数据。 
  30.         isPrepared = musicPlayer.prepare();         LogUtil.info(TAG, "setResource,isPrepared:  " + isPrepared); 
  31.         //歌曲名称         String listenerUri = currentUri.substring(currentUri.lastIndexOf("/") + 1, currentUri.lastIndexOf(".")); 
  32.         playerStateListener.onUriSet(listenerUri);         LogUtil.info(TAG, "setResource,listenerUri:  " + listenerUri); 
  33.     } catch (IOException e) {         LogUtil.error(TAG, "io exception"); 
  34.     } } 
  35.  private void startTask() { 
  36.     LogUtil.debug(TAG, "startTask");     finishTask(); 
  37.     timerTask = new TimerTask() {         @Override 
  38.         public void run() {             handler.sendEvent(PLAY_STATE_PROGRESS); 
  39.         }     }; 
  40.     timer = new Timer();     timer.schedule(timerTask, DELAY_TIME, PERIOD); 
  41. }  
  42. private void finishTask() {     LogUtil.debug(TAG, "finishTask"); 
  43.     if (timer != null && timerTask != null) {         timer.cancel(); 
  44.         timer = null;         timerTask = null; 
  45.     } }  

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

onPlaySuccess播放成功时被调用

onPauseSuccess暂停时被调用

onPositionChange进度发生变化时被调用

onMusicFinished音乐播放完成时被调用

onUriSet资源被设置时被调用

  1.  public interface PlayerStateListener { 
  2.     void onPlaySuccess(int totalTime);  
  3.     void onPauseSuccess();  
  4.     void onPositionChange(int currentTime);  
  5.     void onMusicFinished();  
  6.     void onUriSet(String name); } 

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

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

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

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

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

onPlayBackComplete播放完成时调用。

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

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

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

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

  1.  private class PlayCallBack implements Player.IPlayerCallback { 
  2.       
  3.     @Override     public void onPrepared() { 
  4.         LogUtil.info(TAG, "onPrepared");     } 
  5.           @Override 
  6.     public void onMessage(int type, int extra) {         LogUtil.info(TAG, "onMessage  " + type + "-" + extra); 
  7.     }  
  8.           @Override 
  9.     public void onError(int errorType, int errorCode) {         LogUtil.info(TAG, "onError  " + errorType + "-" + errorCode); 
  10.     }  
  11.      
  12.     @Override     public void onResolutionChanged(int width, int height) { 
  13.         LogUtil.info(TAG, "onResolutionChanged  " + width + "-" + height);     } 
  14.       
  15.     @Override     public void onPlayBackComplete() { 
  16.         //不会自动被调用????         LogUtil.info(TAG, "onPlayBackComplete----------------"); 
  17.         handler.sendEvent(PLAY_STATE_FINISH);  
  18.     }  
  19.          @Override 
  20.     public void onRewindToComplete() {         LogUtil.info(TAG, "onRewindToComplete"); 
  21.     }  
  22.          @Override 
  23.     public void onBufferingChange(int percent) {         LogUtil.info(TAG, "onBufferingChange:" + percent); 
  24.     }  
  25.          @Override 
  26.     public void onNewTimedmetaData(Player.MediaTimedmetaData mediaTimedmetaData) {         LogUtil.info(TAG, "onNewTimedmetaData"); 
  27.     }  
  28.       
  29.     @Override     public void onMediaTimeIncontinuity(Player.MediaTimeInfo mediaTimeInfo) { 
  30.         LogUtil.info(TAG, "onNewTimedmetaData");     } 

3.MainAbilitySlice 中 implements PlayerStateListener , IAbilityContinuation接口

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

3.1.实现PlayerStateListener接口方法

  1. @Override public void onPlaySuccess(int totalTime) { 
  2.     LogUtil.debug(TAG, "onPlaySuccess");     //设置图标 
  3.     musicPlayButton.setPixelMap(ResourceTable.Media_ic_himusic_pause);     //设置总时长文本 
  4.     this.totalTimeText.setText(getTime(totalTime));     //设置进度条 
  5.     slider.setMaxValue(totalTime);     //设置当前歌曲海报 
  6.     musicPosters.setPixelMap(posters[currentPos]); } 
  7.  @Override 
  8. public void onPauseSuccess() {     LogUtil.debug(TAG, "onPauseSuccess"); 
  9.     //设置图标     musicPlayButton.setPixelMap(ResourceTable.Media_ic_himusic_play); 
  10. }  
  11. @Override public void onUriSet(String name) { 
  12.     LogUtil.debug(TAG, "onUriSet");     //设置歌曲名称 
  13.     musicNameText.setText(name); } 
  14.  @Override 
  15. public void onPositionChange(int currentTime) {    if(currentTime < totalTime){ 
  16.        LogUtil.info(TAG, "onPositionChange currentTime = " + currentTime+",totalTime="+totalTime);        this.currentTime = currentTime; 
  17.         //设置播放时间文本         this.currentTimeText.setText(getTime(currentTime)); 
  18.         //设置进度条的当前播放时间         slider.setProgressValue(currentTime); 
  19.     }else{        LogUtil.info(TAG, "onPositionChange, current song end"); 
  20.         //设置播放器图标 
  21.         musicPlayButton.setPixelMap(ResourceTable.Media_ic_himusic_play);     } 
  22. }  
  23.  @Override 
  24. public void onMusicFinished() {     //TODO??????????? 
  25.     LogUtil.debug(TAG, "onMusicFinished");     currentPos = currentPos == 0 ? 1 : 0; 
  26.     currentUri = musics[currentPos];     //切换歌曲 
  27.     playerManager.switchMusic(currentUri);     //总时长 
  28.     totalTime=playerManager.getTotalTime(); } 

3.2.实现IAbilityContinuation接口方法

  1. @Override public boolean onStartContinuation() { 
  2.     LogUtil.debug(TAG, "onStartContinuation");     return true; 
  3. }  
  4. @Override public boolean onSaveData(IntentParams intentParams) { 
  5.     LogUtil.debug(TAG, "onSaveData");     // 
  6.     intentParams.setParam(KEY_CURRENT_TIME, currentTime);     intentParams.setParam(KEY_POSITION, currentPos); 
  7.     intentParams.setParam(KEY_PLAY_STATE, String.valueOf(playerManager.isPlaying()));     LogUtil.info(TAG, "onSavedata:" + currentTime); 
  8.     return true; } 
  9.  @Override 
  10. public boolean onRestoreData(IntentParams intentParams) {     LogUtil.debug(TAG, "onRestoreData"); 
  11.     if (!(intentParams.getParam(KEY_POSITION) instanceof Integer)) {         return false; 
  12.     }     if (!(intentParams.getParam(KEY_CURRENT_TIME) instanceof Integer)) { 
  13.         return false;     } 
  14.     if (!(intentParams.getParam(KEY_PLAY_STATE) instanceof String)) {         return false; 
  15.     }  
  16.     //恢复数据,获取迁移过来的参数:播放位置、时间和播放状态     currentPos = (int) intentParams.getParam(KEY_POSITION); 
  17.     currentTime = (int) intentParams.getParam(KEY_CURRENT_TIME);     Object object = intentParams.getParam(KEY_PLAY_STATE); 
  18.   
  19.     if (object instanceof String) {         isPlaying = Boolean.parseBoolean((String) object); 
  20.     }     isInteractionPlay = true; 
  21.     LogUtil.info(TAG, "onRestoredata:" + currentTime);     return true; 
  22. }  
  23. @Override public void onCompleteContinuation(int i) { 
  24.     terminate(); } 

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

实现 Slider.ValueChangedListener 接口方法

  1.  private class ValueChangedListenerImpl implements Slider.ValueChangedListener { 
  2.     @Override     public void onProgressUpdated(Slider slider, int progress, boolean fromUser) { 
  3.         currentTime = progress;     } 
  4.      @Override 
  5.     public void onTouchStart(Slider slider) {         LogUtil.debug(TAG, "onTouchStart"); 
  6.     }  
  7.     @Override     public void onTouchEnd(Slider slider) { 
  8.         LogUtil.debug(TAG, "onTouchEnd");         //快速更改播放进度 
  9.         playerManager.rewindTo(currentTime);         //当前播放时间 
  10.         currentTimeText.setText(getTime(currentTime));     } 

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

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

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

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

 3.6.onStart完成数据的初始化

  1. @Override public void onStart(Intent intent) { 
  2.     super.onStart(intent);     super.setUIContent(ResourceTable.Layout_main_ability_slice); 
  3.      initComponents(); 
  4.      initMedia(); 
  5.      updateUI(); 

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

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

  1.  
  2. private void initComponents() {     LogUtil.debug(TAG, "initComponents"); 
  3.     musicNameText = (Text) findComponentById(ResourceTable.Id_music_name);     currentTimeText = (Text) findComponentById(ResourceTable.Id_play_progress_time); 
  4.     totalTimeText = (Text) findComponentById(ResourceTable.Id_play_total_time);  
  5.     musicPosters = (Image) findComponentById(ResourceTable.Id_music_posters);  
  6.     musicPlayButton = (Image) findComponentById(ResourceTable.Id_music_play_btn);     findComponentById(ResourceTable.Id_remote_play).setClickedListener(this::continueAbility); 
  7.     findComponentById(ResourceTable.Id_music_play_prev_btn).setClickedListener(this::prevMusic);     findComponentById(ResourceTable.Id_music_play_next_btn).setClickedListener(this::nextMusic); 
  8.      musicPlayButton.setClickedListener(this::playOrPauseMusic); 
  9.      // 
  10.     slider = (Slider) findComponentById(ResourceTable.Id_play_progress_bar);     slider.setValueChangedListener(new ValueChangedListenerImpl()); 
  11. }  
  12. private void continueAbility(Component component) {     try { 
  13.         continueAbility();     } catch (IllegalStateException e) { 
  14.         LogUtil.info(TAG, e.getMessage());     } 
  15. }  
  16.  
  17. private void prevMusic(Component component) {     currentPos = currentPos == 0 ? 1 : 0; 
  18.     currentUri = musics[currentPos];     // 
  19.     playerManager.switchMusic(currentUri);     //总时长 
  20.     totalTime=playerManager.getTotalTime(); } 
  21.   private void nextMusic(Component component) { 
  22.     currentPos = currentPos == 0 ? 1 : 0;     currentUri = musics[currentPos]; 
  23.     //切换音乐     playerManager.switchMusic(currentUri); 
  24.     //总时长     totalTime=playerManager.getTotalTime(); 
  25. }  
  26.  
  27. private void playOrPauseMusic(Component component) {     // 
  28.     playOrPause(); } 
  29.   
  30. private void playOrPause() {  
  31.     LogUtil.debug(TAG, "playOrPause,playerManager:"+playerManager);     try { 
  32.         //         if (playerManager.isPlaying()) { 
  33.             LogUtil.debug(TAG, "playOrPause pause");             playerManager.pause(); 
  34.         }else{             //设置资源 
  35.             playerManager.setResource(currentUri);             //设置进度 
  36.             playerManager.rewindTo(currentTime);             playerManager.play(); 
  37.             LogUtil.debug(TAG, "playOrPause play");         } 
  38.     } catch (Exception e) {         LogUtil.error(TAG, "playOrPause"); 
  39.         e.printStackTrace();     } 

3.7.初始化媒体对象

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

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

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

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

  1.  
  2. private void updateUI() {     LogUtil.debug(TAG, "updateUI"); 
  3.     //海报     musicPosters.setPixelMap(posters[currentPos]); 
  4.     //当前时间和总时长     currentTimeText.setText(getTime(currentTime)); 
  5.     totalTimeText.setText(getTime(playerManager.getTotalTime()));     //播放进度 
  6.     slider.setMaxValue(playerManager.getTotalTime());     slider.setProgressValue(currentTime); 
  7.      //总时长 
  8.     totalTime=playerManager.getTotalTime();  
  9.     //远端迁移恢复     if (isInteractionPlay) { 
  10.         LogUtil.debug(TAG, "remotePlay,rewindTo:"+currentTime);         playerManager.rewindTo(currentTime); 
  11.         if (!isPlaying) {             return; 
  12.         }         //播放 
  13.         playerManager.play();     } 
问题总结

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

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

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

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

附件直接下载DistributedMusicPlayer.zip

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

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

https://harmonyos.51cto.com



 

转载请注明:文章转载自 www.051e.com
本文地址:http://www.051e.com/it/796769.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 ©2023-2025 051e.com

ICP备案号:京ICP备12030808号