一、前言
在本教程中,我们将使用 鸿蒙系统(HarmonyOS) 实现一个简单的音乐播放器应用。该播放器支持基本功能,如播放、暂停、上一首、下一首,具有良好的用户界面,风格模仿网易云音乐。
二、功能特点
- 播放功能:实现播放、暂停、上一首、下一首切换。
- 播放进度:显示当前播放时间及总时长,并支持进度条拖动。
- 音量调节:通过滑块控制播放音量。
- UI 优化:采用简洁风格布局,用户体验友好。
三、开发环境
- 开发工具:DevEco Studio
- 鸿蒙版本:API Version 8+
- 设备要求:鸿蒙模拟器或鸿蒙支持设备
四、具体步骤
步骤 1:添加依赖
在 build.gradle
文件中,添加媒体和布局依赖:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.so'])
implementation 'ohos.media:media:1.0.0'
implementation 'ohos.agp:agp:1.0.0'
}
同时,配置 config.json
文件,声明所需权限:
"permissions": [
"ohos.permission.MEDIA_LOCATION",
"ohos.permission.READ_MEDIA"
]
步骤 2:完整代码
2.1 主活动文件:MainAbility.java
主活动控制界面显示和用户交互逻辑。
package com.example.musicplayer;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.agp.components.Slider;
import ohos.agp.components.Text;
import ohos.agp.components.Button;
public class MainAbility extends Ability {
private PlayerController playerController;
private PlaylistManager playlistManager;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
setUIContent(ResourceTable.Layout_ability_main);
playlistManager = new PlaylistManager();
playerController = new PlayerController(this, playlistManager);
setupUI();
}
private void setupUI() {
// 界面组件绑定
Text trackName = (Text) findComponentById(ResourceTable.Id_track_name);
Text currentTime = (Text) findComponentById(ResourceTable.Id_current_time);
Text totalTime = (Text) findComponentById(ResourceTable.Id_total_time);
Button playButton = (Button) findComponentById(ResourceTable.Id_button_play);
Button nextButton = (Button) findComponentById(ResourceTable.Id_button_next);
Button prevButton = (Button) findComponentById(ResourceTable.Id_button_previous);
Slider volumeSlider = (Slider) findComponentById(ResourceTable.Id_volume_slider);
Slider progressSlider = (Slider) findComponentById(ResourceTable.Id_progress_slider);
// 绑定事件
playButton.setClickedListener(component -> playerController.togglePlayPause());
nextButton.setClickedListener(component -> playerController.playNextTrack());
prevButton.setClickedListener(component -> playerController.playPreviousTrack());
// 更新播放进度和时间
playerController.setTrackChangeListener((name, duration) -> {
trackName.setText(name);
totalTime.setText(formatTime(duration));
});
playerController.setTimeUpdateListener((current, duration) -> {
currentTime.setText(formatTime(current));
progressSlider.setProgress((int) (current * 100 / duration));
});
// 音量控制
volumeSlider.setValueChangedListener((slider, value, fromUser) -> {
if (fromUser) playerController.setVolume(value / 100.0f);
});
// 播放进度控制
progressSlider.setValueChangedListener((slider, value, fromUser) -> {
if (fromUser) playerController.seekTo(value / 100.0f);
});
}
private String formatTime(long timeInMs) {
int minutes = (int) (timeInMs / 60000);
int seconds = (int) ((timeInMs % 60000) / 1000);
return String.format("%02d:%02d", minutes, seconds);
}
}
2.2 播放器控制:PlayerController.java
播放器控制逻辑包括播放、暂停、音量和进度控制。
package com.example.musicplayer;
import ohos.app.Context;
import ohos.media.audio.AudioPlayer;
import java.util.Timer;
import java.util.TimerTask;
public class PlayerController {
private final AudioPlayer audioPlayer;
private final PlaylistManager playlistManager;
private final Timer timer;
private TimeUpdateListener timeUpdateListener;
private TrackChangeListener trackChangeListener;
public PlayerController(Context context, PlaylistManager playlistManager) {
this.audioPlayer = new AudioPlayer(context);
this.playlistManager = playlistManager;
this.timer = new Timer();
setupPlayer();
startUpdatingTime();
}
private void setupPlayer() {
audioPlayer.setPlayerStateChangedListener((player, state) -> {
if (state == AudioPlayer.PlayerState.PLAYING) {
notifyTrackChange();
}
});
}
public void playNextTrack() {
playlistManager.nextTrack();
playCurrentTrack();
}
public void playPreviousTrack() {
playlistManager.previousTrack();
playCurrentTrack();
}
public void togglePlayPause() {
if (audioPlayer.isPlaying()) {
audioPlayer.pause();
} else {
playCurrentTrack();
}
}
private void playCurrentTrack() {
audioPlayer.setSource(playlistManager.getCurrentTrack());
audioPlayer.prepare();
}
public void setVolume(float volume) {
audioPlayer.setVolume(volume);
}
public void seekTo(float progress) {
long position = (long) (audioPlayer.getDuration() * progress);
audioPlayer.seekTo(position);
}
private void startUpdatingTime() {
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (timeUpdateListener != null && audioPlayer.isPlaying()) {
timeUpdateListener.onTimeUpdate(audioPlayer.getCurrentPosition(), audioPlayer.getDuration());
}
}
}, 0, 1000);
}
public void setTimeUpdateListener(TimeUpdateListener listener) {
this.timeUpdateListener = listener;
}
public void setTrackChangeListener(TrackChangeListener listener) {
this.trackChangeListener = listener;
}
private void notifyTrackChange() {
if (trackChangeListener != null) {
trackChangeListener.onTrackChange(playlistManager.getCurrentTrackName(), audioPlayer.getDuration());
}
}
public interface TimeUpdateListener {
void onTimeUpdate(long currentTime, long totalTime);
}
public interface TrackChangeListener {
void onTrackChange(String trackName, long duration);
}
}
2.3 播放器列表:PlaylistManager.java
package com.example.musicplayer;
import java.util.ArrayList;
import java.util.List;
public class PlaylistManager {
private final List<String> playlist;
private int currentIndex;
public PlaylistManager() {
this.playlist = new ArrayList<>();
loadPlaylist();
}
private void loadPlaylist() {
playlist.add("/data/accounts/account_0/appdata/com.example.musicplayer/res/raw/song1.mp3");
playlist.add("/data/accounts/account_0/appdata/com.example.musicplayer/res/raw/song2.mp3");
playlist.add("/data/accounts/account_0/appdata/com.example.musicplayer/res/raw/song3.mp3");
}
public String getCurrentTrack() {
return playlist.get(currentIndex);
}
public String getCurrentTrackName() {
return "歌曲:" + playlist.get(currentIndex).substring(playlist.get(currentIndex).lastIndexOf("/") + 1);
}
public void nextTrack() {
currentIndex = (currentIndex + 1) % playlist.size();
}
public void previousTrack() {
currentIndex = (currentIndex - 1 + playlist.size()) % playlist.size();
}
}
2.4 布局文件:ability_main.xml
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:width="match_parent"
ohos:height="match_parent"
ohos:orientation="vertical">
<Text
ohos:id="$+id:track_name"
ohos:width="match_parent"
ohos:height="match_content"
ohos:text="当前无音乐播放"
ohos:text_size="18fp"
ohos:margin="16vp"
ohos:text_alignment="center"/>
<Text
ohos:id="$+id:current_time"
ohos:width="match_content"
ohos:height="match_content"
ohos:text="00:00"
ohos:margin="8vp"/>
<Slider
ohos:id="$+id:progress_slider"
ohos:width="match_parent"
ohos:height="50vp"/>
<Text
ohos:id="$+id:total_time"
ohos:width="match_content"
ohos:height="match_content"
ohos:text="00:00"
ohos:margin="8vp"/>
<DirectionalLayout
ohos:width="match_parent"
ohos:height="match_content"
ohos:orientation="horizontal"
ohos:alignment="center">
<Button
ohos:id="$+id:button_previous"
ohos:width="100vp"
ohos:height="50vp"
ohos:text="上一首"/>
<Button
ohos:id="$+id:button_play"
ohos:width="100vp"
ohos:height="50vp"
ohos:text="播放/暂停"/>
<Button
ohos:id="$+id:button_next"
ohos:width="100vp"
ohos:height="50vp"
ohos:text="下一首"/>
</DirectionalLayout>
<Slider
ohos:id="$+id:volume_slider"
ohos:width="match_parent"
ohos:height="50vp"
ohos:progress="50"/>
</DirectionalLayout>
五、运行效果
在完成代码编写后,运行此鸿蒙音乐播放器应用,可以实现以下效果:
-
主界面展示:
- 显示当前播放的歌曲名称、播放时间(当前时间与总时间)。
- 显示进度条,可以根据歌曲进度实时更新,支持拖动调整进度。
- 显示音量滑块,控制音量大小。
-
基本控制功能:
- 点击播放/暂停按钮时,播放器会暂停或继续播放。
- 点击上一首或下一首按钮时,播放器会切换到上一首或下一首歌曲。
- 音量通过滑块调整,调整后的音量实时生效。
-
音频播放控制:
- 播放器能够正常读取
.mp3
文件并进行播放。 - 播放进度和时间显示精确,支持拖动进度条控制播放进度。
- 播放器能够正常读取
界面截图示例
主界面:
- 中部有歌曲名和歌手名称
- 下方有播放、暂停、上一首、下一首按钮,音量滑块位于界面底部。
歌词界面:
- 顶部展示歌曲名和歌手信息。
- 中部展示歌曲歌词。
- 下方有播放、暂停、上一首、下一首按钮,音量滑块位于界面底部。
六、常见报错及解决方案
在开发和运行过程中,可能会遇到以下一些常见问题:
1. 找不到音频文件
错误信息:FileNotFoundException: Could not find the file ...
解决方案:确保音频文件已经正确放入项目的 resources/raw
目录中,并且文件路径在代码中正确配置。
- 例如,检查
playlistManager.addTrack
中路径是否正确设置:playlist.add("/data/accounts/account_0/appdata/com.example.musicplayer/res/raw/song1.mp3");
2. 权限问题
错误信息:Permission denied
或无法访问媒体文件。
解决方案:确保已经在 config.json
文件中声明了相关权限,如:
"permissions": [
"ohos.permission.MEDIA_LOCATION",
"ohos.permission.READ_MEDIA"
]
如果音频文件未能读取或访问,确认模拟器或设备是否已授予这些权限。
3. UI 显示异常
错误信息:UI 布局未能正确显示或响应。
解决方案:确保 ability_main.xml
中的布局组件已正确配置,并且界面组件的 ID 匹配代码中的 findComponentById
方法。
- 确保每个组件的
width
和height
设置正确,避免出现元素重叠或不显示的情况。
4. 播放器未响应播放操作
错误信息:点击播放按钮无反应。
解决方案:检查 audioPlayer.setSource
和 audioPlayer.prepare()
是否成功调用,并确保播放器资源路径正确。
- 调试时可以打印日志,确认播放器是否成功加载并开始播放。
audioPlayer.setSource(playlistManager.getCurrentTrack()); audioPlayer.prepare(); Log.info("Player", "Track prepared: " + playlistManager.getCurrentTrack());
5. 内存泄漏或资源未释放
错误信息:程序运行时间过长时,可能出现内存占用过高。
解决方案:确保 AudioPlayer
在不需要时被正确释放。可以在活动销毁时调用 audioPlayer.release()
。
@Override
protected void onStop() {
super.onStop();
audioPlayer.release();
}
七、总结
在本教程中,我们使用鸿蒙系统(HarmonyOS)开发了一个简单的音乐播放器,涵盖了基本的音频播放功能,如播放、暂停、上一首、下一首、音量控制、播放进度等功能。通过学习和实现这些功能,能够帮助开发者熟悉鸿蒙系统的 音频播放、UI 设计 和 事件响应 机制。
主要学习点:
- 鸿蒙音频播放 API:通过
AudioPlayer
实现音频的加载、播放、暂停和进度控制。 - UI 设计与响应:通过
Button
、Slider
等组件设计了播放器的用户界面,并响应用户交互。 - 事件驱动编程:通过事件监听和回调机制实现动态更新界面状态,如播放进度、音量控制等。
未来改进:
- 支持更多格式的音频文件:目前我们只支持
.mp3
格式,未来可以添加对.wav
、.flac
等格式的支持。 - 实现播放列表功能:目前播放器只能播放单曲,未来可以添加播放列表功能,支持用户选择不同的歌曲进行播放。
- 增强用户体验:可以进一步优化 UI 设计,例如添加动画效果、歌曲封面显示等,提升整体用户体验。