您现在的位置是:亿华云 > 数据库
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)
上一篇: 二、如何选择合适的域名
下一篇: 以上的就是为大家介绍的关于域名的详解
相关文章
- 4、说起来容易
- 来学习一下CSS中的宽高比,让 h5 开发更想你的夜!
- YouTube 推荐算法被曝倾向于潜在有害视频
- 两行 JS 代码实现页面横向滚动特效
- 域名资源有限,好域名更是有限,但机会随时都有,这取决于我们能否抓住机会。一般观点认为,国内域名注册太深,建议优先考虑外国注册人。外国注册人相对诚实,但价格差别很大,从几美元到几十美元不等。域名投资者应抓住机遇,尽早注册国外域名。
- 聊一聊逻辑是怎样炼成的?
- 「Webpack」从0到1学会 Code Splitting
- 经典算法:无序数组寻找第K大的数值
- ICANN 规章禁止转移已经被记录或者在60天前内转移的域名。
- 20个 Javascript 技巧,提高我们的摸鱼时间!