
想了解更多内容,请访问:
51CTO和华为官方合作共建的鸿蒙技术社区
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播放器回调类
- 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(); }
- }
- public void pause() {
- if (!musicPlayer.pause()) { LogUtil.info(TAG, "pause fail");
- return; }
- //停止计时 finishTask();
- // handler.sendEvent(PLAY_STATE_PAUSE);
- }
- public void switchMusic(String uri) { currentUri = uri;
- //设置资源 setResource(currentUri);
- //播放 play();
- }
- public void rewindTo(int currentTime) { musicPlayer.rewindTo(currentTime * 1000);
- }
- 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资源被设置时被调用
- 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"); }
- @Override
- public void onMessage(int type, int extra) { LogUtil.info(TAG, "onMessage " + type + "-" + extra);
- }
- @Override
- public void onError(int errorType, int errorCode) { LogUtil.info(TAG, "onError " + errorType + "-" + errorCode);
- }
- @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);
- }
- @Override
- public void onRewindToComplete() { LogUtil.info(TAG, "onRewindToComplete");
- }
- @Override
- public void onBufferingChange(int percent) { LogUtil.info(TAG, "onBufferingChange:" + percent);
- }
- @Override
- public void onNewTimedmetaData(Player.MediaTimedmetaData mediaTimedmetaData) { LogUtil.info(TAG, "onNewTimedmetaData");
- }
- @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();
- }
初始化界面组件,实现对应按钮的监听事件
播放或暂停、上一首、下一首、迁移以及进度条的进度变化事件的监听
- 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()); }
- }
- private void prevMusic(Component component) { currentPos = currentPos == 0 ? 1 : 0;
- currentUri = musics[currentPos]; //
- playerManager.switchMusic(currentUri); //总时长
- totalTime=playerManager.getTotalTime(); }
- private void nextMusic(Component component) {
- currentPos = currentPos == 0 ? 1 : 0; currentUri = musics[currentPos];
- //切换音乐 playerManager.switchMusic(currentUri);
- //总时长 totalTime=playerManager.getTotalTime();
- }
- 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 = new WeakReference<>(this); //设置状态监听器 - playerManager.setPlayerStateListener(playerStateListener.get()); //初始化播放器信息
- playerManager.init(); LogUtil.debug(TAG, "initMedia FINISH");
- }
3.8.远端迁移后恢复播放界面
恢复播放器的播放进度、播放状态、海报、当前时间和总时长、slider播放进度
问题总结
- 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
想了解更多内容,请访问:
51CTO和华为官方合作共建的鸿蒙技术社区
https://harmonyos.51cto.com