HarmongOS音乐播放器开发示例教程

  • 前言
  • 完整项目代码链接[GMusic-HarmongOS-Samples](https://github.com/SakurajimaMaii/GMusic-HarmongOS-Samples)欢迎⭐和fork
  • 对应BiliBili视频[鸿蒙开发教程之音乐软件](https://www.bilibili.com/video/BV1z5411M7WU)
  • 1. 所需知识和最终效果
  • 2. 资源获取
  • 2.1 权限申请
  • 2.1.1 在config.json文件中的“reqPermissions”字段中声明所需要的权限。
  • 2.1.2 在Ability中动态申请
  • 2.2 数据模型
  • 2.3 数据读取
  • 2.3.1 通过外部存储的Uri来获取对应的ResultSet
  • 2.3.2 通过ResultSet获取对应的音频对象
  • 3. UI布局
  • 3.1 主页面布局
  • 3.2 音乐布局
  • 4. 播放逻辑
  • 4.1 列表点击事件的设置
  • 4.2 底部播放控件的设置
  • 4.2.1 playMusic()函数
  • 4.2.2 lastMusic()函数
  • 4.2.3 nextMusic()函数
  • 4.2.4 play()函数


前言

完整项目代码链接GMusic-HarmongOS-Samples欢迎⭐和fork

对应BiliBili视频鸿蒙开发教程之音乐软件

1. 所需知识和最终效果

鸿蒙音乐播放器 Java 鸿蒙的音乐播放器_xml


鸿蒙音乐播放器 Java 鸿蒙的音乐播放器_鸿蒙音乐播放器 Java_02


2. 资源获取

2.1 权限申请

2.1.1 在config.json文件中的“reqPermissions”字段中声明所需要的权限。

鸿蒙音乐播放器 Java 鸿蒙的音乐播放器_鸿蒙音乐播放器 Java_03

2.1.2 在Ability中动态申请

  1. 申请对应权限
@Override
public void onStart(Intent intent) {
    super.onStart(intent);
    super.setMainRoute(MainAbilitySlice.class.getName());

    if (verifySelfPermission("ohos.permission.READ_MEDIA") != IBundleManager.PERMISSION_GRANTED) {
        if (canRequestPermission("ohos.permission.READ_MEDIA")) {
            // 是否可以申请弹框授权(首次申请或者用户未选择禁止且不再提示)
            requestPermissionsFromUser(
                    new String[] { "ohos.permission.READ_MEDIA" } , MY_PERMISSIONS_REQUEST_RW);
        } else {
            new ToastDialog(this).setText("需要授予应用读取存储权限").setAlignment(LayoutAlignment.CENTER).show();
        }
    }
}
  1. 检测申请结果
@Override
public void onRequestPermissionsFromUserResult (int requestCode, String[] permissions, int[] grantResults) {
    if (requestCode == MY_PERMISSIONS_REQUEST_RW) {// 匹配requestPermissions的requestCode
        if (grantResults.length > 0
                && grantResults[0] == IBundleManager.PERMISSION_GRANTED) {
            new ToastDialog(this).setText("所有权限已经被授予").setAlignment(LayoutAlignment.CENTER).show();
        } else {
            new ToastDialog(this).setText("所有权限已经被拒绝").setAlignment(LayoutAlignment.CENTER).show();
        }
    }
}

2.2 数据模型

声明对应的音乐对象

public class MusicBean {
    private int id;
    private String title;
    private String song;
    private String data;
    private String duration;
    private String artist;
    private String album;

    public MusicBean() {
    }

    /**
     * @param id       music_id
     * @param title    music_name
     * @param song
     * @param data     music_path
     * @param duration music_duration
     * @param artist music_artist
     */
    public MusicBean(int id, String title, String song, String data, String duration,String artist,String album) {
        this.id = id;
        this.title = title;
        this.song = song;
        this.data = data;
        this.duration = duration;
        this.artist = artist;
        this.album = album;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getSong() {
        return song;
    }

    public void setSong(String song) {
        this.song = song;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public String getDuration() {
        return duration;
    }

    public void setDuration(String duration) {
        this.duration = duration;
    }

    public String getArtist() {
        return artist;
    }

    public void setArtist(String artist) {
        this.artist = artist;
    }

    public String getAlbum() {
        return album;
    }

    public void setAlbum(String album) {
        this.album = album;
    }
}

2.3 数据读取

2.3.1 通过外部存储的Uri来获取对应的ResultSet

/**
* @param context Context
* @return
* If you want to get information about AVStorage.Audio.Media, please refer to:
* https://developer.harmonyos.com/cn/docs/documentation/doc-references/avstorage_audio_media-0000001054678942
* If you want to get information about this function, please refer to:
* https://developer.harmonyos.com/cn/docs/documentation/doc-guides/tv-media-playback-0000001050714866
*/
private ResultSet queryAvStore(Context context) {
    ResultSet resultSet = null;
    DataAbilityHelper helper = DataAbilityHelper.creator(context);
    try {
        resultSet = helper.query(AVStorage.Audio.Media.EXTERNAL_DATA_ABILITY_URI, null, null);
    } catch (DataAbilityRemoteException e) {
        e.printStackTrace();
    }
    return resultSet;
}

2.3.2 通过ResultSet获取对应的音频对象

关于 artistalbum 字段的获取可以参考文章HarmongOS音频开发之音频信息获取(以获取艺术家为例)

/**
* @param context Context
* @return
* get playlist
* If you want to get information about AVStorage.AVBaseColumns.ID or others, please refer to:
* https://developer.harmonyos.com/cn/docs/documentation/doc-references/avstorage_avbasecolumns-0000001054358919#ZH-CN_TOPIC_0000001054358919__DATA
*/
private List<MusicBean> getMusicBeanList(Context context) {
    ResultSet resultSet = queryAvStore(context);
    List<MusicBean> musicBeans = new ArrayList<>();
    while (resultSet.goToNextRow()) {
        MusicBean musicBean = new MusicBean();
        musicBean.setId(resultSet.getInt(resultSet.getColumnIndexForName(AVStorage.AVBaseColumns.ID)));
        musicBean.setData(resultSet.getString(resultSet.getColumnIndexForName(AVStorage.AVBaseColumns.DATA)));
        musicBean.setTitle(resultSet.getString(resultSet.getColumnIndexForName(AVStorage.AVBaseColumns.TITLE)));
        musicBean.setDuration(new SimpleDateFormat("mm:ss").format(new Date(resultSet.getLong(resultSet.getColumnIndexForName(AVStorage.AVBaseColumns.DURATION)))));
        musicBean.setSong(resultSet.getString(resultSet.getColumnIndexForName(AVStorage.AVBaseColumns.DISPLAY_NAME)));
        musicBean.setArtist(resultSet.getString(resultSet.getColumnIndexForName("artist")));
        musicBean.setAlbum(resultSet.getString(resultSet.getColumnIndexForName("album")));
        musicBeans.add(musicBean);
    }
    return musicBeans;
}

3. UI布局

3.1 主页面布局

主页面布局主要采用了DirectionalLayout布局,如果你想获取对应的布局信息,可以参考链接DirectionalLayout布局,以下是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">
    <ListContainer
        ohos:id="$+id:music_bean_lc"
        ohos:height="0vp"
        ohos:width="match_parent"
        ohos:orientation="vertical"
        ohos:weight="1"
        ohos:background_element="$media:background"/>
    <DirectionalLayout
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:background_element="#E6000000"
        ohos:orientation="horizontal"
        ohos:padding="5vp">
        <DirectionalLayout
            ohos:height="match_content"
            ohos:width="0vp"
            ohos:weight="1"
            ohos:orientation="vertical"
            ohos:layout_alignment="vertical_center">
            <Text
                ohos:id="$+id:bottom_music_name_tv"
                ohos:height="match_content"
                ohos:width="match_content"
                ohos:text="$string:music_name"
                ohos:text_size="20fp"
                ohos:text_color="$color:color_white"/>
            <Text
                ohos:id="$+id:bottom_music_artists_tv"
                ohos:height="match_content"
                ohos:width="match_content"
                ohos:text="$string:music_artist"
                ohos:text_size="10fp"
                ohos:text_color="$color:color_white"/>
        </DirectionalLayout>
        <Button
            ohos:id="$+id:bottom_last_btn"
            ohos:height="50vp"
            ohos:width="50vp"
            ohos:margin="10vp"
            ohos:background_element="$graphic:ic_last"/>
        <Button
            ohos:id="$+id:bottom_play_btn"
            ohos:height="50vp"
            ohos:width="50vp"
            ohos:margin="10vp"
            ohos:background_element="$graphic:ic_play"/>
        <Button
            ohos:id="$+id:bottom_next_btn"
            ohos:height="50vp"
            ohos:width="50vp"
            ohos:margin="10vp"
            ohos:background_element="$graphic:ic_next"/>
    </DirectionalLayout>
</DirectionalLayout>

3.2 音乐布局

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_content"
    ohos:width="match_parent"
    ohos:padding="10vp"
    ohos:orientation="horizontal">
    <DirectionalLayout
        ohos:height="match_content"
        ohos:width="0vp"
        ohos:weight="3"
        ohos:orientation="vertical">
        <Text
            ohos:id="$+id:music_name_tv"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="$string:music_name"
            ohos:text_size="20fp"
            ohos:text_color="$color:color_white"/>
        <Text
            ohos:id="$+id:music_artists_tv"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="$string:music_artist"
            ohos:text_size="10fp"
            ohos:text_color="$color:color_white"/>
    </DirectionalLayout>
    <Text
        ohos:id="$+id:music_duration_tv"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text="$string:music_duration"
        ohos:text_size="10fp"
        ohos:layout_alignment="right|bottom"
        ohos:text_color="$color:color_white"/>
</DirectionalLayout>

4. 播放逻辑

4.1 列表点击事件的设置

listContainer.setItemClickedListener((listContainer1, component, i, l) -> {
    //get music object
    MusicBean item = (MusicBean) listContainer1.getItemProvider().getItem(i);
    //update some information
    currentPlayMusicPos = i;
    musicNameTv.setText(item.getTitle());
    musicArtistTv.setText(item.getArtist()+"-"+item.getAlbum());
    //update the button image
    //Regarding this execution statement, I hope you refer to the link below
    //
    playBtn.setBackground(new VectorElement(this,ResourceTable.Graphic_ic_pause));
    //play the music
    try {
        if (PlayManager.getInstance().play(new Source(DataAbilityHelper
                                .creator(component.getContext())
                                .openFile(Uri.appendEncodedPathToUri(AVStorage.Audio.Media.EXTERNAL_DATA_ABILITY_URI, String.valueOf(item.getId())), "r")),0)) {
            HiLog.info(hiLogLabel, "播放成功");
        } else {
            HiLog.info(hiLogLabel, "播放失败");
        }
    } catch (DataAbilityRemoteException | FileNotFoundException e) {
        e.printStackTrace();
        HiLog.info(hiLogLabel, "播放失败");
    }
});

4.2 底部播放控件的设置

在这里用到的PlayManager() 类你可以在链接音乐播放器开发指导
中获取详情

playBtn.setClickedListener(component -> playMusic(component.getContext()));
lastBtn.setClickedListener(component -> lastMusic(component.getContext()));
nextBtn.setClickedListener(component -> nextMusic(component.getContext()));

4.2.1 playMusic()函数

private void playMusic(Context context){
    if(currentPlayMusicPos == -1){
        new ToastDialog(context).setText("请从播放列表中选择歌曲").setAlignment(LayoutAlignment.CENTER).show();
    }else{
        if(PlayManager.getInstance().isPlaying()){
            PlayManager.getInstance().pause();
            playBtn.setBackground(new VectorElement(this,ResourceTable.Graphic_ic_play));
        }else{
            MusicBean item = list.get(currentPlayMusicPos);
            play(item,context,PlayManager.getInstance().getAudioCurrentPosition());
        }
    }
}

4.2.2 lastMusic()函数

private void lastMusic(Context context){
    currentPlayMusicPos--;
    if (currentPlayMusicPos < 0) {
        currentPlayMusicPos = list.size() - 1;
    }
    MusicBean item = list.get(currentPlayMusicPos);
    play(item,context,0);
}

4.2.3 nextMusic()函数

private void lastMusic(Context context){
    currentPlayMusicPos--;
    if (currentPlayMusicPos < 0) {
        currentPlayMusicPos = list.size() - 1;
    }
    MusicBean item = list.get(currentPlayMusicPos);
    play(item,context,0);
}

4.2.4 play()函数

private void play(MusicBean musicBean,Context context,int time){
    musicNameTv.setText(musicBean.getTitle());
    musicArtistTv.setText(musicBean.getArtist()+"-"+musicBean.getAlbum());
    try {
        if (PlayManager.getInstance().play(new Source(DataAbilityHelper
                .creator(context)
                .openFile(Uri.appendEncodedPathToUri(AVStorage.Audio.Media.EXTERNAL_DATA_ABILITY_URI, String.valueOf(musicBean.getId())),"r")),time)) {
            HiLog.info(hiLogLabel, "播放成功");
            playBtn.setBackground(new VectorElement(this,ResourceTable.Graphic_ic_pause));
        } else {
            HiLog.info(hiLogLabel, "播放失败");
        }
    } catch (DataAbilityRemoteException | FileNotFoundException e) {
        e.printStackTrace();
        HiLog.info(hiLogLabel, "播放失败");
    }
}