【中工开发者】基于鸿蒙系统开发简单音乐播放器

一、前言

在本教程中,我们将使用 鸿蒙系统(HarmonyOS) 实现一个简单的音乐播放器应用。该播放器支持基本功能,如播放、暂停、上一首、下一首,具有良好的用户界面,风格模仿网易云音乐。


二、功能特点

  1. 播放功能:实现播放、暂停、上一首、下一首切换。
  2. 播放进度:显示当前播放时间及总时长,并支持进度条拖动。
  3. 音量调节:通过滑块控制播放音量。
  4. 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>

五、运行效果

在完成代码编写后,运行此鸿蒙音乐播放器应用,可以实现以下效果:

  1. 主界面展示

    • 显示当前播放的歌曲名称、播放时间(当前时间与总时间)。
    • 显示进度条,可以根据歌曲进度实时更新,支持拖动调整进度。
    • 显示音量滑块,控制音量大小。
  2. 基本控制功能

    • 点击播放/暂停按钮时,播放器会暂停或继续播放。
    • 点击上一首或下一首按钮时,播放器会切换到上一首或下一首歌曲。
    • 音量通过滑块调整,调整后的音量实时生效。
  3. 音频播放控制

    • 播放器能够正常读取 .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 方法。

  • 确保每个组件的 widthheight 设置正确,避免出现元素重叠或不显示的情况。

4. 播放器未响应播放操作

错误信息:点击播放按钮无反应。

解决方案:检查 audioPlayer.setSourceaudioPlayer.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 设计事件响应 机制。

主要学习点:

  1. 鸿蒙音频播放 API:通过 AudioPlayer 实现音频的加载、播放、暂停和进度控制。
  2. UI 设计与响应:通过 ButtonSlider 等组件设计了播放器的用户界面,并响应用户交互。
  3. 事件驱动编程:通过事件监听和回调机制实现动态更新界面状态,如播放进度、音量控制等。

未来改进:

  1. 支持更多格式的音频文件:目前我们只支持 .mp3 格式,未来可以添加对 .wav.flac 等格式的支持。
  2. 实现播放列表功能:目前播放器只能播放单曲,未来可以添加播放列表功能,支持用户选择不同的歌曲进行播放。
  3. 增强用户体验:可以进一步优化 UI 设计,例如添加动画效果、歌曲封面显示等,提升整体用户体验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值