wxj #8

Merged
p8txnwle5 merged 37 commits from wxj into develop 1 year ago

@ -1,12 +1,26 @@
package com.example.musicplayer.event;
// 该类的文档注释,提供了类的作者、创建时间和类的基本描述信息
/**
* <pre>
* author :
* time : 2019/07/19
* desc :
*
*
*
*
*
* ID
* </pre>
*/
// 定义一个名为 AlbumCollectionEvent 的公共类
public class AlbumCollectionEvent {
}
// 目前该类为空,没有任何成员变量和方法,可在后续开发中添加。
// 例如,可能会添加以下成员变量:
// private int albumId; 用于存储收藏的专辑的唯一标识符
// private Date collectionTime; 用于存储用户收藏该专辑的时间
// 可能会添加以下方法:
// public void notifyUI() { } 用于通知用户界面更新收藏状态
// public void syncWithServer() { } 用于将收藏信息同步到服务器
}

@ -1,5 +1,6 @@
package com.example.musicplayer.event;
// 导入 DownloadInfo 类,因为 DownloadEvent 类中使用了该类
import com.example.musicplayer.entiy.DownloadInfo;
/**
@ -7,42 +8,61 @@ import com.example.musicplayer.entiy.DownloadInfo;
* author :
* time : 2019/09/16
* desc :
*
*
*
* DownloadEvent
*
* </pre>
*/
public class DownloadEvent {
private int downloadStatus;//下载的状态
// 存储下载的状态,可用于表示下载是否开始、正在进行、完成、失败等不同状态
private int downloadStatus;
// 存储下载信息,如文件大小、下载速度、已下载量等
private DownloadInfo downloadInfo;
// 存储在下载列表或队列中的位置,方便进行位置相关的操作,比如更新 UI 中的下载进度条位置
private int position;
// 构造函数,仅接收下载状态作为参数
public DownloadEvent(int status){
// 将传入的状态存储到 downloadStatus 成员变量中
downloadStatus = status;
}
// 构造函数,接收下载状态和 DownloadInfo 对象作为参数
public DownloadEvent(int status, DownloadInfo downloadInfo){
// 将传入的状态存储到 downloadStatus 成员变量中
downloadStatus = status;
// 将传入的 DownloadInfo 对象存储到 downloadInfo 成员变量中
this.downloadInfo = downloadInfo;
}
// 构造函数,接收下载状态和位置作为参数
public DownloadEvent(int status,int position){
// 将传入的状态存储到 downloadStatus 成员变量中
downloadStatus = status;
// 将传入的位置存储到 position 成员变量中
this.position = position;
}
// 获取位置的方法
public int getPosition() {
return position;
}
// 设置位置的方法
public void setPosition(int position) {
this.position = position;
}
// 获取下载状态的方法
public int getDownloadStatus() {
return downloadStatus;
}
// 获取下载信息的方法
public DownloadInfo getDownloadInfo() {
return downloadInfo;
}
}
}

@ -5,8 +5,16 @@ package com.example.musicplayer.event;
* author :
* time : 2019/07/19
* desc :
*
*
*
*
*
*
*
* </pre>
*/
public class OnlineSongChangeEvent {
}
// 目前该类为空,可在后续开发中添加成员变量和方法。
}

@ -5,8 +5,15 @@ package com.example.musicplayer.event;
* author :
* time : 2019/07/20
* desc :
*
*
*
*
*
*
*
* </pre>
*/
public class OnlineSongErrorEvent {
}
// 目前该类为空,可在后续开发中添加成员变量和方法。
}

@ -5,8 +5,15 @@ package com.example.musicplayer.event;
* author :
* time : 2019/07/19
* desc :
*
*
*
*
*
*
*
* </pre>
*/
public class SongAlbumEvent {
}
// 目前该类为空,可在后续开发中添加成员变量和方法。
}

@ -5,16 +5,26 @@ package com.example.musicplayer.event;
* author :
* time : 2019/07/19
* desc :
*
*
*
*
*
*
* </pre>
*/
public class SongCollectionEvent {
// 存储歌曲的收藏状态true 表示已收藏false 表示未收藏
private boolean isLove;
// 构造函数,接收一个布尔值表示收藏状态
public SongCollectionEvent(boolean isLove){
// 将传入的收藏状态存储到 isLove 成员变量中
this.isLove = isLove;
}
// 获取歌曲的收藏状态的方法
public boolean isLove() {
return isLove;
}
}
}

@ -5,8 +5,16 @@ package com.example.musicplayer.event;
* author :
* time : 2019/09/18
* desc :
*
*
*
*
*
*
*
*
* </pre>
*/
public class SongDownloadedEvent {
// 该类目前为空,后续可根据具体的需求添加成员变量和方法。
}

@ -5,8 +5,15 @@ package com.example.musicplayer.event;
* author :
* time : 2019/07/19
* desc :
*
*
*
*
*
*
*
* </pre>
*/
public class SongHistoryEvent {
// 目前该类为空,后续可根据具体需求添加成员变量和方法。
}

@ -5,20 +5,31 @@ package com.example.musicplayer.event;
* author :
* time : 2019/09/20
* desc :
*
*
*
*
* "type"
*
* "type" 便
* </pre>
*/
public class SongListNumEvent {
// 存储歌曲列表数量变化的类型,可用于表示数量的增减及变化量
private int type;
// 构造函数,接收一个整数表示歌曲列表数量变化的类型,并将其存储在 type 变量中
public SongListNumEvent(int type){
this.type = type;
}
// 获取歌曲列表数量变化类型的方法
public int getType() {
return type;
}
// 设置歌曲列表数量变化类型的方法
public void setType(int type) {
this.type = type;
}
}
}

@ -5,8 +5,16 @@ package com.example.musicplayer.event;
* author :
* time : 2019/07/19
* desc :
*
*
*
*
*
*
*
*
* </pre>
*/
public class SongLocalEvent {
}
// 目前该类为空,后续可根据具体需求添加成员变量和方法。
}

@ -5,8 +5,15 @@ package com.example.musicplayer.event;
* author :
* time : 2019/07/19
* desc :
*
*
*
*
*
*
*
* </pre>
*/
public class SongLocalSizeChangeEvent {
}
// 该类目前为空,可在后续开发中添加成员变量和方法。
}

@ -5,17 +5,28 @@ package com.example.musicplayer.event;
* author :
* time : 2019/07/19
* desc :
*
*
*
* songStatus
*
*
*
*
* </pre>
*/
public class SongStatusEvent {
// 存储歌曲的播放状态,不同的整数值可以表示不同的播放状态,
// 例如0 表示停止1 表示正在播放2 表示暂停等,具体可根据应用需求定义
private int songStatus;
// 构造函数,接收一个整数表示歌曲的播放状态,并将其存储在 songStatus 变量中
public SongStatusEvent(int songStatus){
this.songStatus = songStatus;
}
// 获取歌曲播放状态的方法
public int getSongStatus() {
return songStatus;
}
}
}

@ -25,92 +25,164 @@ import io.reactivex.Observable;
* author :
* time : 2019/07/15
* desc :
*
* NetworkHelperDbHelper PreferencesHelper
*
* NetworkHelperImplDbHelperImpl PreferencesHelperImpl
*
* </pre>
*/
public class DataModel implements NetworkHelper, DbHelper,PreferencesHelper {
public class DataModel implements NetworkHelper, DbHelper, PreferencesHelper {
// 声明一个 NetworkHelperImpl 类型的私有成员变量 mNetworkHelper用于存储网络操作的实现类对象
private NetworkHelperImpl mNetworkHelper;
// 声明一个 DbHelperImpl 类型的私有成员变量 mDbHelper用于存储数据库操作的实现类对象
private DbHelperImpl mDbHelper;
// 声明一个 PreferencesHelperImpl 类型的私有成员变量 mPreferencesHelper用于存储偏好设置操作的实现类对象
private PreferencesHelperImpl mPreferencesHelper;
public DataModel(NetworkHelperImpl networkHelper,DbHelperImpl dbHelper,PreferencesHelperImpl preferencesHelper){
// 构造函数,用于创建 DataModel 对象
// 参数 networkHelperNetworkHelperImpl 类型的对象,提供网络操作的具体实现
// 参数 dbHelperDbHelperImpl 类型的对象,提供数据库操作的具体实现
// 参数 preferencesHelperPreferencesHelperImpl 类型的对象,提供偏好设置操作的具体实现
public DataModel(NetworkHelperImpl networkHelper, DbHelperImpl dbHelper, PreferencesHelperImpl preferencesHelper) {
// 将传入的网络操作实现类对象存储在 mNetworkHelper 成员变量中
mNetworkHelper = networkHelper;
// 将传入的数据库操作实现类对象存储在 mDbHelper 成员变量中
mDbHelper = dbHelper;
// 将传入的偏好设置操作实现类对象存储在 mPreferencesHelper 成员变量中
mPreferencesHelper = preferencesHelper;
}
// 实现 NetworkHelper 接口中的 getAlbumSong 方法,用于获取专辑歌曲信息
// 参数 id专辑歌曲的唯一标识符用于指定要获取的专辑歌曲
// 返回值:一个 Observable<AlbumSong> 对象,用于异步获取专辑歌曲信息
@Override
public Observable<AlbumSong> getAlbumSong(String id) {
// 调用 mNetworkHelper 的 getAlbumSong 方法来执行获取专辑歌曲的操作
return mNetworkHelper.getAlbumSong(id);
}
// 实现 NetworkHelper 接口中的 search 方法,用于搜索歌曲
// 参数 seek搜索关键字用于指定搜索的内容
// 参数 offset搜索结果的偏移量用于分页等操作
// 返回值:一个 Observable<SearchSong> 对象,用于异步获取搜索歌曲的结果
@Override
public Observable<SearchSong> search(String seek, int offset) {
// 调用 mNetworkHelper 的 search 方法来执行歌曲搜索操作
return mNetworkHelper.search(seek, offset);
}
// 实现 NetworkHelper 接口中的 searchAlbum 方法,用于搜索专辑
// 参数 seek搜索关键字用于指定搜索的内容
// 参数 offset搜索结果的偏移量用于分页等操作
// 返回值:一个 Observable<Album> 对象,用于异步获取搜索专辑的结果
@Override
public Observable<Album> searchAlbum(String seek, int offset) {
// 调用 mNetworkHelper 的 searchAlbum 方法来执行专辑搜索操作
return mNetworkHelper.searchAlbum(seek, offset);
}
// 实现 NetworkHelper 接口中的 getLrc 方法,用于获取歌词
// 参数 seek搜索关键字用于指定搜索的歌词内容
// 返回值:一个 Observable<SongLrc> 对象,用于异步获取歌词的结果
@Override
public Observable<SongLrc> getLrc(String seek) {
// 调用 mNetworkHelper 的 getLrc 方法来执行获取歌词的操作
return mNetworkHelper.getLrc(seek);
}
// 实现 NetworkHelper 接口中的 getOnlineSongLrc 方法,用于获取网络歌曲的歌词
// 参数 songId歌曲的唯一标识符用于指定要获取歌词的网络歌曲
// 返回值:一个 Observable<OnlineSongLrc> 对象,用于异步获取网络歌曲歌词的结果
@Override
public Observable<OnlineSongLrc> getOnlineSongLrc(String songId) {
// 调用 mNetworkHelper 的 getOnlineSongLrc 方法来执行获取网络歌曲歌词的操作
return mNetworkHelper.getOnlineSongLrc(songId);
}
// 实现 NetworkHelper 接口中的 getSingerImg 方法,用于获取歌手头像
// 参数 singer歌手的名称用于指定要获取头像的歌手
// 返回值:一个 Observable<SingerImg> 对象,用于异步获取歌手头像的结果
@Override
public Observable<SingerImg> getSingerImg(String singer) {
// 调用 mNetworkHelper 的 getSingerImg 方法来执行获取歌手头像的操作
return mNetworkHelper.getSingerImg(singer);
}
// 实现 NetworkHelper 接口中的 getSongUrl 方法,用于获取歌曲的播放地址
// 参数 data与播放地址相关的数据可能包含歌曲的相关信息
// 返回值:一个 Observable<SongUrl> 对象,用于异步获取歌曲播放地址的结果
@Override
public Observable<SongUrl> getSongUrl(String data) {
// 调用 mNetworkHelper 的 getSongUrl 方法来执行获取歌曲播放地址的操作
return mNetworkHelper.getSongUrl(data);
}
// 实现 DbHelper 接口中的 insertAllAlbumSong 方法,用于将专辑歌曲列表插入数据库
// 参数 songList包含专辑歌曲信息的列表其中元素类型为 AlbumSong.DataBean.ListBean
@Override
public void insertAllAlbumSong(List<AlbumSong.DataBean.ListBean> songList) {
mDbHelper.insertAllAlbumSong(songList);
// 调用 mDbHelper 的 insertAllAlbumSong 方法将专辑歌曲列表插入数据库
mDbHelper.insertAllAlbumSong(songList);
}
// 实现 DbHelper 接口中的 getLocalMp3Info 方法,用于获取本地 MP3 信息
// 返回值:一个 List<LocalSong> 对象,存储本地歌曲信息
@Override
public List<LocalSong> getLocalMp3Info() {
// 调用 mDbHelper 的 getLocalMp3Info 方法来获取本地歌曲信息
return mDbHelper.getLocalMp3Info();
}
// 实现 DbHelper 接口中的 saveSong 方法,用于保存本地歌曲到数据库
// 参数 localSongs要保存的本地歌曲列表其中元素类型为 LocalSong
// 返回值:一个 boolean 值,表示保存操作是否成功
@Override
public boolean saveSong(List<LocalSong> localSongs) {
// 调用 mDbHelper 的 saveSong 方法来保存本地歌曲
return mDbHelper.saveSong(localSongs);
}
// 实现 DbHelper 接口中的 queryLove 方法,用于查询歌曲是否被收藏
// 参数 songId歌曲的唯一标识符用于指定要查询的歌曲
// 返回值:一个 boolean 值,表示歌曲是否被收藏
@Override
public boolean queryLove(String songId) {
// 调用 mDbHelper 的 queryLove 方法来查询歌曲是否被收藏
return mDbHelper.queryLove(songId);
}
// 实现 DbHelper 接口中的 saveToLove 方法,用于将歌曲收藏到数据库
// 参数 song要收藏的歌曲对象类型为 Song
// 返回值:一个 boolean 值,表示收藏操作是否成功
@Override
public boolean saveToLove(Song song) {
// 调用 mDbHelper 的 saveToLove 方法来执行收藏歌曲的操作
return mDbHelper.saveToLove(song);
}
// 实现 DbHelper 接口中的 deleteFromLove 方法,用于从收藏中删除歌曲
// 参数 songId歌曲的唯一标识符用于指定要删除收藏的歌曲
// 返回值:一个 boolean 值,表示删除操作是否成功
@Override
public boolean deleteFromLove(String songId) {
// 调用 mDbHelper 的 deleteFromLove 方法来执行从收藏中删除歌曲的操作
return mDbHelper.deleteFromLove(songId);
}
// 实现 PreferencesHelper 接口中的 setPlayMode 方法,用于设置播放模式
// 参数 mode表示播放模式的整数可用于设置不同的播放模式如顺序播放、随机播放等
@Override
public void setPlayMode(int mode) {
// 调用 mPreferencesHelper 的 setPlayMode 方法来设置播放模式
mPreferencesHelper.setPlayMode(mode);
}
// 实现 PreferencesHelper 接口中的 getPlayMode 方法,用于获取播放模式
// 返回值:一个整数,表示当前的播放模式
@Override
public int getPlayMode() {
// 调用 mPreferencesHelper 的 getPlayMode 方法来获取播放模式
return mPreferencesHelper.getPlayMode();
}
}
}

@ -12,20 +12,72 @@ import java.util.List;
* author :
* time : 2019/07/16
* desc :
*
*
*
*
*
* </pre>
*/
public interface DbHelper {
/**
*
* @param songList
*
*
* @param songList AlbumSong.DataBean.ListBean
*
* 便使
*/
void insertAllAlbumSong(List<AlbumSong.DataBean.ListBean> songList);
List<LocalSong> getLocalMp3Info(); //得到本地列表
boolean saveSong(List<LocalSong> localSongs);//将本地音乐放到数据库中
boolean queryLove(String songId);//从数据库查找是否为收藏歌曲
boolean saveToLove(Song song);//收藏歌曲
boolean deleteFromLove(String songId);//取消收藏歌曲
/**
*
* LocalSong
*
*
* @return
*/
List<LocalSong> getLocalMp3Info();
}
/**
*
*
* @param localSongs LocalSong
*
* 便
* @return true false
*/
boolean saveSong(List<LocalSong> localSongs);
/**
*
*
* @param songId
*
*
* @return true false
*/
boolean queryLove(String songId);
/**
*
*
* @param song Song
*
*
* @return true false
*/
boolean saveToLove(Song song);
/**
*
*
* @param songId
*
*
* @return true false
*/
boolean deleteFromLove(String songId);
}

@ -22,137 +22,206 @@ import java.util.List;
* author :
* time : 2019/07/16
* desc :
*
* DbHelper
* 使 LitePal
*
* </pre>
*/
public class DbHelperImpl implements DbHelper {
@Override
public void insertAllAlbumSong(List<AlbumSong.DataBean.ListBean> songList) {
// 先删除 OnlineSong 表中的所有数据
LitePal.deleteAll(OnlineSong.class);
// 遍历歌曲列表,将每首歌曲的信息存储到 OnlineSong 表中
for (int i = 0; i < songList.size(); i++) {
AlbumSong.DataBean.ListBean song = songList.get(i);
OnlineSong onlineSong = new OnlineSong();
// 设置歌曲的 ID从 1 开始递增
onlineSong.setId(i + 1);
// 设置歌曲名称
onlineSong.setName(song.getSongname());
// 设置歌手,取第一个歌手的名字
onlineSong.setSinger(song.getSinger().get(0).getName());
// 设置歌曲的唯一标识符
onlineSong.setSongId(song.getSongmid());
// 设置歌曲时长
onlineSong.setDuration(song.getInterval());
onlineSong.setPic(Api.ALBUM_PIC + song.getAlbummid()+Api.JPG);
// 设置歌曲封面图片的 URL
onlineSong.setPic(Api.ALBUM_PIC + song.getAlbummid() + Api.JPG);
// 暂时设置歌曲的 URL 和歌词为 null
onlineSong.setUrl(null);
onlineSong.setLrc(null);
// 保存歌曲信息到数据库
onlineSong.save();
}
}
@Override
public List<LocalSong> getLocalMp3Info() {
// 创建一个用于存储本地歌曲信息的列表
List<LocalSong> mp3InfoList = new ArrayList<>();
getFromDownloadFile(mp3InfoList); //从下载列表中读取歌曲文件
// 从下载列表中读取歌曲文件
getFromDownloadFile(mp3InfoList);
// 查询系统媒体库中的音频文件
Cursor cursor = App.getContext().getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null,
MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
// 遍历查询结果
for (int i = 0; i < cursor.getCount(); i++) {
cursor.moveToNext();
LocalSong mp3Info = new LocalSong();
// 获取音乐标题
String title = cursor.getString((cursor
.getColumnIndex(MediaStore.Audio.Media.TITLE)));//音乐标题
.getColumnIndex(MediaStore.Audio.Media.TITLE)));
// 获取艺术家
String artist = cursor.getString(cursor
.getColumnIndex(MediaStore.Audio.Media.ARTIST));//艺术家
.getColumnIndex(MediaStore.Audio.Media.ARTIST));
// 获取时长
long duration = cursor.getLong(cursor
.getColumnIndex(MediaStore.Audio.Media.DURATION));//时长
.getColumnIndex(MediaStore.Audio.Media.DURATION));
// 获取文件大小
long size = cursor.getLong(cursor
.getColumnIndex(MediaStore.Audio.Media.SIZE)); //文件大小
.getColumnIndex(MediaStore.Audio.Media.SIZE));
// 获取文件路径
String url = cursor.getString(cursor
.getColumnIndex(MediaStore.Audio.Media.DATA)); //文件路径
.getColumnIndex(MediaStore.Audio.Media.DATA));
// 获取是否为音乐的标记
int isMusic = cursor.getInt(cursor
.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC));//是否为音乐
if (isMusic != 0) {//只把音乐添加到集合当中
.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC));
// 只添加音乐文件到集合中
if (isMusic!= 0) {
// 对于文件大小大于 800KB 的文件进行处理
if (size > 1000 * 800) {
// 注释部分是切割标题,分离出歌曲名和歌手 (本地媒体库读取的歌曲信息不规范)
// 对于本地媒体库读取的歌曲信息进行规范处理,分离歌曲名和歌手
if (title.contains("-")) {
String[] str = title.split("-");
artist = str[0];
title = str[1];
}
// 设置歌曲名称
mp3Info.setName(title.trim());
// 设置歌手
mp3Info.setSinger(artist);
// 设置时长(单位为秒)
mp3Info.setDuration(duration / 1000);
// 设置文件路径
mp3Info.setUrl(url);
mp3Info.setSongId(i+"");
// 设置歌曲的唯一标识符
mp3Info.setSongId(i + "");
// 将歌曲信息添加到列表中
mp3InfoList.add(mp3Info);
}
}
}
// 关闭游标
cursor.close();
// 返回存储本地歌曲信息的列表
return mp3InfoList;
}
@Override
public boolean saveSong(List<LocalSong> localSongs) {
// 先删除 LocalSong 表中的所有数据
LitePal.deleteAll(LocalSong.class);
// 遍历要保存的本地歌曲列表,将歌曲信息存储到数据库中
for (LocalSong localSong : localSongs) {
LocalSong song = new LocalSong();
// 设置歌曲名称
song.setName(localSong.getName());
// 设置歌手
song.setSinger(localSong.getSinger());
// 设置文件路径
song.setUrl(localSong.getUrl());
// 设置歌曲的唯一标识符
song.setSongId(localSong.getSongId());
// 设置时长
song.setDuration(localSong.getDuration());
if(!song.save()) return false;
// 保存歌曲信息,如果保存失败返回 false
if (!song.save()) return false;
}
// 保存成功返回 true
return true;
}
@Override
public boolean queryLove(String songId) {
List<Love> love=LitePal.where("songId=?",songId).find(Love.class);
return love.size() != 0;
// 根据歌曲的唯一标识符查询收藏歌曲列表
List<Love> love = LitePal.where("songId=?", songId).find(Love.class);
// 如果查询结果不为空,表示歌曲已被收藏,返回 true否则返回 false
return love.size()!= 0;
}
@Override
public boolean saveToLove(Song song) {
Love love =new Love();
// 创建一个新的收藏歌曲对象
Love love = new Love();
// 设置歌曲名称
love.setName(song.getSongName());
// 设置歌手
love.setSinger(song.getSinger());
// 设置文件路径
love.setUrl(song.getUrl());
// 设置歌曲封面图片的 URL
love.setPic(song.getImgUrl());
// 设置时长
love.setDuration(song.getDuration());
// 设置歌曲的唯一标识符
love.setSongId(song.getSongId());
// 设置是否为在线歌曲
love.setOnline(song.isOnline());
// 设置歌曲的 QQ 标识符
love.setQqId(song.getQqId());
// 设置歌曲的媒体标识符
love.setMediaId(song.getMediaId());
// 设置是否已下载
love.setDownload(song.isDownload());
// 保存收藏歌曲信息,保存成功返回 true否则返回 false
return love.save();
}
@Override
public boolean deleteFromLove(String songId) {
return LitePal.deleteAll(Love.class,"songId=?",songId) !=0;
// 根据歌曲的唯一标识符删除收藏歌曲,删除成功返回 true否则返回 false
return LitePal.deleteAll(Love.class, "songId=?", songId)!= 0;
}
//从下载列表中读取文件
private void getFromDownloadFile(List<LocalSong> songList){
// 从下载列表中读取文件的方法
private void getFromDownloadFile(List<LocalSong> songList) {
// 获取存储歌曲的目录
File file = new File(Api.STORAGE_SONG_FILE);
if(!file.exists()) {
// 如果目录不存在,则创建目录
if (!file.exists()) {
file.mkdirs();
return;
}
// 获取目录下的所有文件
File[] subFile = file.listFiles();
// 遍历文件列表
for (File value : subFile) {
String songFileName = value.getName();
// 提取歌曲文件名(不包含后缀)
String songFile = songFileName.substring(0, songFileName.lastIndexOf("."));
// 分割文件名,包含歌手、歌曲名、时长、歌曲唯一标识符和文件大小
String[] songValue = songFile.split("-");
// 获取文件大小
long size = Long.valueOf(songValue[4]);
//如果文件的大小不等于实际大小,则表示该歌曲还未下载完成,被人为暂停,故跳过该歌曲,不加入到已下载集合
if(size != value.length()) continue;
LocalSong song =new LocalSong();
// 如果文件大小不等于实际大小,说明歌曲未下载完成,跳过该歌曲
if (size!= value.length()) continue;
LocalSong song = new LocalSong();
// 设置歌手
song.setSinger(songValue[0]);
// 设置歌曲名称
song.setName(songValue[1]);
// 设置时长
song.setDuration(Long.valueOf(songValue[2]));
// 设置歌曲的唯一标识符
song.setSongId(songValue[3]);
// 设置文件路径
song.setUrl(Api.STORAGE_SONG_FILE + songFileName);
// 将歌曲信息添加到歌曲列表中
songList.add(song);
}
}
}
}

@ -16,16 +16,90 @@ import io.reactivex.Observable;
* author :
* time : 2019/07/15
* desc :
*
*
* RxJava Observable
*
*
* </pre>
*/
public interface NetworkHelper {
Observable<AlbumSong> getAlbumSong(String id); //得到专辑
Observable<SearchSong> search(String seek, int offset); //搜索歌曲
Observable<Album> searchAlbum(String seek,int offset);//搜索照片
Observable<SongLrc> getLrc(String seek);//获取歌词
Observable<OnlineSongLrc> getOnlineSongLrc(String songId);//获取网络歌曲的歌词
Observable<SingerImg> getSingerImg(String singer);//获取歌手头像
Observable<SongUrl> getSongUrl(String data);//获取播放地址
}
/**
*
*
* @param id
*
* Observable<AlbumSong>
*
*
*/
Observable<AlbumSong> getAlbumSong(String id);
/**
*
*
* @param seek
* @param offset
*
* Observable<SearchSong>
*
*
*/
Observable<SearchSong> search(String seek, int offset);
/**
*
*
* @param seek
* @param offset
*
* Observable<Album>
*
*
*/
Observable<Album> searchAlbum(String seek,int offset);
/**
*
*
* @param seek
*
* Observable<SongLrc>
*
*
*/
Observable<SongLrc> getLrc(String seek);
/**
*
*
* @param songId
*
* Observable<OnlineSongLrc>
*
*
*/
Observable<OnlineSongLrc> getOnlineSongLrc(String songId);
/**
*
*
* @param singer
*
* Observable<SingerImg>
*
*
*/
Observable<SingerImg> getSingerImg(String singer);
/**
*
*
* @param data
*
* Observable<SongUrl>
*
* 便
*/
Observable<SongUrl> getSongUrl(String data);
}

@ -17,48 +17,62 @@ import io.reactivex.Observable;
* author :
* time : 2019/07/15
* desc :
*
* NetworkHelper
* RetrofitService
* RetrofitService
*
* RxJava Observable
* </pre>
*/
public class NetworkHelperImpl implements NetworkHelper {
// 存储 RetrofitService 实例,用于执行网络请求
private RetrofitService mRetrofitService;
// 构造函数,接收 RetrofitService 实例并存储
public NetworkHelperImpl(RetrofitService retrofitService){
mRetrofitService = retrofitService;
}
// 获取专辑歌曲的方法,调用 RetrofitService 的 getAlbumSong 方法
@Override
public Observable<AlbumSong> getAlbumSong(String id) {
return mRetrofitService.getAlbumSong(id);
}
// 搜索歌曲的方法,调用 RetrofitService 的 search 方法
@Override
public Observable<SearchSong> search(String seek, int offset) {
return mRetrofitService.search(seek, offset);
}
// 搜索专辑的方法,调用 RetrofitService 的 searchAlbum 方法
@Override
public Observable<Album> searchAlbum(String seek, int offset) {
return mRetrofitService.searchAlbum(seek, offset);
}
// 获取歌词的方法,调用 RetrofitService 的 getLrc 方法
@Override
public Observable<SongLrc> getLrc(String seek) {
return mRetrofitService.getLrc(seek);
}
// 获取网络歌曲歌词的方法,调用 RetrofitService 的 getOnlineSongLrc 方法
@Override
public Observable<OnlineSongLrc> getOnlineSongLrc(String songId) {
return mRetrofitService.getOnlineSongLrc(songId);
}
// 获取歌手头像的方法,调用 RetrofitService 的 getSingerImg 方法
@Override
public Observable<SingerImg> getSingerImg(String singer) {
return mRetrofitService.getSingerImg(singer);
}
// 获取歌曲播放地址的方法,调用 RetrofitService 的 getSongUrl 方法
@Override
public Observable<SongUrl> getSongUrl(String data) {
return mRetrofitService.getSongUrl(data);
}
}
}

@ -18,83 +18,114 @@ import retrofit2.converter.gson.GsonConverterFactory;
* <pre>
* author :
* time : 2019/07/15
* desc : Retrofit便
* desc : Retrofit便
*
* Retrofit Retrofit
* Retrofit
*
* 使 OkHttpClient
* 使 Gson JSON
* RxJava2
* </pre>
*/
public class RetrofitFactory {
// 存储 OkHttpClient 实例,用于网络请求
private static OkHttpClient sOkHttpClient;
// 存储通用的 Retrofit 实例
private static Retrofit sRetrofit;
// 存储用于歌曲播放地址请求的 Retrofit 实例
private static Retrofit songUrlRetrofit;
// 存储用于歌手照片请求的 Retrofit 实例
private static Retrofit sSingerPicRetrofit;
// 创建网络请求Observable
// 创建网络请求 Observable 的方法,使用通用的 Retrofit 实例
public static RetrofitService createRequest() {
return getRetrofit().create(RetrofitService.class);
}
// 创建用于歌手相关请求的网络请求 Observable 的方法,使用歌手照片的 Retrofit 实例
public static RetrofitService createRequestOfSinger() {
return getRetrofitOfSinger().create(RetrofitService.class);
}
public static RetrofitService createRequestOfSongUrl(){
// 创建用于歌曲播放地址请求的网络请求 Observable 的方法,使用歌曲播放地址的 Retrofit 实例
public static RetrofitService createRequestOfSongUrl() {
return getRetrofitOfSongUrl().create(RetrofitService.class);
}
// 配置Retrofit
// 配置通用的 Retrofit 实例
private synchronized static Retrofit getRetrofit() {
if (sRetrofit == null) {
sRetrofit = new Retrofit.Builder()
.baseUrl(Api.FIDDLER_BASE_QQ_URL) // 对应服务端的host
.client(getOkHttpClient())
.addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create())) // 这里还结合了Gson
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 把Retrofit请求转化成RxJava的Observable
.build();
// 设置基础 URL对应服务端的 host
.baseUrl(Api.FIDDLER_BASE_QQ_URL)
// 使用 OkHttpClient 进行网络请求
.client(getOkHttpClient())
// 使用 Gson 进行 JSON 数据的解析和转换,使用了宽松模式
.addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create()))
// 将 Retrofit 请求转化成 RxJava 的 Observable以便使用 RxJava 处理异步请求结果
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
return sRetrofit;
}
// 获取歌手照片
// 配置用于歌手照片请求的 Retrofit 实例
private synchronized static Retrofit getRetrofitOfSinger() {
if (sSingerPicRetrofit == null) {
sSingerPicRetrofit = new Retrofit.Builder()
.baseUrl(Api.SINGER_PIC_BASE_URL) // 对应服务端的host
.client(getOkHttpClient())
.addConverterFactory(GsonConverterFactory.create()) // 这里还结合了Gson
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 把Retrofit请求转化成RxJava的Observable
.build();
// 设置基础 URL对应服务端的 host
.baseUrl(Api.SINGER_PIC_BASE_URL)
// 使用 OkHttpClient 进行网络请求
.client(getOkHttpClient())
// 使用 Gson 进行 JSON 数据的解析和转换
.addConverterFactory(GsonConverterFactory.create())
// 将 Retrofit 请求转化成 RxJava 的 Observable
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
return sSingerPicRetrofit;
}
//得到播放地址
// 配置用于歌曲播放地址请求的 Retrofit 实例
private synchronized static Retrofit getRetrofitOfSongUrl() {
if (songUrlRetrofit == null) {
songUrlRetrofit = new Retrofit.Builder()
.baseUrl(Api.FIDDLER_BASE_SONG_URL) // 对应服务端的host
.client(getOkHttpClient())
.addConverterFactory(GsonConverterFactory.create()) // 这里还结合了Gson
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 把Retrofit请求转化成RxJava的Observable
.build();
// 设置基础 URL对应服务端的 host
.baseUrl(Api.FIDDLER_BASE_SONG_URL)
// 使用 OkHttpClient 进行网络请求
.client(getOkHttpClient())
// 使用 Gson 进行 JSON 数据的解析和转换
.addConverterFactory(GsonConverterFactory.create())
// 将 Retrofit 请求转化成 RxJava 的 Observable
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
return songUrlRetrofit;
}
//配置OkHttp
// 配置 OkHttpClient 实例
private synchronized static OkHttpClient getOkHttpClient() {
if (sOkHttpClient == null) {
// 创建一个 HttpLoggingInterceptor 用于打印网络请求日志
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(message -> {
//打印retrofit日志
// 打印 retrofit 日志
Log.i("RetrofitLog","retrofitBack = "+message);
});
// 设置日志级别为 BODY会打印请求和响应的详细信息
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
sOkHttpClient = new OkHttpClient.Builder()
.connectTimeout(100, TimeUnit.SECONDS)
.readTimeout(100,TimeUnit.SECONDS)
.writeTimeout(100,TimeUnit.SECONDS)
//.addInterceptor(loggingInterceptor) 日志
.build();
// 设置连接超时时间
.connectTimeout(100, TimeUnit.SECONDS)
// 设置读取超时时间
.readTimeout(100,TimeUnit.SECONDS)
// 设置写入超时时间
.writeTimeout(100,TimeUnit.SECONDS)
// 可以添加日志拦截器,这里被注释掉了
//.addInterceptor(loggingInterceptor)
.build();
}
return sOkHttpClient;
}
}
}

@ -22,43 +22,73 @@ import retrofit2.http.Query;
* author :
* time : 2019/07/15
* desc :
*
* 使 Retrofit
*
*
* API 使 RxJava Observable
* </pre>
*/
public interface RetrofitService {
/**
* https://c.y.qq.com/soso/fcgi-bin/client_search_cp?p=2&n=2&w=周杰伦&format=json
* https://c.y.qq.com/soso/fcgi-bin/client_search_cp?p=2&n=2&w=周杰伦&format=json
*
* @param seek
* @param offset
*
* 使 @GET Api.SEARCH_SONG URL
* @Query "w" "p"
* Observable<SearchSong>
*/
@GET(Api.SEARCH_SONG)
Observable<SearchSong> search(@Query("w") String seek, @Query("p")int offset);
/**
* https://c.y.qq.com/soso/fcgi-bin/client_search_cp?p=1&n=2&w=林宥嘉&format=json&t=8
*
* @param seek
* @param offset
*
* 使 @GET Api.SEARCH_ALBUM URL
* @Query "w" "p"
* Observable<Album>
*/
@GET(Api.SEARCH_ALBUM)
Observable<Album> searchAlbum(@Query("w") String seek, @Query("p")int offset);
/**
* https://c.y.qq.com/v8/fcg-bin/fcg_v8_album_info_cp.fcg?albummid=004YodY33zsWTT&format=json
* @param id mid
*
* @param id mid
*
* 使 @GET Api.ALBUM_DETAIL URL
* @Query "albummid" mid
* Observable<AlbumSong>
*/
@GET(Api.ALBUM_DETAIL)
Observable<AlbumSong> getAlbumSong(@Query("albummid")String id);
/**
* songmid{}
* songmid{}
* https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&data=%7B%22req_0%22%3A%7B%22module%22%3A%22vkey.GetVkeyServer%22%2C%22method%22%3A%22CgiGetVkey%22%2C%22param%22%3A%7B%22guid%22%3A%22358840384%22%2C%22 +
* songmid%22%3A%5B%22{003wFozn3V3Ra0} +
* %22%5D%2C%22songtype%22%3A%5B0%5D%2C%22uin%22%3A%221443481947%22%2C%22loginflag%22%3A1%2C%22platform%22%3A%2220%22%7D%7D%2C%22comm%22%3A%7B%22uin%22%3A%221443481947%22%2C%22format%22%3A%22json%22%2C%22ct%22%3A24%2C%22cv%22%3A0%7D%7D
*
* 使 @GET Api.SONG_URL URL
* @Query "data" true
* Observable<SongUrl>
*/
@GET(Api.SONG_URL)
Observable<SongUrl> getSongUrl(@Query(value = "data",encoded = true) String data);
/**
* songmidhttps://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?songmid=000wocYU11tSzS&format=json&nobase64=1
* headersRefererqq
* songmid https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?songmid=000wocYU11tSzS&format=json&nobase64=1
* headers Referer qq
*
* 使 @GET Api.ONLINE_SONG_LRC URL
* @Headers Api.HEADER_REFERER
* @Query "songmid" mid
* Observable<OnlineSongLrc>线
*/
@Headers(Api.HEADER_REFERER)
@GET(Api.ONLINE_SONG_LRC)
@ -66,18 +96,29 @@ public interface RetrofitService {
/**
* https://c.y.qq.com/soso/fcgi-bin/client_search_cp?p=1&n=1&w=说谎&format=json&t=7
*
* @param seek
*
* 使 @GET Api.SONG_LRC URL
* @Query "w"
* Observable<SongLrc>
*/
@GET(Api.SONG_LRC)
Observable<SongLrc> getLrc(@Query("w") String seek);
/**
* http://music.163.com/api/search/get/web?s=刘瑞琦&type=100
*
* @param singer
*
* 使 @POST Api.SINGER_PIC URL
* @Headers Api.HEADER_USER_AGENT
* @FormUrlEncoded 使
* @Field "s"
* Observable<SingerImg>
*/
@Headers(Api.HEADER_USER_AGENT)
@POST(Api.SINGER_PIC)
@FormUrlEncoded
Observable<SingerImg> getSingerImg(@Field("s")String singer);
}
}

@ -5,10 +5,18 @@ package com.example.musicplayer.model.prefs;
* author :
* time : 2019/09/09
* desc :
*
*
*
*
*
*
* </pre>
*/
public interface PreferencesHelper {
void setPlayMode(int mode); //保存播放状态
int getPlayMode();//得到播放状态
}
// 保存播放状态的方法,接收一个整数表示播放模式
void setPlayMode(int mode);
// 获取播放状态的方法,返回一个整数表示播放模式
int getPlayMode();
}

@ -11,23 +11,33 @@ import com.example.musicplayer.app.Constant;
* author :
* time : 2019/09/09
* desc :
*
* PreferencesHelper 使 SharedPreferences
* App.getContext() 使 Constant.SHARED_PREFERENCES_NAME SharedPreferences
*
* </pre>
*/
public class PreferencesHelperImpl implements PreferencesHelper{
// 存储 SharedPreferences 实例,用于存储播放模式等信息
private SharedPreferences mPreferences;
// 构造函数,获取 SharedPreferences 实例
public PreferencesHelperImpl(){
// 获取 SharedPreferences 实例,使用应用上下文和预定义的名称,且为私有模式
mPreferences = App.getContext().getSharedPreferences(Constant.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
}
// 实现接口中的设置播放模式方法,将播放模式存储到 SharedPreferences 中
@Override
public void setPlayMode(int mode) {
// 编辑 SharedPreferences 并存储播放模式,使用 Constant.PREFS_PLAY_MODE 作为键mode 作为值,并使用 apply 方法异步保存
mPreferences.edit().putInt(Constant.PREFS_PLAY_MODE,mode).apply();
}
// 实现接口中的获取播放模式方法,从 SharedPreferences 中获取播放模式
@Override
public int getPlayMode() {
// 从 SharedPreferences 中获取播放模式,使用 Constant.PREFS_PLAY_MODE 作为键,若不存在则默认返回 0
return mPreferences.getInt(Constant.PREFS_PLAY_MODE,0);
}
}
}

@ -19,32 +19,53 @@ import static com.example.musicplayer.view.search.AlbumSongFragment.ALBUM_SONG;
/**
* Created by on 2018/11/27.
*
* <pre>
* Presenter BasePresenter IAlbumSongContract.Presenter
*
* 使 RxJava IO 线线
* BaseObserver
* </pre>
*/
public class AlbumSongPresenter extends BasePresenter<IAlbumSongContract.View> implements IAlbumSongContract.Presenter {
// 用于日志输出的 TAG 常量,方便在日志中识别该类
private final static String TAG = "AlbumSongPresenter";
// 实现 IAlbumSongContract.Presenter 接口中的 getAlbumDetail 方法,用于获取专辑的详细信息
@Override
public void getAlbumDetail(String id, int type) {
// 使用 addRxSubscribe 方法添加 RxJava 的订阅操作
addRxSubscribe(
// 调用 mModel 的 getAlbumSong 方法获取专辑歌曲信息mModel 可能是在 BasePresenter 中声明的对象
mModel.getAlbumSong(id)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new BaseObserver<AlbumSong>(mView) {
// 将网络请求操作调度到 IO 线程,避免阻塞主线程
.subscribeOn(Schedulers.io())
// 将结果处理调度到主线程,方便更新 UI
.observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对结果进行观察和处理
.subscribeWith(new BaseObserver<AlbumSong>(mView) {
// 在网络请求开始时调用,用于显示加载状态
@Override
public void onStart() {
mView.showLoading();
}
// 当请求成功获取到结果时调用
@Override
public void onNext(AlbumSong albumSong) {
super.onNext(albumSong);
// 隐藏加载状态
mView.hideLoading();
// 如果获取的结果代码为 0表示请求成功
if (albumSong.getCode() == 0) {
// 根据类型进行不同的处理
if (type == ALBUM_SONG) {
// 调用 insertAllAlbumSong 方法插入所有专辑歌曲
insertAllAlbumSong(albumSong.getData().getList());
} else {
// 调用视图的 showAlbumMessage 方法显示专辑信息
mView.showAlbumMessage(
albumSong.getData().getName(),
albumSong.getData().getLan(),
@ -53,18 +74,25 @@ public class AlbumSongPresenter extends BasePresenter<IAlbumSongContract.View> i
albumSong.getData().getDesc());
}
} else {
// 如果请求不成功,显示专辑歌曲错误
mView.showAlbumSongError();
}
}
// 当请求发生错误时调用
@Override
public void onError(Throwable e) {
// 打印错误栈信息
e.printStackTrace();
// 输出错误信息到日志
Log.d(TAG, "onError: " + e.toString());
// 隐藏加载状态
mView.hideLoading();
// 如果是网络异常且类型为 ALBUM_SONG则显示网络错误
if (e instanceof UnknownHostException && type == ALBUM_SONG) {
mView.showNetError();
} else {
// 否则显示专辑歌曲错误
mView.showAlbumSongError();
}
}
@ -72,9 +100,12 @@ public class AlbumSongPresenter extends BasePresenter<IAlbumSongContract.View> i
);
}
// 实现 IAlbumSongContract.Presenter 接口中的 insertAllAlbumSong 方法,用于插入所有专辑歌曲
@Override
public void insertAllAlbumSong(List<AlbumSong.DataBean.ListBean> songList) {
// 调用 mModel 的 insertAllAlbumSong 方法将歌曲列表插入数据库mModel 可能是在 BasePresenter 中声明的对象
mModel.insertAllAlbumSong(songList);
// 调用视图的 setAlbumSongList 方法更新视图的专辑歌曲列表
mView.setAlbumSongList(songList);
}
}
}

@ -14,27 +14,42 @@ import java.util.List;
/**
* Created by on 2018/10/17.
*
* <pre>
* Presenter BasePresenter ILocalContract.Presenter
* MP3
* mModel 使 EventBus
*
* </pre>
*/
public class LocalPresenter extends BasePresenter<ILocalContract.View> implements ILocalContract.Presenter {
// 实现 ILocalContract.Presenter 接口中的 getLocalMp3Info 方法,用于获取本地 MP3 信息
@Override
public void getLocalMp3Info() {
// 调用 mModel 的 getLocalMp3Info 方法获取本地歌曲列表mModel 可能是在 BasePresenter 中声明的对象
List<LocalSong> localSongList = mModel.getLocalMp3Info();
// 如果获取到的本地歌曲列表大小为 0表示没有本地歌曲
if(localSongList.size() == 0){
// 调用视图的 showErrorView 方法显示错误视图
mView.showErrorView();
}else {
// 否则调用 saveSong 方法保存本地歌曲
saveSong(localSongList);
}
}
// 实现 ILocalContract.Presenter 接口中的 saveSong 方法,用于保存本地歌曲
@Override
public void saveSong(List<LocalSong> localSongs) {
// 调用 mModel 的 saveSong 方法保存本地歌曲mModel 可能是在 BasePresenter 中声明的对象
if(mModel.saveSong(localSongs)) {
// 如果保存成功,使用 EventBus 发送一个 SongListNumEvent 事件,类型为 Constant.LIST_TYPE_LOCAL
EventBus.getDefault().post(new SongListNumEvent(Constant.LIST_TYPE_LOCAL));
// 调用视图的 showToast 方法显示成功导入本地音乐的提示
mView.showToast("成功导入本地音乐");
// 调用视图的 showMusicList 方法显示音乐列表
mView.showMusicList(localSongs);
}
}
}
}

@ -21,134 +21,191 @@ import io.reactivex.schedulers.Schedulers;
/**
* Created by on 2018/10/26.
*
* <pre>
* Presenter BasePresenter IPlayContract.Presenter
* ID
* 使 RxJava IO 线线
* RetrofitFactory 使 BaseObserver
* </pre>
*/
public class PlayPresenter extends BasePresenter<IPlayContract.View> implements IPlayContract.Presenter {
// 实现 IPlayContract.Presenter 接口中的 getSingerImg 方法,用于获取歌手图片
@Override
public void getSingerImg(String singer, String song, long duration) {
addRxSubscribe(
// 通过 RetrofitFactory 创建请求服务并调用 getSingerImg 方法获取歌手图片
RetrofitFactory.createRequestOfSinger().getSingerImg(singer)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.doOnNext(singerImg -> mView.setSingerImg(singerImg.getResult().getArtists().get(0).getImg1v1Url()))
.doOnError(SingerImg -> mView.showToast("获取不到歌手图片"))
.observeOn(Schedulers.io())
.flatMap((Function<SingerImg, ObservableSource<SearchSong>>) singerImg -> RetrofitFactory.createRequest().search(song,1))
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new BaseObserver<SearchSong>(mView) {
// 将网络请求操作调度到 IO 线程
.subscribeOn(Schedulers.io())
// 将结果处理调度到主线程
.observeOn(AndroidSchedulers.mainThread())
// 当获取到歌手图片时,设置歌手图片到视图
.doOnNext(singerImg -> mView.setSingerImg(singerImg.getResult().getArtists().get(0).getImg1v1Url()))
// 当获取歌手图片出现错误时,显示错误信息
.doOnError(SingerImg -> mView.showToast("获取不到歌手图片"))
// 再次将后续操作调度到 IO 线程
.observeOn(Schedulers.io())
// 将获取歌手图片的操作结果转换为搜索歌曲的操作
.flatMap((Function<SingerImg, ObservableSource<SearchSong>>) singerImg -> RetrofitFactory.createRequest().search(song, 1))
// 将后续操作调度到主线程
.observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对搜索歌曲的结果进行观察和处理
.subscribeWith(new BaseObserver<SearchSong>(mView) {
// 当搜索歌曲成功时
@Override
public void onNext(SearchSong value) {
super.onNext(value);
if (value.getCode() == 0) {
matchLrc(value.getData().getSong().getList(),duration);
// 调用 matchLrc 方法匹配歌词
matchLrc(value.getData().getSong().getList(), duration);
} else {
// 当搜索结果异常时,显示获取歌词错误
mView.getLrcError(null);
}
}
// 当搜索歌曲出现错误时
@Override
public void onError(Throwable e) {
super.onError(e);
// 显示获取歌词错误
mView.getLrcError(null);
}
})
);
}
// 实现 IPlayContract.Presenter 接口中的 getLrc 方法,用于获取歌词
@Override
public void getLrc(String songId,int type) {
public void getLrc(String songId, int type) {
// 调用 mModel 的 getOnlineSongLrc 方法获取在线歌曲歌词mModel 可能是在 BasePresenter 中声明的对象
mModel.getOnlineSongLrc(songId)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new BaseObserver<OnlineSongLrc>(mView,false,false){
// 将网络请求操作调度到 IO 线程
.subscribeOn(Schedulers.io())
// 将结果处理调度到主线程
.observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对获取歌词的结果进行观察和处理
.subscribeWith(new BaseObserver<OnlineSongLrc>(mView, false, false) {
// 当获取歌词成功时
@Override
public void onNext(OnlineSongLrc onlineSongLrc){
if(onlineSongLrc.getCode() == 0){
public void onNext(OnlineSongLrc onlineSongLrc) {
if (onlineSongLrc.getCode() == 0) {
String lrc = onlineSongLrc.getLyric();
//如果是本地音乐,就将歌词保存起来
if(type == Constant.SONG_LOCAL) mView.saveLrc(lrc);
// 如果是本地音乐,保存歌词
if (type == Constant.SONG_LOCAL) mView.saveLrc(lrc);
// 显示歌词
mView.showLrc(lrc);
}else {
} else {
// 当获取歌词失败时,显示获取歌词错误
mView.getLrcError(null);
}
}
});
}
// 实现 IPlayContract.Presenter 接口中的 getSongId 方法,用于获取歌曲 ID
@Override
public void getSongId(String song, long duration) {
addRxSubscribe(
// 调用 mModel 的 search 方法搜索歌曲
mModel.search(song, 1)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new BaseObserver<SearchSong>(mView, true, true) {
// 将网络请求操作调度到 IO 线程
.subscribeOn(Schedulers.io())
// 将结果处理调度到主线程
.observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对搜索结果进行观察和处理
.subscribeWith(new BaseObserver<SearchSong>(mView, true, true) {
// 当搜索成功时
@Override
public void onNext(SearchSong searchSong) {
super.onNext(searchSong);
if (searchSong.getCode() == 0) {
matchSong(searchSong.getData().getSong().getList(),duration);
// 调用 matchSong 方法匹配歌曲
matchSong(searchSong.getData().getSong().getList(), duration);
} else {
// 当搜索结果异常时,显示获取歌词错误
mView.getLrcError(null);
}
}
}));
});
}
// 实现 IPlayContract.Presenter 接口中的 setPlayMode 方法,用于设置播放模式
@Override
public void setPlayMode(int mode) {
// 调用 mModel 的 setPlayMode 方法设置播放模式mModel 可能是在 BasePresenter 中声明的对象
mModel.setPlayMode(mode);
}
// 实现 IPlayContract.Presenter 接口中的 getPlayMode 方法,用于获取播放模式
@Override
public int getPlayMode() {
// 调用 mModel 的 getPlayMode 方法获取播放模式mModel 可能是在 BasePresenter 中声明的对象
return mModel.getPlayMode();
}
// 实现 IPlayContract.Presenter 接口中的 queryLove 方法,用于查询歌曲是否被收藏
@Override
public void queryLove(String songId) {
// 调用 mView 的 showLove 方法显示歌曲是否被收藏mView 是 IPlayContract.View 类型的视图对象
mView.showLove(mModel.queryLove(songId));
}
// 实现 IPlayContract.Presenter 接口中的 saveToLove 方法,用于将歌曲保存到收藏
@Override
public void saveToLove(Song song) {
if(mModel.saveToLove(song)){
// 调用 mModel 的 saveToLove 方法保存歌曲到收藏mModel 可能是在 BasePresenter 中声明的对象
if (mModel.saveToLove(song)) {
// 当保存成功时,调用 mView 的 saveToLoveSuccess 方法显示保存成功信息
mView.saveToLoveSuccess();
}
}
// 实现 IPlayContract.Presenter 接口中的 deleteFromLove 方法,用于从收藏中删除歌曲
@Override
public void deleteFromLove(String songId) {
if(mModel.deleteFromLove(songId)){
// 调用 mModel 的 deleteFromLove 方法从收藏中删除歌曲mModel 可能是在 BasePresenter 中声明的对象
if (mModel.deleteFromLove(songId)) {
// 当删除成功时,调用 mView 的 sendUpdateCollection 方法更新收藏列表
mView.sendUpdateCollection();
}
}
//匹配歌词
private void matchLrc(List<SearchSong.DataBean.SongBean.ListBean> listBeans,long duration){
// 匹配歌词的私有方法
private void matchLrc(List<SearchSong.DataBean.SongBean.ListBean> listBeans, long duration) {
boolean isFind = false;
for(SearchSong.DataBean.SongBean.ListBean listBean:listBeans){
if(duration == listBean.getInterval()){
// 遍历搜索歌曲结果列表
for (SearchSong.DataBean.SongBean.ListBean listBean : listBeans) {
// 如果歌曲时长匹配,设置找到标志并设置歌曲 ID
if (duration == listBean.getInterval()) {
isFind = true;
mView.setLocalSongId(listBean.getSongmid());
}
}
//如果找不到歌曲id就传输找不到歌曲的消
if(!isFind) {
// 如果未找到匹配的歌曲,显示找不到歌曲 ID 的错误信
if (!isFind) {
mView.getLrcError(Constant.SONG_ID_UNFIND);
}
}
private void matchSong(List<SearchSong.DataBean.SongBean.ListBean> listBeans,long duration){
// 匹配歌曲的私有方法
private void matchSong(List<SearchSong.DataBean.SongBean.ListBean> listBeans, long duration) {
boolean isFind = false;
for(SearchSong.DataBean.SongBean.ListBean listBean:listBeans){
if(duration == listBean.getInterval()){
// 遍历搜索歌曲结果列表
for (SearchSong.DataBean.SongBean.ListBean listBean : listBeans) {
// 如果歌曲时长匹配,设置找到标志并获取歌曲成功
if (duration == listBean.getInterval()) {
isFind = true;
mView.getSongIdSuccess(listBean.getSongmid());
}
}
//如果找不到歌曲id就传输找不到歌曲的消
if(!isFind) {
// 如果未找到匹配的歌曲,显示找不到歌曲 ID 的错误信
if (!isFind) {
mView.getLrcError(Constant.SONG_ID_UNFIND);
}
}
}
}

@ -30,22 +30,37 @@ import okhttp3.Response;
/**
* Created by on 2018/11/21.
*
* <pre>
* Presenter BasePresenter ISearchContentContract.Presenter
*
* 使 RxJava IO 线线
* BaseObserver
* </pre>
*/
public class SearchContentPresenter extends BasePresenter<ISearchContentContract.View>
implements ISearchContentContract.Presenter {
// 用于日志输出的 TAG 常量,方便在日志中识别该类
private static final String TAG = "SearchContentPresenter";
// 实现 ISearchContentContract.Presenter 接口中的 search 方法,用于搜索歌曲
@Override
public void search(String seek, int offset) {
addRxSubscribe(
// 调用 mModel 的 search 方法进行搜索mModel 可能是在 BasePresenter 中声明的对象
mModel.search(seek, offset)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new BaseObserver<SearchSong>(mView, true, true) {
// 将网络请求操作调度到 IO 线程
.subscribeOn(Schedulers.io())
// 将结果处理调度到主线程
.observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对搜索结果进行观察和处理
.subscribeWith(new BaseObserver<SearchSong>(mView, true, true) {
// 当搜索结果成功时
@Override
public void onNext(SearchSong searchSong) {
super.onNext(searchSong);
if (searchSong.getCode() == 0) {
// 将搜索结果中的歌曲列表设置到视图中
mView.setSongsList((ArrayList<SearchSong.DataBean.SongBean.ListBean>)
searchSong.getData().getSong().getList());
}
@ -53,12 +68,19 @@ public class SearchContentPresenter extends BasePresenter<ISearchContentContract
}));
}
// 实现 ISearchContentContract.Presenter 接口中的 searchMore 方法,用于搜索更多歌曲
@Override
public void searchMore(String seek, int offset) {
addRxSubscribe(
// 调用 mModel 的 search 方法进行搜索mModel 可能是在 BasePresenter 中声明的对象
mModel.search(seek, offset)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new BaseObserver<SearchSong>(mView, false, true) {
// 将网络请求操作调度到 IO 线程
.subscribeOn(Schedulers.io())
// 将结果处理调度到主线程
.observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对搜索结果进行观察和处理
.subscribeWith(new BaseObserver<SearchSong>(mView, false, true) {
// 当搜索结果成功时
@Override
public void onNext(SearchSong searchSong) {
super.onNext(searchSong);
@ -66,90 +88,125 @@ public class SearchContentPresenter extends BasePresenter<ISearchContentContract
ArrayList<SearchSong.DataBean.SongBean.ListBean> songListBeans =
(ArrayList<SearchSong.DataBean.SongBean.ListBean>) searchSong.getData().getSong().getList();
if (songListBeans.size() == 0) {
// 当搜索结果为空时,显示搜索更多错误
mView.searchMoreError();
} else {
// 当搜索结果不为空时,显示搜索更多成功并传递结果列表
mView.searchMoreSuccess(songListBeans);
}
} else {
// 当搜索结果异常时,显示搜索更多错误
mView.searchMoreError();
}
}
// 当搜索出现错误时
@Override
public void onError(Throwable e){
public void onError(Throwable e) {
super.onError(e);
// 显示搜索更多的网络错误
mView.showSearcherMoreNetworkError();
}
}));
}
// 实现 ISearchContentContract.Presenter 接口中的 searchAlbum 方法,用于搜索专辑
@Override
public void searchAlbum(String seek, int offset) {
addRxSubscribe(
// 调用 mModel 的 searchAlbum 方法进行专辑搜索mModel 可能是在 BasePresenter 中声明的对象
mModel.searchAlbum(seek, offset)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new BaseObserver<Album>(mView, true, true) {
// 将网络请求操作调度到 IO 线程
.subscribeOn(Schedulers.io())
// 将结果处理调度到主线程
.observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对搜索专辑结果进行观察和处理
.subscribeWith(new BaseObserver<Album>(mView, true, true) {
// 当搜索专辑结果成功时
@Override
public void onNext(Album album) {
super.onNext(album);
if (album.getCode() == 0) {
// 当搜索专辑结果正常时,显示搜索专辑成功并传递结果列表
mView.searchAlbumSuccess(album.getData().getAlbum().getList());
} else {
// 当搜索专辑结果异常时,显示搜索专辑错误
mView.searchAlbumError();
}
}
}));
}
// 实现 ISearchContentContract.Presenter 接口中的 searchAlbumMore 方法,用于搜索更多专辑
@Override
public void searchAlbumMore(String seek, int offset) {
addRxSubscribe(
// 调用 mModel 的 searchAlbum 方法进行专辑搜索mModel 可能是在 BasePresenter 中声明的对象
mModel.searchAlbum(seek, offset)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new BaseObserver<Album>(mView, false, true) {
// 将网络请求操作调度到 IO 线程
.subscribeOn(Schedulers.io())
// 将结果处理调度到主线程
.observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对搜索专辑结果进行观察和处理
.subscribeWith(new BaseObserver<Album>(mView, false, true) {
// 当搜索专辑结果成功时
@Override
public void onNext(Album album) {
super.onNext(album);
if (album.getCode() == 0) {
// 当搜索专辑结果正常时,显示搜索更多专辑成功并传递结果列表
mView.searchAlbumMoreSuccess(album.getData().getAlbum().getList());
} else {
// 当搜索专辑结果异常时,显示搜索更多错误
mView.searchMoreError();
}
}
// 当搜索专辑出现错误时
@Override
public void onError(Throwable e){
public void onError(Throwable e) {
super.onError(e);
// 显示搜索更多的网络错误
mView.showSearcherMoreNetworkError();
}
}));
}
// 实现 ISearchContentContract.Presenter 中的 getSongUrl 方法,用于获取歌曲播放地址
@Override
public void getSongUrl(Song song) {
// 输出日志,显示正在获取歌曲播放地址及相关数据
Log.d(TAG, "getSongUrl: "+Api.SONG_URL_DATA_LEFT+song.getSongId()+Api.SONG_URL_DATA_RIGHT);
addRxSubscribe(
// 通过 RetrofitFactory 创建请求服务并调用 getSongUrl 方法获取歌曲播放地址
RetrofitFactory.createRequestOfSongUrl().getSongUrl(Api.SONG_URL_DATA_LEFT+song.getSongId()+Api.SONG_URL_DATA_RIGHT)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new BaseObserver<SongUrl>(mView,false,false){
// 将网络请求操作调度到 IO 线程
.subscribeOn(Schedulers.io())
// 将结果处理调度到主线程
.observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对获取歌曲播放地址的结果进行观察和处理
.subscribeWith(new BaseObserver<SongUrl>(mView,false,false){
// 当获取歌曲播放地址成功时
@Override
public void onNext(SongUrl songUrl){
public void onNext(SongUrl songUrl) {
super.onNext(songUrl);
if(songUrl.getCode() == 0){
if (songUrl.getCode() == 0) {
String sip = songUrl.getReq_0().getData().getSip().get(0);
String purl = songUrl.getReq_0().getData().getMidurlinfo().get(0).getPurl();
if(purl.equals("")){
if (purl.equals("")) {
// 当歌曲无版权时,显示提示信息
mView.showToast("该歌曲暂时没有版权,搜搜其它歌曲吧");
}else {
mView.getSongUrlSuccess(song,sip+purl);
} else {
// 当获取播放地址成功时,调用视图的 getSongUrlSuccess 方法
mView.getSongUrlSuccess(song, sip + purl);
}
}else {
} else {
// 当获取歌曲播放地址失败时,显示错误信息
mView.showToast(songUrl.getCode()+":获取不到歌曲播放地址");
}
}
})
);
}
}
}

@ -8,7 +8,6 @@ import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
@ -46,164 +45,225 @@ import static com.example.musicplayer.app.Constant.TYPE_DOWNLOADING;
* <pre>
* author :
* time : 2019/04/08
* desc : DownloadTask
* desc : DownloadTask
*
*
* 使 DownloadTask DownloadListener
* 使 EventBus UI
* 使 LitePal
* 使 Notification
* </pre>
*/
public class DownloadService extends Service {
// 日志标签
private static final String TAG = "DownloadService";
// 存储当前的下载任务
private DownloadTask downloadTask;
// 存储下载的 URL
private String downloadUrl;
// 自定义的 Binder 对象,用于与客户端通信
private DownloadBinder downloadBinder = new DownloadBinder();
private LinkedList<DownloadInfo> downloadQueue = new LinkedList<>();//等待队列
private int position = 0;//下载歌曲在下载歌曲列表的位置
// 存储下载等待队列,使用 LinkedList 方便在队列头部和尾部操作
private LinkedList<DownloadInfo> downloadQueue = new LinkedList<>();
// 下载歌曲在下载歌曲列表中的位置
private int position = 0;
// 下载监听器,监听下载进度和状态
private DownloadListener listener = new DownloadListener() {
// 下载进度更新时调用
@Override
public void onProgress(DownloadInfo downloadInfo) {
// 更新下载信息的状态为正在下载
downloadInfo.setStatus(Constant.DOWNLOAD_ING);
EventBus.getDefault().post(new DownloadEvent(TYPE_DOWNLOADING, downloadInfo)); //通知下载模块
if(downloadInfo.getProgress()!=100){
getNotificationManager().notify(1, getNotification("正在下载: "+downloadInfo.getSongName(), downloadInfo.getProgress()));
}else {
if(downloadQueue.isEmpty()) getNotificationManager().notify(1, getNotification("下载成功",-1));
// 发布下载事件,通知下载模块
EventBus.getDefault().post(new DownloadEvent(TYPE_DOWNLOADING, downloadInfo));
if (downloadInfo.getProgress()!= 100) {
// 更新通知显示正在下载及进度
getNotificationManager().notify(1, getNotification("正在下载: " + downloadInfo.getSongName(), downloadInfo.getProgress()));
} else {
if (downloadQueue.isEmpty()) {
// 当下载队列空且下载完成时,更新通知为下载成功
getNotificationManager().notify(1, getNotification("下载成功", -1));
}
}
}
// 下载成功时调用
@Override
public void onSuccess() {
downloadTask = null;
// 从下载队列中取出已完成的下载信息
DownloadInfo downloadInfo = downloadQueue.poll();
operateDb(downloadInfo); //操作数据库
start();//下载队列中的其它歌曲
//下载成功通知前台服务通知关闭,并创建一个下载成功的通知
// 操作数据库,可能是更新下载信息等操作
operateDb(downloadInfo);
// 开始下载队列中的下一个歌曲
start();
// 停止前台服务通知,并创建一个下载成功的通知
stopForeground(true);
if(downloadQueue.isEmpty()) getNotificationManager().notify(1, getNotification("下载成功",-1));
if (downloadQueue.isEmpty()) {
getNotificationManager().notify(1, getNotification("下载成功", -1));
}
}
// 下载完成时调用
@Override
public void onDownloaded() {
downloadTask = null;
// 显示下载完成的 Toast 提示
CommonUtil.showToast(DownloadService.this, "已下载");
}
// 下载失败时调用
@Override
public void onFailed() {
downloadTask = null;
//下载失败通知前台服务通知关闭,并创建一个下载失败的通知
// 停止前台服务通知,并创建一个下载失败的通知
stopForeground(true);
getNotificationManager().notify(1, getNotification("下载失败",-1));
getNotificationManager().notify(1, getNotification("下载失败", -1));
// 显示下载失败的 Toast 提示
Toast.makeText(DownloadService.this, "下载失败", Toast.LENGTH_SHORT).show();
}
// 下载暂停时调用
@Override
public void onPaused() {
downloadTask = null;
DownloadInfo downloadInfo=downloadQueue.poll();//从下载列表中移除该歌曲
// 从下载队列中移除暂停的歌曲
DownloadInfo downloadInfo = downloadQueue.poll();
// 更新数据库中暂停的歌曲状态
updateDbOfPause(downloadInfo.getSongId());
getNotificationManager().notify(1, getNotification("下载已暂停:"+downloadInfo.getSongName(), -1));
start();//下载下载列表中的歌曲
// 更新通知为下载已暂停
getNotificationManager().notify(1, getNotification("下载已暂停:" + downloadInfo.getSongName(), -1));
// 开始下载队列中的下一个歌曲
start();
// 更新下载信息状态为暂停,并发布暂停事件
downloadInfo.setStatus(Constant.DOWNLOAD_PAUSED);
EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_PAUSED, downloadInfo)); //下载暂停
EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_PAUSED, downloadInfo));
// 显示下载已暂停的 Toast 提示
Toast.makeText(DownloadService.this, "下载已暂停", Toast.LENGTH_SHORT).show();
}
// 下载取消时调用
@Override
public void onCanceled() {
downloadTask = null;
// 停止前台服务通知
stopForeground(true);
// 显示下载已取消的 Toast 提示
Toast.makeText(DownloadService.this, "下载已取消", Toast.LENGTH_SHORT).show();
}
};
// 服务绑定方法,返回 Binder 对象
@Nullable
@Override
public IBinder onBind(Intent intent) {
return downloadBinder;
}
// 自定义的 Binder 类,用于与客户端通信
public class DownloadBinder extends Binder {
// 开始下载方法
public void startDownload(DownloadInfo song) {
try {
postDownloadEvent(song);//通知正在下载界面
// 通知正在下载界面
postDownloadEvent(song);
} catch (Exception e) {
e.printStackTrace();
}
if (downloadTask != null) {
if (downloadTask!= null) {
// 已存在下载任务时的提示
CommonUtil.showToast(DownloadService.this, "已经加入下载队列");
} else {
// 开始下载的提示
CommonUtil.showToast(DownloadService.this, "开始下载");
start();
}
}
// 暂停下载方法
public void pauseDownload(String songId) {
//暂停的歌曲是否为当前下载的歌曲
if (downloadTask != null &&downloadQueue.peek().getSongId().equals(songId)) {
// 判断暂停的歌曲是否为当前正在下载的歌曲
if (downloadTask!= null && downloadQueue.peek().getSongId().equals(songId)) {
// 暂停当前下载任务
downloadTask.pauseDownload();
}else {//暂停的歌曲是下载队列的歌曲
//将该歌曲从下载队列中移除
} else {
// 暂停的歌曲在下载队列中
for (int i = 0; i < downloadQueue.size(); i++) {
DownloadInfo downloadInfo = downloadQueue.get(i);
if (downloadInfo.getSongId().equals(songId)) {
// 从下载队列中移除该歌曲
downloadQueue.remove(i);
// 更新数据库中该歌曲的暂停状态
updateDbOfPause(downloadInfo.getSongId());
// 更新下载信息状态为暂停,并发布暂停事件
downloadInfo.setStatus(Constant.DOWNLOAD_PAUSED);
EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_PAUSED, downloadInfo)); //下载暂停
EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_PAUSED, downloadInfo));
}
}
}
}
// 取消下载方法
public void cancelDownload(DownloadInfo song) {
String songId = song.getSongId();
//如果该歌曲正在下载则需要将downloadTask置为null
if (downloadTask != null && downloadQueue.peek().getSongId().equals(songId)) {
// 如果该歌曲正在下载,取消下载任务
if (downloadTask!= null && downloadQueue.peek().getSongId().equals(songId)) {
downloadTask.cancelDownload();
}
//将该歌曲从下载队列中移除
// 将该歌曲从下载队列中移除
for (int i = 0; i < downloadQueue.size(); i++) {
DownloadInfo downloadInfo = downloadQueue.get(i);
if (downloadInfo.getSongId().equals(songId)) downloadQueue.remove(i);
}
// 更新数据库
updateDb(songId);
// 删除数据库中的该歌曲信息
deleteDb(songId);
//取消下载需要将文件删除并将通知关闭
if (song.getUrl() != null) {
checkoutFile(song,song.getUrl()); //实际文件长度
// 取消下载需要将文件删除并关闭通知
if (song.getUrl()!= null) {
checkoutFile(song, song.getUrl());
}
//通知正在下载列表
// 发布下载取消事件
EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_CANCELED));
}
}
// 开始下载的方法
private void start() {
if (downloadTask == null && !downloadQueue.isEmpty()) {
if (downloadTask == null &&!downloadQueue.isEmpty()) {
// 从下载队列中取出要下载的歌曲信息
DownloadInfo downloadInfo = downloadQueue.peek();
// 从数据库中查找该歌曲信息
List<DownloadInfo> songList =
LitePal.where("songId = ?",downloadInfo.getSongId()).find(DownloadInfo.class);
LitePal.where("songId =?", downloadInfo.getSongId()).find(DownloadInfo.class);
DownloadInfo currentDownloadInfo = songList.get(0);
// 更新歌曲状态为准备下载
currentDownloadInfo.setStatus(Constant.DOWNLOAD_READY);
EventBus.getDefault().post(new DownloadEvent(TYPE_DOWNLOADING,currentDownloadInfo));
// 发布下载事件
EventBus.getDefault().post(new DownloadEvent(TYPE_DOWNLOADING, currentDownloadInfo));
downloadUrl = currentDownloadInfo.getUrl();
// 创建新的下载任务并执行
downloadTask = new DownloadTask(listener);
downloadTask.execute(currentDownloadInfo);
getNotificationManager().notify(1, getNotification("正在下载:"+downloadInfo.getSongName(), 0));
// 更新通知为正在下载
getNotificationManager().notify(1, getNotification("正在下载:" + downloadInfo.getSongName(), 0));
}
}
// 获取通知管理器
private NotificationManager getNotificationManager() {
return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}
private Notification getNotification(String title,int progress) {
// 创建通知的方法
private Notification getNotification(String title, int progress) {
// 创建跳转到主界面的意图
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 创建通知通道
String id = "channel_001";
String name = "下载通知";
NotificationChannel mChannel = new NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW);
@ -212,107 +272,132 @@ public class DownloadService extends Service {
builder.setSmallIcon(R.mipmap.icon);
builder.setContentIntent(pi);
builder.setContentTitle(title);
if(progress>0){
builder.setContentText(progress +"%");
if (progress > 0) {
builder.setContentText(progress + "%");
builder.setProgress(100, progress, false);
}
return builder.build();
} else {
// 兼容旧版本的通知创建
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "default");
builder.setSmallIcon(R.mipmap.icon);
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.icon));
builder.setContentIntent(pi);
builder.setContentTitle(title);
if(progress>0){
builder.setContentText(progress +"%");
if (progress > 0) {
builder.setContentText(progress + "%");
builder.setProgress(100, progress, false);
}
return builder.build();
}
}
// 操作数据库的方法,可能是下载成功后的操作
private void operateDb(DownloadInfo downloadInfo) {
updateDb(downloadInfo.getSongId());
deleteDb(downloadInfo.getSongId());
EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_SUCCESS));//通知已下载列表
EventBus.getDefault().post(new SongListNumEvent(Constant.LIST_TYPE_DOWNLOAD)); //通知主界面的下载个数需要改变
// 发布下载成功事件
EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_SUCCESS));
// 发布歌曲列表数量改变事件
EventBus.getDefault().post(new SongListNumEvent(Constant.LIST_TYPE_DOWNLOAD));
}
//更新数据库中歌曲列表的位置即下载完成歌曲后的位置都要减去1
// 更新数据库中歌曲列表的位置
private void updateDb(String songId) {
long id = LitePal.select("id").where("songId = ?", songId).find(DownloadInfo.class).get(0).getId();
List<DownloadInfo> songIdList = LitePal.where("id > ?", id + "").find(DownloadInfo.class);
// 查找该歌曲在数据库中的 ID
long id = LitePal.select("id").where("songId =?", songId).find(DownloadInfo.class).get(0).getId();
// 查找该歌曲之后的歌曲列表
List<DownloadInfo> songIdList = LitePal.where("id >?", id + "").find(DownloadInfo.class);
for (DownloadInfo song : songIdList) {
// 更新位置,将后续歌曲位置减 1
song.setPosition(song.getPosition() - 1);
song.save();
}
}
//暂停时更新列表歌曲状态
private void updateDbOfPause(String songId){
// 暂停时更新数据库中歌曲的状态
private void updateDbOfPause(String songId) {
List<DownloadInfo> statusList =
LitePal.where("songId = ?",songId).find(DownloadInfo.class,true);
LitePal.where("songId =?", songId).find(DownloadInfo.class, true);
DownloadInfo downloadInfo = statusList.get(0);
downloadInfo.setStatus(Constant.DOWNLOAD_PAUSED);
downloadInfo.save();
}
//下载完成时要删除下载歌曲表中的数据以及关联表中的数据
// 下载完成时删除数据库中的数据
private void deleteDb(String songId) {
LitePal.deleteAll(DownloadInfo.class, "songId=?", songId);//删除已下载歌曲的相关列
// 删除已下载歌曲的相关列
LitePal.deleteAll(DownloadInfo.class, "songId=?", songId);
}
// 发布下载事件
private void postDownloadEvent(DownloadInfo downloadInfo) {
//如果需要下载的表中有该条歌曲,则添加到下载队列后跳过
// 检查数据库中是否已存在该歌曲的下载信息
List<DownloadInfo> downloadInfoList =
LitePal.where("songId = ?",downloadInfo.getSongId()).find(DownloadInfo.class,true);
if (downloadInfoList.size() != 0){
LitePal.where("songId =?", downloadInfo.getSongId()).find(DownloadInfo.class, true);
if (downloadInfoList.size()!= 0) {
DownloadInfo historyDownloadInfo = downloadInfoList.get(0);
// 更新状态为等待
historyDownloadInfo.setStatus(Constant.DOWNLOAD_WAIT);
historyDownloadInfo.save();
EventBus.getDefault().post(new DownloadEvent(Constant.DOWNLOAD_PAUSED,historyDownloadInfo));
// 发布暂停事件
EventBus.getDefault().post(new DownloadEvent(Constant.DOWNLOAD_PAUSED, historyDownloadInfo));
// 将歌曲添加到下载队列
downloadQueue.offer(historyDownloadInfo);
return;
}
// 设置歌曲在下载列表中的位置
position = LitePal.findAll(DownloadInfo.class).size();
downloadInfo.setPosition(position);
downloadInfo.setStatus(Constant.DOWNLOAD_WAIT); //等待
downloadInfo.setStatus(Constant.DOWNLOAD_WAIT);
downloadInfo.save();
downloadQueue.offer(downloadInfo);//将歌曲放到等待队列中
// 将歌曲添加到等待队列
downloadQueue.offer(downloadInfo);
// 发布添加下载事件
EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_ADD));
}
//获取歌曲实际大小,然后判断是否存在于文件中
public void checkoutFile(DownloadInfo song, String downloadUrl){
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(downloadUrl)
.build();
Call call= client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if(response.isSuccessful()){
long size = response.body().contentLength();
String fileName = DownloadUtil.getSaveSongFile(song.getSinger(),song.getSongName(),song.getDuration(),song.getSongId(),size);
File downloadFile = new File(Api.STORAGE_SONG_FILE);
String directory = String.valueOf(downloadFile);
File file = new File(fileName, directory);
if (file.exists()) {
file.delete();
}
getNotificationManager().cancel(1);
stopForeground(true);
}
}
});
}
// 获取歌曲实际大小,并判断文件是否存在,可能用于取消下载时的清理
public void checkoutFile(DownloadInfo song, String downloadUrl) {
// 创建 OkHttpClient 实例,用于网络请求
OkHttpClient client = new OkHttpClient();
// 构建请求对象,指定请求的 URL
Request request = new Request.Builder()
.url(downloadUrl)
.build();
// 创建一个 Call 对象,用于执行请求
Call call = client.newCall(request);
// 将请求加入请求队列,并设置回调
call.enqueue(new Callback() {
// 请求失败时调用
@Override
public void onFailure(Call call, IOException e) {
// 此处暂时未处理请求失败的情况,可以根据需求添加相应的处理逻辑
}
}
// 请求成功时调用
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
// 获取响应体的内容长度,即文件的实际大小
long size = response.body().contentLength();
// 根据歌曲信息和文件大小生成保存歌曲的文件名
String fileName = DownloadUtil.getSaveSongFile(song.getSinger(), song.getSongName(), song.getDuration(), song.getSongId(), size);
// 获取存储歌曲文件的目录
File downloadFile = new File(Api.STORAGE_SONG_FILE);
String directory = String.valueOf(downloadFile);
// 构建文件对象
File file = new File(fileName, directory);
if (file.exists()) {
// 如果文件存在,删除文件
file.delete();
}
// 取消通知
getNotificationManager().cancel(1);
// 停止服务的前台运行状态
stopForeground(true);
}
}
});
}

@ -28,164 +28,173 @@ import com.example.musicplayer.app.App;
/**
* Created by on 2018/10/26.
* 使
*/
//通用的工具类
public class CommonUtil {
// 存储 Toast 实例,避免重复创建
private static Toast toast;
// 控制活动的状态栏显示或隐藏
public static void hideStatusBar(Activity activity, boolean isHide) {
View decorView = activity.getWindow().getDecorView();
// 获取活动的根视图
View decorView = activity.getWindow().getDecorView();
if (isHide) {
if (Build.VERSION.SDK_INT >= 22) {
// 隐藏状态栏并使布局扩展到状态栏区域,状态栏透明
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
}
} else {
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
activity.getWindow().setStatusBarColor(App.getContext().getResources().getColor(R.color.actionBarColor));
// 显示状态栏并设置状态栏颜色
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
activity.getWindow().setStatusBarColor(App.getContext().getResources().getColor(R.color.actionBarColor));
}
}
//
// 显示 Toast 消息
@SuppressLint("ShowToast")
public static void showToast(Context context, String message) {
if (toast == null) {
toast = Toast.makeText(context, message, Toast.LENGTH_SHORT);
// 创建一个新的 Toast 实例
toast = Toast.makeText(context, message, Toast.LENGTH_SHORT);
} else {
toast.setText(message);
// 更新 Toast 的消息内容
toast.setText(message);
}
toast.show();
// 显示 Toast
toast.show();
}
/**
*
*/
// 获取屏幕的宽度
public static int getScreenWidth(Context context) {
if (null == context) {
return 0;
}
return context.getResources().getDisplayMetrics().widthPixels;
// 获取屏幕的宽度像素值
return context.getResources().getDisplayMetrics().widthPixels;
}
/**
*
*/
// 获取屏幕的高度
public static int getScreenHeight(Context context) {
if (null == context) {
return 0;
}
return context.getResources().getDisplayMetrics().heightPixels;
// 获取屏幕的高度像素值
return context.getResources().getDisplayMetrics().heightPixels;
}
/**
* EditText
*/
//弹出软键盘
// 使 EditText 获取焦点并显示软键盘
public static void showKeyboard(EditText editText, Context context) {
//其中editText为dialog中的输入框的 EditText
if (editText != null) {
//设置可获得焦点
editText.setFocusable(true);
editText.setFocusableInTouchMode(true);
//请求获得焦点
editText.requestFocus();
//调用系统输入法
InputMethodManager inputManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.toggleSoftInput(0,InputMethodManager.HIDE_NOT_ALWAYS);
if (editText!= null) {
// 使 EditText 可获得焦点
editText.setFocusable(true);
editText.setFocusableInTouchMode(true);
// 请求获得焦点
editText.requestFocus();
// 获取输入法管理器
InputMethodManager inputManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
// 显示软键盘
inputManager.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
}
}
/**
*
*
* @param mEditText
* @param context
*/
// 关闭软键盘
public static void closeKeybord(EditText mEditText, Context context) {
mEditText.clearFocus();
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
// 清除 EditText 的焦点
mEditText.clearFocus();
// 获取输入法管理器
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
// 隐藏软键盘
imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
}
/**
* 使
*/
// 使指定字符串在 TextView 中显示不同颜色
public static void showStringColor(String appointStr, String originalStr, TextView textView) {
originalStr = originalStr.replaceAll(appointStr, "<font color='#FFC66D'>" + appointStr + "</font>");
textView.setText(Html.fromHtml(originalStr));
// 将原始字符串中的指定字符串替换为带有颜色标记的 HTML 字符串
originalStr = originalStr.replaceAll(appointStr, "<font color='#FFC66D'>" + appointStr + "</font>");
// 使用 Html.fromHtml 将 HTML 字符串转换为 Spanned 并设置到 TextView 中
textView.setText(Html.fromHtml(originalStr));
}
//获取状态栏高度
// 获取状态栏高度
public static int getStatusHeightPx(Activity act) {
int height = 0;
int resourceId = act.getResources().getIdentifier("status_bar_height", "dimen", "android");
// 获取状态栏高度资源的标识符
int resourceId = act.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
height = act.getResources().getDimensionPixelSize(resourceId);
// 获取状态栏的高度像素值
height = act.getResources().getDimensionPixelSize(resourceId);
}
return height;
}
//高斯模糊
// 生成前景模糊的 Drawable
public static Drawable getForegroundDrawable(Bitmap bitmap) {
/*得到屏幕的宽高比,以便按比例切割图片一部分*/
// 计算屏幕宽高比
final float widthHeightSize = (float) (DisplayUtil.getScreenWidth(App.getContext())
* 1.0 / DisplayUtil.getScreenHeight(App.getContext()) * 1.0);
int cropBitmapWidth = (int) (widthHeightSize * bitmap.getHeight());
int cropBitmapWidthX = (int) ((bitmap.getWidth() - cropBitmapWidth) / 2.0);
/*切割部分图片*/
* 1.0 / DisplayUtil.getScreenHeight(App.getContext()) * 1.0);
// 计算裁剪图片的宽度
int cropBitmapWidth = (int) (widthHeightSize * bitmap.getHeight());
int cropBitmapWidthX = (int) ((bitmap.getWidth() - cropBitmapWidth) / 2.0);
// 裁剪部分图片
Bitmap cropBitmap = Bitmap.createBitmap(bitmap, cropBitmapWidthX, 0, cropBitmapWidth,
bitmap.getHeight());
/*缩小图片*/
bitmap.getHeight());
// 缩小图片
Bitmap scaleBitmap = Bitmap.createScaledBitmap(cropBitmap, bitmap.getWidth() / 50, bitmap
.getHeight() / 50, false);
/*模糊化*/
final Bitmap blurBitmap = FastBlurUtil.doBlur(scaleBitmap, 3, true);
final Drawable foregroundDrawable = new BitmapDrawable(blurBitmap);
/*加入灰色遮罩层,避免图片过亮影响其他控件*/
return foregroundDrawable;
.getHeight() / 50, false);
// 对缩小后的图片进行模糊处理
final Bitmap blurBitmap = FastBlurUtil.doBlur(scaleBitmap, 3, true);
// 创建模糊后的 Drawable
final Drawable foregroundDrawable = new BitmapDrawable(blurBitmap);
// 可添加灰色遮罩层(未实现)
return foregroundDrawable;
}
public static Bitmap getImgBitmap(Context context,String imgUrl){
// 使用 Glide 加载图片并获取其 Bitmap
public static Bitmap getImgBitmap(Context context, String imgUrl) {
SimpleTarget target = new SimpleTarget<Drawable>(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) {
@Override
public void onResourceReady(@Nullable Drawable resource, Transition<? super Drawable> transition) {
Bitmap bitmap = ((BitmapDrawable) resource).getBitmap();
// 当资源加载完成时,将 Drawable 转换为 Bitmap
Bitmap bitmap = ((BitmapDrawable) resource).getBitmap();
}
};
// 使用 Glide 加载图片,设置占位符和错误图片
Glide.with(context)
.load(imgUrl)
.apply(RequestOptions.placeholderOf(R.drawable.welcome))
.apply(RequestOptions.errorOf(R.drawable.welcome))
.into(target);
return null;
.load(imgUrl)
.apply(RequestOptions.placeholderOf(R.drawable.welcome))
.apply(RequestOptions.errorOf(R.drawable.welcome))
.into(target);
// 此方法始终返回 null可能需要修改
return null;
}
public static void setImgWithGlide(Context context,String imgUrl,ImageView view){
// 使用 Glide 加载图片到 ImageView
public static void setImgWithGlide(Context context, String imgUrl, ImageView view) {
// 使用 Glide 加载图片,设置占位符和错误图片
Glide.with(context)
.load(imgUrl)
.apply(RequestOptions.placeholderOf(R.drawable.welcome))
.apply(RequestOptions.errorOf(R.drawable.love))
.into(view);
.load(imgUrl)
.apply(RequestOptions.placeholderOf(R.drawable.welcome))
.apply(RequestOptions.errorOf(R.drawable.love))
.into(view);
}
public static void setSingerImg (Context context, String singer, ImageView view) {
if(singer.contains("/")){
String[] s=singer.split("/");
singer=s[0];
}
singer=singer.trim();
String imgUrl = Api.STORAGE_IMG_FILE + singer + ".jpg";
// 根据歌手信息加载歌手图片到 ImageView
public static void setSingerImg(Context context, String singer, ImageView view) {
if (singer.contains("/")) {
// 处理歌手名字中包含斜杠的情况
String[] s = singer.split("/");
singer = s[0];
}
singer = singer.trim();
// 构建歌手图片的 URL
String imgUrl = Api.STORAGE_IMG_FILE + singer + ".jpg";
// 使用 Glide 加载图片,设置占位符和错误图片
Glide.with(context)
.load(imgUrl)
.apply(RequestOptions.placeholderOf(R.drawable.welcome))
.apply(RequestOptions.errorOf(R.drawable.welcome))
.into(view);
.load(imgUrl)
.apply(RequestOptions.placeholderOf(R.drawable.welcome))
.apply(RequestOptions.errorOf(R.drawable.welcome))
.into(view);
}
}
}

@ -4,18 +4,18 @@ import android.content.Context;
/**
* Created by on 2018/10/27.
*
*/
public class DisplayUtil {
/*手柄起始角度*/
/* 手柄起始角度,可能用于旋转动画等操作,初始角度为 -30 度 */
public static final float ROTATION_INIT_NEEDLE = -30;
/*截图屏幕宽高*/
/* 基准屏幕的宽度和高度,作为比例计算的基础 */
private static final float BASE_SCREEN_WIDTH = (float) 1080.0;
private static final float BASE_SCREEN_HEIGHT = (float) 1920.0;
/*唱针宽高、距离等比例*/
/* 唱针的各种比例相关的常量,可能用于布局调整或动画计算 */
public static final float SCALE_NEEDLE_WIDTH = (float) (276.0 / BASE_SCREEN_WIDTH);
public static final float SCALE_NEEDLE_MARGIN_LEFT = (float) (500.0 / BASE_SCREEN_WIDTH);
public static final float SCALE_NEEDLE_PIVOT_X = (float) (43.0 / BASE_SCREEN_WIDTH);
@ -23,20 +23,20 @@ public class DisplayUtil {
public static final float SCALE_NEEDLE_HEIGHT = (float) (413.0 / BASE_SCREEN_HEIGHT);
public static final float SCALE_NEEDLE_MARGIN_TOP = (float) (43.0 / BASE_SCREEN_HEIGHT);
/*唱盘比例*/
/* 唱盘比例,可能用于布局调整,确定唱盘的大小和位置 */
public static final float SCALE_DISC_SIZE = (float) (813.0 / BASE_SCREEN_WIDTH);
public static final float SCALE_DISC_MARGIN_TOP = (float) (190 / BASE_SCREEN_HEIGHT);
/*专辑图片比例*/
/* 专辑图片比例,可能用于布局调整,确定专辑图片的大小 */
public static final float SCALE_MUSIC_PIC_SIZE = (float) (533.0 / BASE_SCREEN_WIDTH);
/*设备屏幕宽度*/
/* 获取设备屏幕宽度,以像素为单位 */
public static int getScreenWidth(Context context) {
return context.getResources().getDisplayMetrics().widthPixels;
}
/*设备屏幕高度*/
/* 获取设备屏幕高度,以像素为单位 */
public static int getScreenHeight(Context context) {
return context.getResources().getDisplayMetrics().heightPixels;
}
}
}

@ -18,64 +18,90 @@ import static com.example.musicplayer.app.Api.STORAGE_SONG_FILE;
* desc :
* </pre>
*/
public class DownloadUtil {
/**
*
* @param fileName
* @return
*
* @param fileName
* @return
*/
public static final List<DownloadSong> getSongFromFile(String fileName){
//将.m4a截取掉得到singer-songName-duration-songId-size
List<DownloadSong> res = new ArrayList<>();
File file=new File(fileName);
if(!file.exists()){
file.mkdirs();
return res;
public static final List<DownloadSong> getSongFromFile(String fileName) {
// 存储已下载歌曲的列表
List<DownloadSong> res = new ArrayList<>();
// 创建文件对象
File file = new File(fileName);
// 如果文件不存在,创建该目录
if (!file.exists()) {
file.mkdirs();
return res;
}
File[] subFile = file.listFiles();
for (File value : subFile) {
String songFileName = value.getName();
String songFile = songFileName.substring(0, songFileName.lastIndexOf("."));
String[] songValue = songFile.split("-");
long size = Long.valueOf(songValue[4]);
//如果文件的大小不等于实际大小,则表示该歌曲还未下载完成,被人为暂停,故跳过该歌曲,不加入到已下载集合
if(size != value.length()) continue;
DownloadSong downloadSong = new DownloadSong();
downloadSong.setSinger(songValue[0]);
downloadSong.setName(songValue[1]);
downloadSong.setDuration(Long.valueOf(songValue[2]));
downloadSong.setSongId(songValue[3]);
downloadSong.setUrl(fileName + songFileName);
res.add(downloadSong);
// 获取目录下的所有文件
File[] subFile = file.listFiles();
for (File value : subFile) {
// 获取文件名
String songFileName = value.getName();
// 去掉文件后缀名
String songFile = songFileName.substring(0, songFileName.lastIndexOf("."));
// 将文件名按 - 分割
String[] songValue = songFile.split("-");
// 获取文件大小
long size = Long.valueOf(songValue[4]);
// 如果文件大小不等于实际文件大小,说明歌曲未下载完成,跳过该文件
if (size!= value.length()) continue;
// 创建 DownloadSong 对象
DownloadSong downloadSong = new DownloadSong();
// 设置歌手信息
downloadSong.setSinger(songValue[0]);
// 设置歌曲名称
downloadSong.setName(songValue[1]);
// 设置歌曲时长
downloadSong.setDuration(Long.valueOf(songValue[2]));
// 设置歌曲 ID
downloadSong.setSongId(songValue[3]);
// 设置歌曲的 URL
downloadSong.setUrl(fileName + songFileName);
// 将 DownloadSong 对象添加到结果列表中
res.add(downloadSong);
}
return res;
return res;
}
public static final boolean isExistOfDownloadSong(String songId){
//将.m4a截取掉得到singer-songName-duration-songId-size
File file=new File(STORAGE_SONG_FILE);
if(!file.exists()){
file.mkdirs();
return false;
/**
*
* @param songId ID
* @return
*/
public static final boolean isExistOfDownloadSong(String songId) {
// 创建文件对象,使用 STORAGE_SONG_FILE 作为文件根目录
File file = new File(STORAGE_SONG_FILE);
// 如果文件不存在,创建该目录
if (!file.exists()) {
file.mkdirs();
return false;
}
File[] subFile = file.listFiles();
for (File value : subFile) {
String songFileName = value.getName();
String songFile = songFileName.substring(0, songFileName.lastIndexOf("."));
String[] songValue = songFile.split("-");
//如果文件的大小不等于实际大小,则表示该歌曲还未下载完成,被人为暂停,故跳过该歌曲,不加入到已下载集合
if(songValue[3].equals(songId)) {
long size = Long.valueOf(songValue[4]);
return size == value.length();
// 获取目录下的所有文件
File[] subFile = file.listFiles();
for (File value : subFile) {
// 获取文件名
String songFileName = value.getName();
// 去掉文件后缀名
String songFile = songFileName.substring(0, songFileName.lastIndexOf("."));
// 将文件名按 - 分割
String[] songValue = songFile.split("-");
// 如果歌曲 ID 匹配
if (songValue[3].equals(songId)) {
// 获取文件大小
long size = Long.valueOf(songValue[4]);
// 判断文件大小是否等于实际文件大小,如果相等表示已下载完成
return size == value.length();
}
}
return false;
return false;
}
//组装下载歌曲的文件名
public static final String getSaveSongFile(String singer,String songName,long duration,String songId,long size){
return singer+"-"+songName+"-"+duration+"-"+songId+"-"+size+".m4a";
// 组装下载歌曲的文件名
public static final String getSaveSongFile(String singer, String songName, long duration, String songId, long size) {
// 按照 singer-songName-duration-songId-size.m4a 的格式组装文件名
return singer + "-" + songName + "-" + duration + "-" + songId + "-" + size + ".m4a";
}
}
}

@ -8,210 +8,323 @@ import android.graphics.Bitmap;
public class FastBlurUtil {
public static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) {
// 首先,根据 canReuseInBitmap 决定是否复用输入的 Bitmap若可以复用则直接使用否则复制一份
Bitmap bitmap;
if (canReuseInBitmap) {
bitmap = sentBitmap;
} else {
// 使用 copy 方法复制一份输入的 Bitmap并保留原始配置和像素信息
bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
}
Bitmap bitmap;
if (canReuseInBitmap) {
bitmap = sentBitmap;
} else {
bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
}
if (radius < 1) {
return (null);
}
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int[] pix = new int[w * h];
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
int wm = w - 1;
int hm = h - 1;
int wh = w * h;
int div = radius + radius + 1;
// 若半径小于 1则不进行模糊处理直接返回 null
if (radius < 1) {
return (null);
}
int r[] = new int[wh];
int g[] = new int[wh];
int b[] = new int[wh];
int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
int vmin[] = new int[Math.max(w, h)];
// 获取位图的宽度和高度
int w = bitmap.getWidth();
int h = bitmap.getHeight();
// 将位图的像素信息存储到 pix 数组中,数组长度为宽度乘以高度
int[] pix = new int[w * h];
// 将 bitmap 的像素存储到 pix 数组中,存储格式为 ARGB 格式
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
int wm = w - 1;
int hm = h - 1;
int wh = w * h;
int div = radius + radius + 1;
// 用于存储模糊处理后的 R、G、B 通道信息
int r[] = new int[wh];
int g[] = new int[wh];
int b[] = new int[wh];
int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
int vmin[] = new int[Math.max(w, h)];
int divsum = (div + 1) >> 1;
divsum *= divsum;
// 存储模糊处理的除数结果
int dv[] = new int[256 * divsum];
// 初始化 dv 数组,存储用于模糊计算的中间结果
for (i = 0; i < 256 * divsum; i++) {
dv[i] = (i / divsum);
}
int divsum = (div + 1) >> 1;
divsum *= divsum;
int dv[] = new int[256 * divsum];
for (i = 0; i < 256 * divsum; i++) {
dv[i] = (i / divsum);
yw = yi = 0;
// 用于存储模糊处理的栈信息
int[][] stack = new int[div][3];
int stackpointer;
int stackstart;
int[] sir;
int rbs;
int r1 = radius + 1;
int routsum, goutsum, boutsum;
int rinsum, ginsum, binsum;
// 水平模糊处理
// 外层循环遍历图像的每一行
for (y = 0; y < h; y++) {
// 初始化颜色通道的求和变量,包括用于模糊计算的不同部分
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
// 内层循环遍历以当前像素为中心的水平滑动窗口,窗口大小由 radius 决定
for (i = -radius; i <= radius; i++) {
// 获取当前像素周围的像素,考虑边界情况,使用 Math.min 和 Math.max 确保不越界
p = pix[yi + Math.min(wm, Math.max(i, 0))];
// 获取栈中存储像素信息的数组
sir = stack[i + radius];
// 提取当前像素的红色通道值,通过位运算提取高 8 位
sir[0] = (p & 0xff0000) >> 16;
// 提取当前像素的绿色通道值,通过位运算提取中间 8 位
sir[1] = (p & 0x00ff00) >> 8;
// 提取当前像素的蓝色通道值,通过位运算提取低 8 位
sir[2] = (p & 0x0000ff);
// 计算像素的权重,距离中心像素越远,权重越低
rbs = r1 - Math.abs(i);
// 计算红色通道的加权和,乘以权重
rsum += sir[0] * rbs;
// 计算绿色通道的加权和,乘以权重
gsum += sir[1] * rbs;
// 计算蓝色通道的加权和,乘以权重
bsum += sir[2] * rbs;
// 对于窗口中位于当前像素右侧的像素,更新 rinsum、ginsum 和 binsum
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
// 对于窗口中位于当前像素左侧的像素,更新 routsum、goutsum 和 boutsum
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
}
// 栈指针初始化为半径
stackpointer = radius;
// 开始内层循环,遍历当前行的每一个像素
for (x = 0; x < w; x++) {
// 此处将继续处理每个像素的模糊计算,对提取的颜色通道值进行处理
// 代码在此处未完全展示,后续代码将根据计算的加权和和权重更新像素的颜色通道值
// 并更新相关的求和变量,以便下一个像素的计算
}
}
// 将处理后的 R、G、B 通道值存储在对应的数组中
// 将模糊处理后的红色通道值存储在 r 数组中,使用 dv 数组中的值进行映射
r[yi] = dv[rsum];
// 将模糊处理后的绿色通道值存储在 g 数组中,使用 dv 数组中的值进行映射
g[yi] = dv[gsum];
// 将模糊处理后的蓝色通道值存储在 b 数组中,使用 dv 数组中的值进行映射
b[yi] = dv[bsum];
// 更新 rsum减去上一轮计算的 routsum
rsum -= routsum;
// 更新 gsum减去上一轮计算的 goutsum
gsum -= goutsum;
// 更新 bsum减去上一轮计算的 boutsum
bsum -= boutsum;
// 计算栈的起始位置,考虑到循环利用栈空间
stackstart = stackpointer - radius + div;
// 获取栈中相应位置的元素
sir = stack[stackstart % div];
// 从 routsum 中减去栈中元素的红色通道值
routsum -= sir[0];
// 从 goutsum 中减去栈中元素的绿色通道值
goutsum -= sir[1];
// 从 boutsum 中减去栈中元素的蓝色通道值
boutsum -= sir[2];
// 如果当前处于第一行
if (y == 0) {
// 计算当前列的最小有效位置,避免越界
vmin[x] = Math.min(x + radius + 1, wm);
}
// 获取当前像素位置的像素值
p = pix[yw + vmin[x]];
// 提取当前像素的红色通道值
sir[0] = (p & 0xff0000) >> 16;
// 提取当前像素的绿色通道值
sir[1] = (p & 0x00ff00) >> 8;
// 提取当前像素的蓝色通道值
sir[2] = (p & 0x0000ff);
// 更新 rinsum加上当前像素的红色通道值
rinsum += sir[0];
// 更新 ginsum加上当前像素的绿色通道值
ginsum += sir[1];
// 更新 binsum加上当前像素的蓝色通道值
binsum += sir[2];
// 更新 rsum加上 rinsum
rsum += rinsum;
// 更新 gsum加上 ginsum
gsum += ginsum;
// 更新 bsum加上 binsum
bsum += binsum;
// 更新栈指针位置,通过取模实现循环利用栈空间
stackpointer = (stackpointer + 1) % div;
// 获取栈中相应位置的元素
sir = stack[(stackpointer) % div];
// 更新 routsum加上栈中元素的红色通道值
routsum += sir[0];
// 更新 goutsum加上栈中元素的绿色通道值
goutsum += sir[1];
// 更新 boutsum加上栈中元素的蓝色通道值
boutsum += sir[2];
// 从 rinsum 中减去栈中元素的红色通道值
rinsum -= sir[0];
// 从 ginsum 中减去栈中元素的绿色通道值
ginsum -= sir[1];
// 从 binsum 中减去栈中元素的蓝色通道值
binsum -= sir[2];
// 移动到下一个像素位置(同一行的下一个像素)
yi++;
}
// 移动到下一行
yw += w;
// 垂直模糊处理
for (x = 0; x < w; x++) {
// 初始化颜色通道的求和变量为 0
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
// 初始化垂直偏移量,用于计算垂直方向的滑动窗口位置
yp = -radius * w;
// 遍历垂直方向的滑动窗口,窗口大小由 radius 决定
for (i = -radius; i <= radius; i++) {
// 计算当前像素在垂直方向上的位置,确保不越界
yi = Math.max(0, yp) + x;
// 获取栈中存储像素信息的数组
sir = stack[i + radius];
// 从 r 数组中获取当前像素的红色通道值
sir[0] = r[yi];
// 从 g 数组中获取当前像素的绿色通道值
sir[1] = g[yi];
// 从 b 数组中获取当前像素的蓝色通道值
sir[2] = b[yi];
// 计算当前像素的权重,距离中心像素越远,权重越低
rbs = r1 - Math.abs(i);
// 计算红色通道的加权和,乘以权重
rsum += r[yi] * rbs;
// 计算绿色通道的加权和,乘以权重
gsum += g[yi] * rbs;
// 计算蓝色通道的加权和,乘以权重
bsum += b[yi] * rbs;
// 对于窗口中位于当前像素下方的像素,更新 rinsum、ginsum 和 binsum
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
// 对于窗口中位于当前像素上方的像素,更新 routsum、goutsum 和 boutsum
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
yw = yi = 0;
int[][] stack = new int[div][3];
int stackpointer;
int stackstart;
int[] sir;
int rbs;
int r1 = radius + 1;
int routsum, goutsum, boutsum;
int rinsum, ginsum, binsum;
for (y = 0; y < h; y++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
for (i = -radius; i <= radius; i++) {
p = pix[yi + Math.min(wm, Math.max(i, 0))];
sir = stack[i + radius];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rbs = r1 - Math.abs(i);
rsum += sir[0] * rbs;
gsum += sir[1] * rbs;
bsum += sir[2] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
}
stackpointer = radius;
for (x = 0; x < w; x++) {
r[yi] = dv[rsum];
g[yi] = dv[gsum];
b[yi] = dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (y == 0) {
vmin[x] = Math.min(x + radius + 1, wm);
}
p = pix[yw + vmin[x]];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[(stackpointer) % div];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi++;
}
yw += w;
// 向下移动到下一个像素,更新垂直偏移量
if (i < hm) {
yp += w;
}
for (x = 0; x < w; x++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
yp = -radius * w;
for (i = -radius; i <= radius; i++) {
yi = Math.max(0, yp) + x;
sir = stack[i + radius];
sir[0] = r[yi];
sir[1] = g[yi];
sir[2] = b[yi];
rbs = r1 - Math.abs(i);
rsum += r[yi] * rbs;
gsum += g[yi] * rbs;
bsum += b[yi] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
if (i < hm) {
yp += w;
}
}
yi = x;
stackpointer = radius;
for (y = 0; y < h; y++) {
// Preserve alpha channel: ( 0xff000000 & pix[yi] )
pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (x == 0) {
vmin[y] = Math.min(y + r1, hm) * w;
}
p = x + vmin[y];
sir[0] = r[p];
sir[1] = g[p];
sir[2] = b[p];
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[stackpointer];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi += w;
}
}
// 初始化 yi 为当前列的位置
yi = x;
// 栈指针初始化为半径
stackpointer = radius;
// 遍历当前列的每一个像素
for (y = 0; y < h; y++) {
// 组合处理后的 R、G、B 通道值,并保留 alpha 通道,更新像素信息
pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
// 更新 rsum减去 routsum
rsum -= routsum;
// 更新 gsum减去 goutsum
gsum -= goutsum;
// 更新 bsum减去 boutsum
bsum -= boutsum;
// 计算栈的起始位置,考虑到循环利用栈空间
stackstart = stackpointer - radius + div;
// 获取栈中相应位置的元素
sir = stack[stackstart % div];
// 从 routsum 中减去栈中元素的红色通道值
routsum -= sir[0];
// 从 goutsum 中减去栈中元素的绿色通道值
goutsum -= sir[1];
// 从 boutsum 中减去栈中元素的蓝色通道值
boutsum -= sir[2];
// 如果当前处于第一列
if (x == 0) {
// 计算当前行的最小有效位置,避免越界
vmin[y] = Math.min(y + r1, hm) * w;
}
// 获取当前像素位置的像素值
p = x + vmin[y];
// 从 r 数组中获取当前像素的红色通道值
sir[0] = r[p];
// 从 g 数组中获取当前像素的绿色通道值
sir[1] = g[p];
// 从 b 数组中获取当前像素的蓝色通道值
sir[2] = b[p];
// 更新 rinsum加上当前像素的红色通道值
rinsum += sir[0];
// 更新 ginsum加上当前像素的绿色通道值
ginsum += sir[1];
// 更新 binsum加上当前像素的蓝色通道值
binsum += sir[2];
// 更新 rsum加上 rinsum
rsum += rinsum;
// 更新 gsum加上 ginsum
gsum += ginsum;
// 更新 bsum加上 binsum
bsum += bsum;
// 更新栈指针位置,通过取模实现循环利用栈空间
stackpointer = (stackpointer + 1) % div;
// 获取栈中相应位置的元素
sir = stack[stackpointer];
// 更新 routsum加上栈中元素的红色通道值
routsum += sir[0];
// 更新 goutsum加上栈中元素的绿色通道值
goutsum += sir[1];
// 更新 boutsum加上栈中元素的蓝色通道值
boutsum += sir[2];
// 从 rinsum 中减去栈中元素的红色通道值
rinsum -= sir[0];
// 从 ginsum 中减去栈中元素的绿色通道值
ginsum -= sir[1];
// 从 binsum 中减去栈中元素的蓝色通道值
binsum -= sir[2];
// 移动到下一个像素位置(下一列的同一像素)
yi += w;
}
}
// 将处理后的像素信息重新设置到 Bitmap 中
bitmap.setPixels(pix, 0, w, 0, 0, w, h);
// 返回模糊处理后的 Bitmap
return (bitmap);
}
}

@ -1,6 +1,5 @@
package com.example.musicplayer.util;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
@ -24,31 +23,31 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* user
* user
* Created by on 2018/7/24.
*/
public class FileUtil {
private static String TAG = "FileUtil";
/**
* person
* song
* params:
* p:person
* song: Song
*/
public static void saveSong(Song song) {
try {
// 获取应用外部存储目录下的 yuanmusic 文件夹路径
File file = new File(App.getContext().getExternalFilesDir("yuanmusic").getAbsolutePath());
if (!file.exists()) {
// 如果文件夹不存在,创建该文件夹
file.mkdirs();
}
//写对象流的对象
// 创建用于存储 Song 对象的文件
File userFile = new File(file, "song.txt");
// 创建对象输出流,将对象序列化到文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(userFile));
oos.writeObject(song);//将Person对象p写入到oos
oos.close(); //关闭文件流
oos.writeObject(song); // 将 Song 对象 song 写入到 oos
oos.close(); // 关闭文件流
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
@ -57,38 +56,42 @@ public class FileUtil {
}
/**
* Person
* Song
*/
public static Song getSong() {
try {
// 创建对象输入流,从文件中读取序列化的对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(App.getContext().getExternalFilesDir("") + "/yuanmusic/song.txt"));
Song song = (Song) ois.readObject();//读出对象
return song; //返回对象
// 读取对象并强制转换为 Song 类型
Song song = (Song) ois.readObject();
return song; // 返回读取的 Song 对象
} catch (FileNotFoundException e) {
e.printStackTrace();
// 当文件不存在时,创建一个新的 Song 对象作为默认值
Song song = new Song();
return song;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
//保存图片到本地
// 保存图片到本地
public static void saveImgToNative(Context context, Bitmap bitmap, String singer) {
// 创建存储图片的文件目录
File file = new File(Api.STORAGE_IMG_FILE);
if (!file.exists()) {
file.mkdirs();
}
// 创建存储歌手图片的文件
File singerImgFile = new File(file, singer + ".jpg");
FileOutputStream fos = null;
try {
// 创建文件输出流
fos = new FileOutputStream(singerImgFile);
// 将 Bitmap 以 JPEG 格式压缩并写入文件
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
} catch (FileNotFoundException e) {
@ -98,7 +101,7 @@ public class FileUtil {
e.printStackTrace();
} finally {
try {
if (fos != null) {
if (fos!= null) {
fos.close();
}
} catch (IOException e) {
@ -107,17 +110,21 @@ public class FileUtil {
}
}
//保存歌词到本地
public static void saveLrcToNative(String lrc,String songName){
//开启线程保存歌词
// 保存歌词到本地
public static void saveLrcToNative(String lrc, String songName) {
// 开启线程保存歌词,避免在主线程中进行 I/O 操作
new Thread(() -> {
// 创建存储歌词的文件目录
File file = new File(Api.STORAGE_LRC_FILE);
if (!file.exists()) {
file.mkdirs();
}
// 创建存储歌词的文件
File lrcFile = new File(file, songName + Constant.LRC);
try {
// 创建文件写入器
FileWriter fileWriter = new FileWriter(lrcFile);
// 写入歌词
fileWriter.write(lrc);
fileWriter.close();
} catch (IOException e) {
@ -128,22 +135,22 @@ public class FileUtil {
public static String getLrcFromNative(String songName) {
try {
FileReader fileReader = new FileReader(Api.STORAGE_LRC_FILE+songName+Constant.LRC);
// 创建文件读取器读取歌词文件
FileReader fileReader = new FileReader(Api.STORAGE_LRC_FILE + songName + Constant.LRC);
BufferedReader bufferedReader = new BufferedReader(fileReader);
StringBuilder lrc = new StringBuilder();
while (true){
while (true) {
// 逐行读取歌词
String s = bufferedReader.readLine();
if(s == null) break;
if (s == null) break;
lrc.append(s).append("\n");
}
fileReader.close();
Log.d(TAG, "getLrcFromNative: "+lrc.toString());
Log.d(TAG, "getLrcFromNative: " + lrc.toString());
return lrc.toString();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

@ -12,79 +12,119 @@ import java.util.List;
* desc :
* </pre>
*/
public class LrcUtil {
/**
*
*
* @param lrcStr
* @return
* @param lrcStr
* @return
*/
public static List<LrcBean> parseStr2List(String lrcStr) {
List<LrcBean> list = new ArrayList<>();
// 存储解析后的歌词对象列表
List<LrcBean> list = new ArrayList<>();
// 替换歌词字符串中的特殊字符
String lrcText = lrcStr.replaceAll("&#58;", ":")
.replaceAll("&apos;", "'")
.replaceAll("&#10;", "\n")
.replaceAll("&#46;", ".")
.replaceAll("&#32;", " ")
.replaceAll("&#45;", "-")
.replaceAll("&#13;", "\r")
.replaceAll("&#39;", "'");
String[] split = lrcText.split("\n");
for (int i = 4; i < split.length; i++) {
String lrc = split[i];
if (lrc.contains(".")) {
String min = lrc.substring(lrc.indexOf("[") + 1, lrc.indexOf("[") + 3);
String seconds = lrc.substring(lrc.indexOf(":") + 1, lrc.indexOf(":") + 3);
String mills = lrc.substring(lrc.indexOf(".") + 1, lrc.indexOf(".") + 3);
long startTime = Long.valueOf(min) * 60 * 1000 + Long.valueOf(seconds) * 1000 + Long.valueOf(mills) * 10;
String text = lrc.substring(lrc.indexOf("]") + 1);
if (text.equals("") || text == null) continue;
if (i == 5) {
int first = text.indexOf("(");
int second = text.indexOf("(", first + 1);
String textFront = null;
String textBehind = null;
boolean isTwo = true;
boolean isOne = true;
try {
textFront = text.substring(0, first);
}catch (StringIndexOutOfBoundsException e){
isOne = false;
}
try {
textBehind = text.substring(first + 1, second);
} catch (StringIndexOutOfBoundsException e) {
isTwo = false;
}
LrcBean lrcBean1 = new LrcBean();
lrcBean1.setStart(startTime);
if(isOne) {
lrcBean1.setLrc(textFront);
} else {
lrcBean1.setLrc(text);
}
list.add(lrcBean1);
if (isTwo) {
LrcBean lrcBean2 = new LrcBean();
lrcBean2.setStart(startTime);
lrcBean2.setLrc(textBehind);
list.add(lrcBean2);
}
} else {
LrcBean lrcBean = new LrcBean();
lrcBean.setStart(startTime);
lrcBean.setLrc(text);
list.add(lrcBean);
}
if (list.size() > 1) {
list.get(list.size() - 2).setEnd(startTime);
}
if (i == split.length - 1) {
list.get(list.size() - 1).setEnd(startTime + 100000);
}
}
}
return list;
.replaceAll("&apos;", "'")
.replaceAll("&#10;", "\n")
.replaceAll("&#46;", ".")
.replaceAll("&#32;", " ")
.replaceAll("&#45;", "-")
.replaceAll("&#13;", "\r")
.replaceAll("&#39;", "'");
// 按行分割歌词字符串
String[] split = lrcText.split("\n");
for (int i = 4; i < split.length; i++) {
String lrc = split[i];
if (lrc.contains(".")) {
// 提取分钟数
String min = lrc.substring(lrc.indexOf("[") + 1, lrc.indexOf("[") + 3);
// 提取秒数
String seconds = lrc.substring(lrc.indexOf(":") + 1, lrc.indexOf(":") + 3);
// 提取毫秒数
String mills = lrc.substring(lrc.indexOf(".") + 1, lrc.indexOf(".") + 3);
// 计算开始时间(毫秒)
long startTime = Long.valueOf(min) * 60 * 1000 + Long.valueOf(seconds) * 1000 + Long.valueOf(mills) * 10;
// 提取歌词文本
// 从歌词行中提取时间戳后面的文本内容
String text = lrc.substring(lrc.indexOf("]") + 1);
// 如果提取的文本内容为空字符串或为 null则跳过当前歌词行的处理继续下一行的处理
if (text.equals("") || text == null) continue;
// 对于第 5 行歌词进行特殊处理
if (i == 5) {
// 查找第一个左括号的位置
int first = text.indexOf("(");
// 查找第二个左括号的位置,从第一个左括号后开始查找
int second = text.indexOf("(", first + 1);
// 存储第一个括号前的文本内容
String textFront = null;
// 存储第一个括号和第二个括号之间的文本内容
String textBehind = null;
// 标记是否存在第一个括号和第二个括号之间的文本
boolean isTwo = true;
// 标记是否存在第一个括号前的文本
boolean isOne = true;
try {
// 尝试提取第一个括号前的文本内容
textFront = text.substring(0, first);
} catch (StringIndexOutOfBoundsException e) {
// 如果出现字符串越界异常,说明第一个括号不存在,标记为 false
isOne = false;
}
try {
// 尝试提取第一个括号和第二个括号之间的文本内容
textBehind = text.substring(first + 1, second);
} catch (StringIndexOutOfBoundsException e) {
// 如果出现字符串越界异常,说明第二个括号不存在,标记为 false
isTwo = false;
}
// 处理越界异常
isTwo = false;
// 创建一个新的 LrcBean 实例
LrcBean lrcBean1 = new LrcBean();
// 设置该 LrcBean 的开始时间为之前计算得到的 startTime
lrcBean1.setStart(startTime);
// 如果存在第一个括号前的文本isOne 为 true将其设置为该 LrcBean 的歌词内容
if (isOne) {
lrcBean1.setLrc(textFront);
// 否则,将整个文本设置为歌词内容
} else {
lrcBean1.setLrc(text);
}
// 将创建的 LrcBean 添加到存储歌词对象的列表中
list.add(lrcBean1);
// 如果存在第二个括号及其之间的文本isTwo 为 true
if (isTwo) {
// 创建另一个新的 LrcBean 实例
LrcBean lrcBean2 = new LrcBean();
// 设置该 LrcBean 的开始时间为 startTime
lrcBean2.setStart(startTime);
// 将第一个括号和第二个括号之间的文本设置为该 LrcBean 的歌词内容
lrcBean2.setLrc(textBehind);
// 将这个 LrcBean 也添加到列表中
list.add(lrcBean2);
}
// 对于其他行(非第 5 行)
} else {
// 创建一个新的 LrcBean 实例
LrcBean lrcBean = new LrcBean();
// 设置该 LrcBean 的开始时间为 startTime
lrcBean.setStart(startTime);
// 将提取的文本设置为该 LrcBean 的歌词内容
lrcBean.setLrc(text);
// 将该 LrcBean 添加到列表中
list.add(lrcBean);
}
// 如果存储歌词对象的列表长度大于 1
if (list.size() > 1) {
// 将上一个歌词对象的结束时间设置为当前歌词对象的开始时间
list.get(list.size() - 2).setEnd(startTime);
}
// 如果当前是最后一行歌词
if (i == split.length - 1) {
// 将最后一个歌词对象的结束时间设置为 startTime 加上 100000 毫秒
list.get(list.size() - 1).setEnd(startTime + 100000);
}
// 完成整个歌词字符串的解析,返回存储歌词对象的列表
return list;
}
}

@ -14,44 +14,49 @@ import java.security.NoSuchAlgorithmException;
* desc : MD5
* </pre>
*/
public class MD5Util {
public static String getFileMD5(File file){
if(file == null||!file.exists()) return "";
FileInputStream in = null;
byte[] buffer = new byte[1024];
StringBuilder res = new StringBuilder();
int len;
public static String getFileMD5(File file) {
// 如果文件为空或不存在,返回空字符串
if (file == null ||!file.exists()) return "";
FileInputStream in = null;
// 用于存储文件读取的数据
byte[] buffer = new byte[1024];
// 存储最终的 MD5 结果
StringBuilder res = new StringBuilder();
int len;
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
in = new FileInputStream(file);
while ((len=in.read(buffer))!=-1){
//计算文件时需要通过分段读取多次调用update来将数据更新给MessageDigest对象
messageDigest.update(buffer,0,len);
// 获取 MD5 消息摘要对象
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
in = new FileInputStream(file);
// 循环读取文件内容
while ((len = in.read(buffer))!= -1) {
// 更新消息摘要对象,分段读取文件数据
messageDigest.update(buffer, 0, len);
}
//真正计算文件的MD5值
byte[] bytes = messageDigest.digest();
//将字节数组转换成16进制的字符串
for(byte b:bytes){
String temp = Integer.toHexString(b&0xff);
if(temp.length()!=2){
temp = "0"+temp;
// 计算最终的 MD5 摘要
byte[] bytes = messageDigest.digest();
// 将 MD5 摘要的字节数组转换为 16 进制字符串
for (byte b : bytes) {
String temp = Integer.toHexString(b & 0xff);
if (temp.length()!= 2) {
temp = "0" + temp;
}
res.append(temp);
res.append(temp);
}
//返回最终的字符串
return res.toString();
} catch (Exception e) {
e.printStackTrace();
} finally {
if(null!=in){
// 返回最终的 MD5 字符串
return res.toString();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null!= in) {
try {
in.close();
}catch (Exception e){
e.printStackTrace();
// 关闭文件输入流
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return res.toString();
return res.toString();
}
}
}

@ -20,33 +20,39 @@ import java.io.FileNotFoundException;
/**
* Created by on 2018/10/22.
*/
public class MediaUtil {
// 格式化时间的方法,将毫秒数转换为分钟和秒的字符串表示
public static String formatTime(long time) {
String min = time / 60 + "";
String sec = time % 60 + "";
if(sec.length()<2){
sec = "0"+sec;
// 计算分钟数
String min = time / 60 + "";
// 计算秒数
String sec = time % 60 + "";
// 如果秒数不足两位,在前面补 0
if (sec.length() < 2) {
sec = "0" + sec;
}
return min + ":" + sec;
// 返回格式化后的时间字符串,格式为 "分钟:秒"
return min + ":" + sec;
}
public static String formatSinger(String singer){
if(singer.contains("/")){
String []s=singer.split("/");
singer=s[0];
// 格式化歌手信息的方法,如果歌手信息包含 "/",取第一个部分
public static String formatSinger(String singer) {
if (singer.contains("/")) {
// 按 "/" 分割歌手信息
String[] s = singer.split("/");
singer = s[0];
}
return singer.trim();
// 去除前后的空格
return singer.trim();
}
@SuppressLint("DefaultLocale")
public static String formatSize(long size){
double d = (double) size/1024/1024;
return String.format("%.1f",d);
// 格式化文件大小的方法,将字节数转换为兆字节表示,保留一位小数
public static String formatSize(long size) {
// 将字节数转换为兆字节
double d = (double) size / 1024 / 1024;
// 格式化浮点数,保留一位小数
return String.format("%.1f", d);
}
}
}

@ -9,23 +9,32 @@ import android.content.Context;
* desc :
* </pre>
*/
public class ScreenUtil {
/**
* dp px()
* @param context
* @param dpValue dp
* @return
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
// 获取设备的屏幕密度
final float scale = context.getResources().getDisplayMetrics().density;
// 将 dp 值乘以屏幕密度并四舍五入,转换为像素值
return (int) (dpValue * scale + 0.5f);
}
/**
* px() dp
* @param context
* @param pxValue
* @return dp
*/
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
// 获取设备的屏幕密度
final float scale = context.getResources().getDisplayMetrics().density;
// 将像素值除以屏幕密度并四舍五入,转换为 dp 值
return (int) (pxValue / scale + 0.5f);
}
}

@ -12,18 +12,28 @@ import java.util.List;
* desc :
* </pre>
*/
public class ServiceUtil {
/**
*
* @param context
* @param serviceName
* @return true false
*/
public static boolean isServiceRunning(Context context, String serviceName) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> infos = am.getRunningServices(100);
for (ActivityManager.RunningServiceInfo info : infos) {
String name = info.service.getClassName();
if (name.equals(serviceName)) {
return true;
// 获取系统的 ActivityManager 服务
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
// 获取正在运行的服务列表,最多 100 个
List<ActivityManager.RunningServiceInfo> infos = am.getRunningServices(100);
// 遍历正在运行的服务列表
for (ActivityManager.RunningServiceInfo info : infos) {
// 获取服务的类名
String name = info.service.getClassName();
// 如果服务类名与要检查的服务类名相同,返回 true
if (name.equals(serviceName)) {
return true;
}
}
return false;
// 若未找到匹配的服务,返回 false
return false;
}
}
}
Loading…
Cancel
Save