diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 26bae0b..8d9d4c6 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -78,6 +78,7 @@ dependencies {
implementation("androidx.media3:media3-exoplayer-dash:1.4.1")
implementation("androidx.media3:media3-session:1.4.1")
implementation("androidx.media3:media3-ui:1.4.1")
+ implementation ("androidx.media3:media3-database:1.4.1")
//----------media3
diff --git a/app/objectbox-models/default.json b/app/objectbox-models/default.json
index e08abd4..6018d3a 100644
--- a/app/objectbox-models/default.json
+++ b/app/objectbox-models/default.json
@@ -46,9 +46,58 @@
}
],
"relations": []
+ },
+ {
+ "id": "2:2417298624721077393",
+ "lastPropertyId": "8:376288481537034221",
+ "name": "BoxDownloadSong",
+ "properties": [
+ {
+ "id": "1:7539246810224330948",
+ "name": "id",
+ "type": 6,
+ "flags": 1
+ },
+ {
+ "id": "2:4996334478861162671",
+ "name": "songName",
+ "type": 9
+ },
+ {
+ "id": "3:7837922119072734767",
+ "name": "singerName",
+ "type": 9
+ },
+ {
+ "id": "4:102902831836074640",
+ "name": "videoId",
+ "type": 9
+ },
+ {
+ "id": "5:230178850883428921",
+ "name": "covert",
+ "type": 9
+ },
+ {
+ "id": "6:5148334492661309747",
+ "name": "durationMs",
+ "type": 6
+ },
+ {
+ "id": "7:9082762505752465280",
+ "name": "duration",
+ "type": 9
+ },
+ {
+ "id": "8:376288481537034221",
+ "name": "cachePath",
+ "type": 9
+ }
+ ],
+ "relations": []
}
],
- "lastEntityId": "1:7260744904384376845",
+ "lastEntityId": "2:2417298624721077393",
"lastIndexId": "0:0",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 523258f..5ddf8e3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -5,6 +5,7 @@
+
+
+
+
+
+
+
+
{
+
+ @Override
+ protected ItemLikeSongBinding getViewBinding(ViewGroup parent) {
+ return ItemLikeSongBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+ VHolder itemHolder = (VHolder) holder;
+ ItemLikeSongBinding vb = itemHolder.getVb();
+ Download download = data.get(position);
+ BoxDownloadSong boxDownloadSong = CommonUtils.downloadToBean(download);
+
+
+ Glide.with(MusicApplication.myApplication)
+ .asDrawable()
+ .load(boxDownloadSong.getCovert())
+ .apply(RequestOptions.bitmapTransform(new RoundedCorners(CommonUtils.dpToPx(4))))
+ .placeholder(R.drawable.placeholder)
+ .listener(new RequestListener() {
+ @Override
+ public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target target, boolean isFirstResource) {
+ CommonUtils.LogMsg(e.getMessage());
+ return false;
+ }
+
+ @Override
+ public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target target, @NonNull DataSource dataSource, boolean isFirstResource) {
+ return false;
+ }
+ })
+ .into(vb.imCovert);
+ vb.tvTitle.setText(boxDownloadSong.getSongName());
+ vb.tvSingerName.setText(boxDownloadSong.getSingerName());
+
+ vb.getRoot().setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if(homeItemClickListener!= null){
+ int absoluteAdapterPosition = itemHolder.getAbsoluteAdapterPosition();
+ homeItemClickListener.onClickDownloadSong(download,absoluteAdapterPosition);
+ }
+ }
+ });
+
+ }
+
+
+}
diff --git a/app/src/main/java/com/hi/music/player/api/HomeItemClickListener.java b/app/src/main/java/com/hi/music/player/api/HomeItemClickListener.java
index 242b5bf..5daaed0 100644
--- a/app/src/main/java/com/hi/music/player/api/HomeItemClickListener.java
+++ b/app/src/main/java/com/hi/music/player/api/HomeItemClickListener.java
@@ -1,5 +1,7 @@
package com.hi.music.player.api;
+import androidx.media3.exoplayer.offline.Download;
+
import com.hi.music.player.javabean.BoxLikeSong;
import com.hi.music.player.javabean.response.ResponsePlayListInfo;
import com.hi.music.player.javabean.response.ResponseSearch;
@@ -68,8 +70,22 @@ public interface HomeItemClickListener {
}
+ /**
+ * 点击喜爱歌曲列表的某一首歌曲
+ * @param boxLikeSong
+ * @param index
+ */
default void onClickLikeSong(BoxLikeSong boxLikeSong, int index){
}
+
+ /**
+ * 点击下载歌曲列表的某一首歌曲
+ * @param index
+ */
+ default void onClickDownloadSong(Download download, int index){
+
+ }
+
}
diff --git a/app/src/main/java/com/hi/music/player/helper/CommonUtils.java b/app/src/main/java/com/hi/music/player/helper/CommonUtils.java
index ee6e802..517e1dc 100644
--- a/app/src/main/java/com/hi/music/player/helper/CommonUtils.java
+++ b/app/src/main/java/com/hi/music/player/helper/CommonUtils.java
@@ -13,17 +13,23 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import androidx.annotation.OptIn;
import androidx.media3.common.MediaItem;
+import androidx.media3.common.util.UnstableApi;
+import androidx.media3.exoplayer.offline.Download;
import androidx.palette.graphics.Palette;
+import com.google.gson.Gson;
import com.hi.music.player.MusicApplication;
import com.hi.music.player.R;
import com.hi.music.player.api.onImageColorListener;
+import com.hi.music.player.javabean.BoxDownloadSong;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@@ -84,18 +90,18 @@ public class CommonUtils {
//time 3:45 转换成毫秒
public static long convertToMilliseconds(String time) {
String[] parts = time.split(":");
- if(parts.length == 2){
+ if (parts.length == 2) {
//3:45
int minutes = Integer.parseInt(parts[0]);
int seconds = Integer.parseInt(parts[1]);
- return (minutes * 60L + seconds) * 1000-1000;
- }else{
+ return (minutes * 60L + seconds) * 1000 - 1000;
+ } else {
//3:45:07
int hours = Integer.parseInt(parts[0]);
int minutes = Integer.parseInt(parts[1]);
int seconds = Integer.parseInt(parts[2]);
- return (hours*60L*60L+minutes * 60L + seconds) * 1000-1000;
+ return (hours * 60L * 60L + minutes * 60L + seconds) * 1000 - 1000;
}
@@ -111,9 +117,9 @@ public class CommonUtils {
// long seconds = (millis / 1000) % 60;
// long minutes = (millis / (1000 * 60)) % 60;
- if(hours>0){
- return String.format(MusicApplication.myApplication.getString(R.string.time_format),hours, minutes, remainingSeconds); // 格式化为 mm:ss
- }else {
+ if (hours > 0) {
+ return String.format(MusicApplication.myApplication.getString(R.string.time_format), hours, minutes, remainingSeconds); // 格式化为 mm:ss
+ } else {
return String.format(MusicApplication.myApplication.getString(R.string.minute_time_format), minutes, remainingSeconds); // 格式化为 mm:ss
}
@@ -174,7 +180,7 @@ public class CommonUtils {
}
- public static void getMainColor(Drawable imDraw, onImageColorListener listener){
+ public static void getMainColor(Drawable imDraw, onImageColorListener listener) {
BitmapDrawable drawable = (BitmapDrawable) imDraw;
// 使用 Palette 提取颜色
@@ -188,7 +194,7 @@ public class CommonUtils {
if (dominantSwatch != null) {
int dominantColor = dominantSwatch.getRgb(); // 获取 RGB 颜色
listener.onImageColor(dominantColor);
- }else {
+ } else {
listener.onImageColor(-1);
}
}
@@ -217,6 +223,7 @@ public class CommonUtils {
drawable.setCornerRadii(radii);
return drawable;
}
+
public static void extractColorsFromImage(Drawable drawable, View view) {
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
@@ -227,34 +234,33 @@ public class CommonUtils {
int mutedColor = palette.getMutedColor(0);
// 创建左右渐变色
- createGradientBackground(vibrantColor, mutedColor,view);
+ createGradientBackground(vibrantColor, mutedColor, view);
}
});
}
private static void createGradientBackground(int leftColor, int rightColor, View view) {
- GradientDrawable gradientDrawable = new android.graphics.drawable.GradientDrawable(
+ GradientDrawable gradientDrawable = new android.graphics.drawable.GradientDrawable(
android.graphics.drawable.GradientDrawable.Orientation.LEFT_RIGHT,
new int[]{leftColor, rightColor}
);
-
-
view.setBackground(gradientDrawable);
}
/**
* 当前媒体项是否有有效的播放地址
+ *
* @param mediaItem
* @return
*/
public static boolean hasValidUri(MediaItem mediaItem) {
MediaItem.LocalConfiguration localConfiguration = mediaItem.localConfiguration;
- if(localConfiguration != null){
- if(CommonUtils.isUriFormat(localConfiguration.uri)){
+ if (localConfiguration != null) {
+ if (CommonUtils.isUriFormat(localConfiguration.uri)) {
return true;
}
}
@@ -268,9 +274,18 @@ public class CommonUtils {
return false;
}
} catch (Exception e) {
- LogMsg( "URI parsing failed: " + e.getMessage());
+ LogMsg("URI parsing failed: " + e.getMessage());
return false;
}
return true;
}
+
+
+ public static BoxDownloadSong downloadToBean(Download download) {
+ @OptIn(markerClass = UnstableApi.class) byte[] data = download.request.data;
+ String additionalData = new String(data, StandardCharsets.UTF_8);
+ Gson gson = new Gson();
+
+ return gson.fromJson(additionalData, BoxDownloadSong.class);
+ }
}
diff --git a/app/src/main/java/com/hi/music/player/helper/MyValue.java b/app/src/main/java/com/hi/music/player/helper/MyValue.java
index 9b66f5d..3bd07a4 100644
--- a/app/src/main/java/com/hi/music/player/helper/MyValue.java
+++ b/app/src/main/java/com/hi/music/player/helper/MyValue.java
@@ -18,10 +18,11 @@ public class MyValue {
//专辑
public static final String PAGE_TYPE_ALBUM="MUSIC_PAGE_TYPE_ALBUM";
+
+
+
//-----------------------------PlayActivity
-
-
/**
* 歌手单曲进入的数据key
*/
@@ -91,7 +92,6 @@ public class MyValue {
public final static int TYPE_ENTER_LIKE= 4;
- //-----------------------------PlayActivity
@@ -105,11 +105,23 @@ public class MyValue {
public static String KEY_CATEGORY_LIST_BROWSER_ID= "browser_id";
- //-----------------------------CategoryListActivity
+
//------------------------ResultListActivity
public static String KEY_SEARCH_RESULT_BROWSER_ID= "search_result_browser_id";
- //-------------------------------------ResultListActivity
+
+
+
+
+
+
+
+ //---------------------LikeSongActivity
+ public static String KEY_ENTER_LIKE_ACTIVITY_TYPE = "song_type";
+
+ public final static int KEY_ENTER_LIKE_ACTIVITY_TYPE_LIKE = 0;
+ public final static int KEY_ENTER_LIKE_ACTIVITY_TYPE_DOWNLOAD = 1;
+
}
diff --git a/app/src/main/java/com/hi/music/player/javabean/BoxDownloadSong.java b/app/src/main/java/com/hi/music/player/javabean/BoxDownloadSong.java
new file mode 100644
index 0000000..e6c5872
--- /dev/null
+++ b/app/src/main/java/com/hi/music/player/javabean/BoxDownloadSong.java
@@ -0,0 +1,88 @@
+package com.hi.music.player.javabean;
+
+import io.objectbox.annotation.Entity;
+import io.objectbox.annotation.Id;
+
+@Entity
+public class BoxDownloadSong {
+ @Id
+ public long id;
+ private String songName;
+
+ private String singerName;
+ private String videoId;
+
+ private String covert;
+ private long durationMs;
+ private String duration ;
+
+ //下载的音视频文件存储路径
+ private String cachePath ;
+
+ public BoxDownloadSong( ) {
+
+ }
+
+ public BoxDownloadSong(String songName, String singerName, String videoId, String covert, long durationMs, String duration) {
+ this.songName = songName;
+ this.singerName = singerName;
+ this.videoId = videoId;
+ this.covert = covert;
+ this.durationMs = durationMs;
+ this.duration = duration;
+ }
+
+ public String getCachePath() {
+ return cachePath;
+ }
+
+ public void setCachePath(String cachePath) {
+ this.cachePath = cachePath;
+ }
+
+ public long getDurationMs() {
+ return durationMs;
+ }
+ public String getDuration() {
+ return duration;
+ }
+
+ public void setDuration(String duration) {
+ this.duration = duration;
+ }
+ public void setDurationMs(long durationMs) {
+ this.durationMs = durationMs;
+ }
+
+ public String getSingerName() {
+ return singerName;
+ }
+
+ public void setSingerName(String singerName) {
+ this.singerName = singerName;
+ }
+
+ public String getVideoId() {
+ return videoId;
+ }
+
+ public void setVideoId(String videoId) {
+ this.videoId = videoId;
+ }
+
+ public String getCovert() {
+ return covert;
+ }
+
+ public void setCovert(String covert) {
+ this.covert = covert;
+ }
+
+ public String getSongName() {
+ return songName;
+ }
+
+ public void setSongName(String songName) {
+ this.songName = songName;
+ }
+}
diff --git a/app/src/main/java/com/hi/music/player/media3/MyCacheManager.java b/app/src/main/java/com/hi/music/player/media3/MyCacheManager.java
new file mode 100644
index 0000000..5eeadce
--- /dev/null
+++ b/app/src/main/java/com/hi/music/player/media3/MyCacheManager.java
@@ -0,0 +1,32 @@
+package com.hi.music.player.media3;
+
+import android.content.Context;
+
+import androidx.annotation.OptIn;
+import androidx.media3.common.util.UnstableApi;
+import androidx.media3.database.StandaloneDatabaseProvider;
+import androidx.media3.datasource.cache.NoOpCacheEvictor;
+import androidx.media3.datasource.cache.SimpleCache;
+
+import com.hi.music.player.MusicApplication;
+
+import java.io.File;
+
+public class MyCacheManager {
+
+ private static SimpleCache downloadCache;
+
+ @OptIn(markerClass = UnstableApi.class)
+ public static SimpleCache getMyCache(StandaloneDatabaseProvider databaseProvider){
+ if(downloadCache == null){
+ Context myApplication = MusicApplication.myApplication;
+ File musicApp = new File(myApplication.getCacheDir(), "Music_App");
+ downloadCache = new SimpleCache(musicApp, new NoOpCacheEvictor(), databaseProvider);
+ }
+ return downloadCache;
+
+ }
+
+
+
+}
diff --git a/app/src/main/java/com/hi/music/player/media3/MyDownloadService.java b/app/src/main/java/com/hi/music/player/media3/MyDownloadService.java
new file mode 100644
index 0000000..accba57
--- /dev/null
+++ b/app/src/main/java/com/hi/music/player/media3/MyDownloadService.java
@@ -0,0 +1,210 @@
+package com.hi.music.player.media3;
+
+import android.app.Notification;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.media3.common.util.UnstableApi;
+import androidx.media3.database.StandaloneDatabaseProvider;
+import androidx.media3.datasource.DefaultHttpDataSource;
+import androidx.media3.datasource.cache.SimpleCache;
+import androidx.media3.exoplayer.offline.Download;
+import androidx.media3.exoplayer.offline.DownloadCursor;
+import androidx.media3.exoplayer.offline.DownloadIndex;
+import androidx.media3.exoplayer.offline.DownloadManager;
+import androidx.media3.exoplayer.offline.DownloadNotificationHelper;
+import androidx.media3.exoplayer.offline.DownloadService;
+import androidx.media3.exoplayer.scheduler.Scheduler;
+
+import com.google.gson.Gson;
+import com.hi.music.player.MusicApplication;
+import com.hi.music.player.R;
+import com.hi.music.player.helper.CommonUtils;
+import com.hi.music.player.javabean.BoxDownloadSong;
+import com.hi.music.player.ui.activity.viewmodel.VMApplication;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+import io.reactivex.Observable;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
+
+
+@UnstableApi
+public class MyDownloadService extends DownloadService {
+ private static final int FOREGROUND_NOTIFICATION_ID = 1;
+ private static final String CHANNEL_ID = "download_song_channel_id";
+
+ private static DownloadManager mDownloadManager;
+
+ public MyDownloadService() {
+ super(FOREGROUND_NOTIFICATION_ID,
+ DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL,
+ CHANNEL_ID, // 通知渠道 ID
+ R.string.app_name, // 通知渠道名称
+ 0
+ );
+ }
+
+ public static void init(Context context) {
+ if (mDownloadManager == null) {
+ StandaloneDatabaseProvider databaseProvider = new StandaloneDatabaseProvider(context);
+ SimpleCache downloadCache = MyCacheManager.getMyCache(databaseProvider);
+ DefaultHttpDataSource.Factory factory = new DefaultHttpDataSource.Factory();
+ Executor downloadExecutor = Runnable::run;
+
+ mDownloadManager = new DownloadManager(
+ context,
+ databaseProvider, // 数据库提供者
+ downloadCache,
+ factory, // 数据源工厂
+ downloadExecutor // 线程池
+ );
+
+ mDownloadManager.setMaxParallelDownloads(3); // 设置最大并行下载数
+
+ }
+ }
+
+ public static synchronized DownloadManager getMyDownloadManager() {
+
+ return mDownloadManager;
+ }
+
+ public static void addDownloadListener(VMApplication vmApplication) {
+ mDownloadManager.addListener(new DownloadManager.Listener() {
+ @Override
+ public void onDownloadChanged(@NonNull DownloadManager downloadManager, Download download, @Nullable Exception finalException) {
+ String id = download.request.id;
+ if (download.state == Download.STATE_COMPLETED) {
+ // 下载完成
+ CommonUtils.LogMsg("----------------下载完成 id=" + id + "--thread=" + Thread.currentThread().getName());
+ updateDownloadUi(vmApplication);
+
+ } else if (download.state == Download.STATE_FAILED) {
+ // 下载失败
+ CommonUtils.LogMsg("----------------下载失败 id=" + id + "---finalException=" + finalException.getMessage());
+ }
+ }
+
+ @Override
+ public void onDownloadRemoved(DownloadManager downloadManager, Download download) {
+ String id = download.request.id;
+ CommonUtils.LogMsg("----------------onDownloadRemoved id=" + id);
+ }
+ });
+ }
+
+
+ @UnstableApi
+ public static void updateDownloadUi(VMApplication vmApplication) {
+ Disposable subscribe = Observable.fromCallable(() -> {
+ // 第一部分操作:在IO线程
+ DownloadIndex downloadIndex = mDownloadManager.getDownloadIndex();
+ DownloadCursor downloads = downloadIndex.getDownloads(Download.STATE_COMPLETED);
+
+ return downloads;
+ })
+ .subscribeOn(Schedulers.io())
+// .observeOn(Schedulers.computation()) // 切换到计算线程
+// .map(result -> {
+// // 第二部分操作:在computation线程
+// return result + " -> Step 2 completed";
+// })
+ .observeOn(AndroidSchedulers.mainThread()) // 切换到主线程
+ .subscribe(finalResult -> {
+ // 最终结果处理:在主线程
+ Download curDownload = null;
+// int count = finalResult.getCount();
+// if (finalResult.moveToLast()) {
+// curDownload = finalResult.getDownload();
+// }
+ List downloadList = new ArrayList<>();
+ while (finalResult.moveToNext()) {
+ Download download = finalResult.getDownload();
+ downloadList.add(download);
+ }
+
+
+ CommonUtils.LogMsg("----------------下载总数量 -count=" + downloadList.size());
+ vmApplication.setDownloadData(downloadList);
+
+ });
+ }
+
+ @NonNull
+ @Override
+ protected DownloadManager getDownloadManager() {
+ // 初始化下载管理器
+// StandaloneDatabaseProvider databaseProvider = new StandaloneDatabaseProvider(this);
+// SimpleCache downloadCache = MyCacheManager.getMyCache(databaseProvider);
+// DefaultHttpDataSource.Factory factory = new DefaultHttpDataSource.Factory();
+// Executor downloadExecutor = Runnable::run;
+//
+// downloadManager = new DownloadManager(
+// this,
+// databaseProvider, // 数据库提供者
+// downloadCache,
+// factory, // 数据源工厂
+// downloadExecutor // 线程池
+// );
+//
+// downloadManager.setMaxParallelDownloads(3); // 设置最大并行下载数
+// downloadManager.addListener(new DownloadManager.Listener() {
+// @Override
+// public void onDownloadChanged(DownloadManager downloadManager, Download download, @Nullable Exception finalException) {
+// String id = download.request.id;
+// if (download.state == Download.STATE_COMPLETED) {
+// // 下载完成
+// CommonUtils.LogMsg("----------------下载完成 id="+id);
+// byte[] data = download.request.data;
+// String additionalData = new String(data, StandardCharsets.UTF_8);
+// Gson gson = new Gson();
+// BoxDownloadSong boxDownloadSong = gson.fromJson(additionalData, BoxDownloadSong.class);
+//
+//
+//
+// } else if (download.state == Download.STATE_FAILED) {
+// // 下载失败
+// CommonUtils.LogMsg("----------------下载失败 id="+id+"---finalException="+finalException.getMessage());
+// }
+// }
+//
+// @Override
+// public void onDownloadRemoved(DownloadManager downloadManager, Download download) {
+// String id = download.request.id;
+// CommonUtils.LogMsg("----------------onDownloadRemoved id="+id);
+// }
+// });
+ return getMyDownloadManager();
+ }
+
+
+ @Nullable
+ @Override
+ protected Scheduler getScheduler() {
+ // 返回 null 表示不需要使用调度器
+ return null;
+ }
+
+ @Override
+ protected Notification getForegroundNotification(List downloads, int notMetRequirements) {
+ // 构建用于显示下载进度的通知
+ return buildNotification(downloads, notMetRequirements);
+ }
+
+ private Notification buildNotification(
+ List downloads, int notMetRequirements) {
+
+ return new DownloadNotificationHelper(this, CHANNEL_ID)
+ .buildProgressNotification(this, R.drawable.ic_download, null, null, downloads, notMetRequirements);
+ }
+
+
+}
diff --git a/app/src/main/java/com/hi/music/player/media3/PlaybackService.java b/app/src/main/java/com/hi/music/player/media3/PlaybackService.java
index 5321da7..78eeae1 100644
--- a/app/src/main/java/com/hi/music/player/media3/PlaybackService.java
+++ b/app/src/main/java/com/hi/music/player/media3/PlaybackService.java
@@ -48,7 +48,7 @@ public class PlaybackService extends MediaSessionService {
long maxCacheSize = 100 * 1024 * 1024; // 缓存大小 100MB
StandaloneDatabaseProvider databaseProvider = new StandaloneDatabaseProvider(this);
SimpleCache cache = new SimpleCache(cacheDir, new LeastRecentlyUsedCacheEvictor(maxCacheSize), databaseProvider);
-
+ SimpleCache downloadCache = MyCacheManager.getMyCache(databaseProvider);
DefaultHttpDataSource.Factory httpDataSourceFactory = new DefaultHttpDataSource.Factory().setAllowCrossProtocolRedirects(true);
diff --git a/app/src/main/java/com/hi/music/player/ui/activity/LikeSongActivity.java b/app/src/main/java/com/hi/music/player/ui/activity/LikeSongActivity.java
index d2985c3..e438af7 100644
--- a/app/src/main/java/com/hi/music/player/ui/activity/LikeSongActivity.java
+++ b/app/src/main/java/com/hi/music/player/ui/activity/LikeSongActivity.java
@@ -3,9 +3,15 @@ package com.hi.music.player.ui.activity;
import android.content.Intent;
import android.view.View;
+import androidx.annotation.OptIn;
+import androidx.lifecycle.Observer;
+import androidx.media3.common.util.UnstableApi;
+import androidx.media3.exoplayer.offline.Download;
+import androidx.media3.exoplayer.offline.DownloadManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.hi.music.player.R;
+import com.hi.music.player.adapter.AdapterDownloadSong;
import com.hi.music.player.adapter.AdapterLikeSong;
import com.hi.music.player.api.HomeItemClickListener;
import com.hi.music.player.databinding.ActivityLikeSongBinding;
@@ -13,6 +19,7 @@ import com.hi.music.player.helper.CommonUtils;
import com.hi.music.player.helper.MyValue;
import com.hi.music.player.javabean.BoxLikeSong;
import com.hi.music.player.javabean.response.ResponsePlayListInfo;
+import com.hi.music.player.media3.MyDownloadService;
import com.hi.music.player.objectbox.ObjectBoxManager;
import java.util.ArrayList;
@@ -22,20 +29,48 @@ public class LikeSongActivity extends BaseActivity impl
private List boxLikeSongs;
+ private int mType;
@Override
protected ActivityLikeSongBinding getViewBinding() {
return ActivityLikeSongBinding.inflate(getLayoutInflater());
}
+ @OptIn(markerClass = UnstableApi.class)
@Override
protected void onCreateInit() {
- boxLikeSongs = ObjectBoxManager.queryAllLike();
- AdapterLikeSong adapterLikeSong = new AdapterLikeSong();
- adapterLikeSong.setHomeItemClickListener(this);
- adapterLikeSong.setData(boxLikeSongs);
- vb.recycler.setLayoutManager(new LinearLayoutManager(this));
- vb.recycler.setAdapter(adapterLikeSong);
- vb.tvSongSize.setText(String.format(getString(R.string.like_song), boxLikeSongs.size()));
+ Intent intent = getIntent();
+ mType = intent.getIntExtra(MyValue.KEY_ENTER_LIKE_ACTIVITY_TYPE, MyValue.KEY_ENTER_LIKE_ACTIVITY_TYPE_LIKE);
+
+ switch (mType){
+ case MyValue.KEY_ENTER_LIKE_ACTIVITY_TYPE_LIKE:
+ vb.tvTitle.setText(getString(R.string.text_like_song));
+ boxLikeSongs = ObjectBoxManager.queryAllLike();
+ AdapterLikeSong adapterLikeSong = new AdapterLikeSong();
+ adapterLikeSong.setHomeItemClickListener(this);
+ adapterLikeSong.setData(boxLikeSongs);
+ vb.recycler.setLayoutManager(new LinearLayoutManager(this));
+ vb.recycler.setAdapter(adapterLikeSong);
+ vb.tvSongSize.setText(String.format(getString(R.string.like_song), boxLikeSongs.size()));
+ break;
+
+ case MyValue.KEY_ENTER_LIKE_ACTIVITY_TYPE_DOWNLOAD:
+ vb.tvTitle.setText(getString(R.string.text_offline_song));
+ MyDownloadService.updateDownloadUi(vmApplication);
+ vmApplication.downloadData.observe(this, new Observer>() {
+ @Override
+ public void onChanged(List downloads) {
+ AdapterDownloadSong adapterDownloadSong = new AdapterDownloadSong();
+ adapterDownloadSong.setHomeItemClickListener(LikeSongActivity.this);
+ adapterDownloadSong.setData(downloads);
+ vb.recycler.setLayoutManager(new LinearLayoutManager(LikeSongActivity.this));
+ vb.recycler.setAdapter(adapterDownloadSong);
+ vb.tvSongSize.setText(String.format(getString(R.string.download_song), downloads.size()));
+ }
+ });
+ break;
+ }
+
+
}
@Override
@@ -91,5 +126,11 @@ public class LikeSongActivity extends BaseActivity impl
+ }
+
+ @Override
+ public void onClickDownloadSong(Download download, int index) {
+
+
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/hi/music/player/ui/activity/PlayActivity.java b/app/src/main/java/com/hi/music/player/ui/activity/PlayActivity.java
index 4d18b30..8694c67 100644
--- a/app/src/main/java/com/hi/music/player/ui/activity/PlayActivity.java
+++ b/app/src/main/java/com/hi/music/player/ui/activity/PlayActivity.java
@@ -20,6 +20,10 @@ import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.Player;
import androidx.media3.common.util.UnstableApi;
+import androidx.media3.exoplayer.offline.Download;
+import androidx.media3.exoplayer.offline.DownloadManager;
+import androidx.media3.exoplayer.offline.DownloadRequest;
+import androidx.media3.exoplayer.offline.DownloadService;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.bumptech.glide.Glide;
@@ -29,6 +33,7 @@ import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target;
+import com.google.gson.Gson;
import com.hi.music.player.MusicApplication;
import com.hi.music.player.R;
import com.hi.music.player.adapter.AdapterPlayList;
@@ -39,17 +44,20 @@ import com.hi.music.player.api.onPlayNextListener;
import com.hi.music.player.databinding.ActivityPlayBinding;
import com.hi.music.player.helper.CommonUtils;
import com.hi.music.player.helper.MyValue;
+import com.hi.music.player.javabean.BoxDownloadSong;
import com.hi.music.player.javabean.BoxLikeSong;
import com.hi.music.player.javabean.CustomerUrlInfo;
import com.hi.music.player.javabean.response.ResponsePlayListInfo;
import com.hi.music.player.javabean.response.ResponsePlayUrl;
import com.hi.music.player.javabean.response.child.ResponseCategory;
import com.hi.music.player.javabean.response.child.ResponseSingle;
+import com.hi.music.player.media3.MyDownloadService;
import com.hi.music.player.media3.MyMediaControllerManager;
import com.hi.music.player.objectbox.ObjectBoxManager;
import com.hi.music.player.ui.activity.viewmodel.VMApplication;
import com.hi.music.player.ui.activity.viewmodel.VMPlay;
+import java.nio.charset.StandardCharsets;
import java.util.List;
public class PlayActivity extends BaseActivity implements SeekBar.OnSeekBarChangeListener {
@@ -114,7 +122,7 @@ public class PlayActivity extends BaseActivity implements S
mEnterType = intent.getIntExtra(MyValue.KEY_ENTER_SOURCE, MyValue.TYPE_ENTER_SOURCE_SINGLE);
initPlayerView();
initProgressHandler();
-
+ addDownloadListener();
CommonUtils.LogMsg("--------mEnterType=" + mEnterType);
switch (mEnterType) {
@@ -347,6 +355,7 @@ public class PlayActivity extends BaseActivity implements S
vb.btnLoop.setOnClickListener(this);
vb.layoutLike.setOnClickListener(this);
vb.layoutDownload.setOnClickListener(this);
+
}
@@ -597,11 +606,62 @@ public class PlayActivity extends BaseActivity implements S
}
+ }else if(v.equals(vb.layoutDownload)){
+ vb.downloadPb.setVisibility(View.VISIBLE);
+ BoxDownloadSong curMediaItemInfo = getCurMediaItemInfo();
+ if(curMediaItemInfo!= null){
+ Gson gson = new Gson();
+ String info = gson.toJson(curMediaItemInfo);
+ byte[] data = info.getBytes(StandardCharsets.UTF_8);
+
+ String videoId1 = curMediaItemInfo.getVideoId();
+ CommonUtils.LogMsg("----------------开始下载 id="+videoId1);
+ String contentUri= "https://rr3---sn-tt1e7nlz.googlevideo.com/videoplayback?expire=1730203742&ei=_nsgZ4KrKsCW2_gP3vHpsQk&ip=146.19.167.8&id=o-AMLbkjliQ0-OoP2NcJ1EcdlaKQ7tyJP2QHMOIxrhiBKx&source=youtube&requiressl=yes&xpc=EgVo2aDSNQ%3D%3D&met=1730182142%2C&mh=HX&mm=31%2C29&mn=sn-tt1e7nlz%2Csn-vgqsrnzd&ms=au%2Crdu&mv=m&mvi=3&pl=24&rms=au%2Cau&gcr=us&initcwndbps=241250&svpuc=1&sabr=1&rqh=1&mt=1730181899&fvip=1&keepalive=yes&fexp=51312688%2C51326932&c=ANDROID&sparams=expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Cxpc%2Cgcr%2Csvpuc%2Csabr%2Crqh&sig=AJfQdSswRgIhAOxvUuLE3tfOLtuF9OP0zmy8tl4HQvm-6BlDjOnkhyGZAiEAjjL2jOFMqrjDgHMtwkWaQb9CNXz6lNqi1ShWWzGjtjY%3D&lsparams=met%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Crms%2Cinitcwndbps&lsig=ACJ0pHgwRQIhAJ7XPfdAxtoFrNQ13TkFhqHiGYGaKaBfxC6gb9fbB369AiBfx8gshE7jEgx0wOHce2IVHrZ_4aQvYptrSh23tfZqqA%3D%3D";
+ DownloadRequest downloadRequest = new DownloadRequest.Builder(videoId1, Uri.parse(contentUri))
+ .setMimeType("video/mp4")
+ .setData(data)
+ .build();
+
+ // 启动 DownloadService 进行下载
+ DownloadService.sendAddDownload(
+ this,
+ MyDownloadService.class, // 上面定义的下载服务类
+ downloadRequest,
+ true // 是否在前台运行
+ );
+ }
+
+
+
+
}
}
+ @OptIn(markerClass = UnstableApi.class)
+ private BoxDownloadSong getCurMediaItemInfo(){
+ MediaItem curMediaItem = mediaControllerManager.getCurMediaItem();
+ if(curMediaItem== null){
+ return null;
+ }
+ MediaMetadata mediaMetadata = curMediaItem.mediaMetadata;
+ if(mediaMetadata.title== null||mediaMetadata.artist== null||mediaMetadata.description==null
+ ||mediaMetadata.durationMs==null){
+ return null;
+ }
+
+ BoxDownloadSong boxDownloadSong = new BoxDownloadSong();
+ boxDownloadSong.setVideoId(curMediaItem.mediaId);
+ boxDownloadSong.setCovert(String.valueOf(mediaMetadata.artworkUri));
+ boxDownloadSong.setSongName((String) mediaMetadata.title);
+ boxDownloadSong.setSingerName((String) mediaMetadata.artist);
+ boxDownloadSong.setDuration((String) mediaMetadata.description);
+ boxDownloadSong.setDurationMs( mediaMetadata.durationMs);
+
+ return boxDownloadSong;
+ }
+
/**
* 控制播放列表的显示
*
@@ -721,4 +781,9 @@ public class PlayActivity extends BaseActivity implements S
public void onStopTrackingTouch(SeekBar seekBar) {
}
+
+ @OptIn(markerClass = UnstableApi.class)
+ private void addDownloadListener(){
+ MyDownloadService.addDownloadListener(vmApplication);
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/hi/music/player/ui/activity/viewmodel/VMApplication.java b/app/src/main/java/com/hi/music/player/ui/activity/viewmodel/VMApplication.java
index d4ab73b..ee17cf5 100644
--- a/app/src/main/java/com/hi/music/player/ui/activity/viewmodel/VMApplication.java
+++ b/app/src/main/java/com/hi/music/player/ui/activity/viewmodel/VMApplication.java
@@ -1,11 +1,19 @@
package com.hi.music.player.ui.activity.viewmodel;
+import android.util.Pair;
+
+import androidx.annotation.OptIn;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
+import androidx.media3.common.util.UnstableApi;
+import androidx.media3.exoplayer.offline.Download;
+import androidx.media3.exoplayer.offline.DownloadCursor;
+import com.google.gson.Gson;
import com.hi.music.player.api.RequestListener;
import com.hi.music.player.helper.CommonUtils;
+import com.hi.music.player.javabean.BoxDownloadSong;
import com.hi.music.player.javabean.CustomerUrlInfo;
import com.hi.music.player.javabean.response.ResponsePlayListInfo;
import com.hi.music.player.javabean.response.ResponsePlayUrl;
@@ -16,6 +24,7 @@ import com.hi.music.player.network.RetrofitManager;
import org.json.JSONObject;
+import java.nio.charset.StandardCharsets;
import java.util.List;
import okhttp3.ResponseBody;
@@ -31,6 +40,10 @@ public class VMApplication extends ViewModel {
public LiveData playStatus = _playStatus;
+ private MutableLiveData> _downloadData = new MutableLiveData<>();
+ public LiveData> downloadData = _downloadData;
+
+
/**
* 重置播放列表
* @param list
@@ -44,4 +57,17 @@ public class VMApplication extends ViewModel {
}
+ @OptIn(markerClass = UnstableApi.class)
+ public void setDownloadData( List downloadList) {
+//
+// byte[] data = download.request.data;
+// String additionalData = new String(data, StandardCharsets.UTF_8);
+// Gson gson = new Gson();
+// BoxDownloadSong boxDownloadSong = gson.fromJson(additionalData, BoxDownloadSong.class);
+// String covert = boxDownloadSong.getCovert();
+// String count = String.valueOf(downloadCount);
+//
+// Pair stringStringPair = new Pair(covert,count);
+ _downloadData.setValue(downloadList);
+ }
}
diff --git a/app/src/main/java/com/hi/music/player/ui/fragmnt/ProfileFragment.java b/app/src/main/java/com/hi/music/player/ui/fragmnt/ProfileFragment.java
index 12b27f4..25c2354 100644
--- a/app/src/main/java/com/hi/music/player/ui/fragmnt/ProfileFragment.java
+++ b/app/src/main/java/com/hi/music/player/ui/fragmnt/ProfileFragment.java
@@ -1,56 +1,58 @@
package com.hi.music.player.ui.fragmnt;
-import static com.hi.music.player.javabean.BoxLikeSong_.covert;
-
import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
+import android.util.Pair;
import android.view.View;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
+import androidx.lifecycle.Observer;
+import androidx.media3.common.util.UnstableApi;
+import androidx.media3.exoplayer.offline.Download;
import com.bumptech.glide.Glide;
-import com.bumptech.glide.load.DataSource;
-import com.bumptech.glide.load.engine.GlideException;
-import com.bumptech.glide.request.RequestListener;
-import com.bumptech.glide.request.target.Target;
-import com.hi.music.player.MusicApplication;
import com.hi.music.player.R;
import com.hi.music.player.api.LikeSongListener;
import com.hi.music.player.databinding.FragmentProfileBinding;
import com.hi.music.player.helper.CommonUtils;
+import com.hi.music.player.helper.MyValue;
+import com.hi.music.player.javabean.BoxDownloadSong;
import com.hi.music.player.javabean.BoxLikeSong;
+import com.hi.music.player.media3.MyDownloadService;
import com.hi.music.player.objectbox.ObjectBoxManager;
import com.hi.music.player.ui.activity.LikeSongActivity;
+import com.hi.music.player.ui.activity.viewmodel.VMApplication;
import java.util.List;
import io.objectbox.reactive.DataSubscription;
-public class ProfileFragment extends BaseFragment implements View.OnClickListener{
+public class ProfileFragment extends BaseFragment implements View.OnClickListener {
private DataSubscription dataSubscription;
- private int likeSongSCount = 0;
+ private int likeSongCount = 0;
+ private int downloadSongCount = 0;
+
+ private VMApplication vmApplication;
+
@Override
protected FragmentProfileBinding getFragmentVb() {
return FragmentProfileBinding.inflate(getLayoutInflater());
}
-
-
+ @OptIn(markerClass = UnstableApi.class)
@Override
protected void initView() {
- dataSubscription = ObjectBoxManager.setLikeDataListener(new LikeSongListener() {
+ vmApplication = getApplicationScopeViewModel(VMApplication.class);
+ dataSubscription = ObjectBoxManager.setLikeDataListener(new LikeSongListener() {
@Override
public void onLikeSongChange(List data) {
CommonUtils.LogMsg("------onLikeSongChange data=" + data.size());
int size = data.size();
- likeSongSCount = size;
+ likeSongCount = size;
Vb.tvLikeSize.setText(String.format(getString(R.string.like_song), size));
if (size == 0) {
Vb.likeCovert.setVisibility(View.GONE);
@@ -68,8 +70,34 @@ public class ProfileFragment extends BaseFragment implem
}
}
});
+ MyDownloadService.updateDownloadUi(vmApplication);
- Vb.relayoutLike.setOnClickListener(this);
+ vmApplication.downloadData.observe(getViewLifecycleOwner(), new Observer>() {
+ @Override
+ public void onChanged(List downloads) {
+ int size = downloads.size();
+ Vb.tvDownloadSize.setText(String.format(getString(R.string.download_song), size));
+ downloadSongCount = size;
+ if (size > 0) {
+ Download download = downloads.get(downloads.size() - 1);
+ BoxDownloadSong boxDownloadSong = CommonUtils.downloadToBean(download);
+
+ Vb.downloadCovert.setVisibility(View.VISIBLE);
+ Vb.downloadDefault.setVisibility(View.GONE);
+ Glide.with(requireContext())
+ .asDrawable()
+ .load(boxDownloadSong.getCovert())
+ .placeholder(R.drawable.placeholder)
+ .error(R.drawable.placeholder)
+ .into(Vb.downloadCovert);
+ } else {
+ Vb.downloadCovert.setVisibility(View.GONE);
+ Vb.downloadDefault.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+
+ Vb.relayoutLike.setOnClickListener(this);
Vb.relayoutDownload.setOnClickListener(this);
}
@@ -83,7 +111,7 @@ public class ProfileFragment extends BaseFragment implem
@Override
public void onDestroyView() {
super.onDestroyView();
- if(dataSubscription!= null){
+ if (dataSubscription != null) {
dataSubscription.cancel();
}
CommonUtils.LogMsg("------ProfileFragment-onDestroyView");
@@ -97,9 +125,16 @@ public class ProfileFragment extends BaseFragment implem
@Override
public void onClick(View v) {
- if(v.equals(Vb.relayoutLike)){
- if(likeSongSCount>0){
+ if (v.equals(Vb.relayoutLike)) {
+ if (likeSongCount > 0) {
Intent intent = new Intent(requireActivity(), LikeSongActivity.class);
+ intent.putExtra(MyValue.KEY_ENTER_LIKE_ACTIVITY_TYPE, MyValue.KEY_ENTER_LIKE_ACTIVITY_TYPE_LIKE);
+ startActivity(intent);
+ }
+ } else if (v.equals(Vb.relayoutDownload)) {
+ if (downloadSongCount > 0) {
+ Intent intent = new Intent(requireActivity(), LikeSongActivity.class);
+ intent.putExtra(MyValue.KEY_ENTER_LIKE_ACTIVITY_TYPE, MyValue.KEY_ENTER_LIKE_ACTIVITY_TYPE_DOWNLOAD);
startActivity(intent);
}
}
diff --git a/app/src/main/res/layout/activity_like_song.xml b/app/src/main/res/layout/activity_like_song.xml
index 28ada3b..1ca08dd 100644
--- a/app/src/main/res/layout/activity_like_song.xml
+++ b/app/src/main/res/layout/activity_like_song.xml
@@ -24,6 +24,7 @@
android:text="@string/text_like_song"
android:textColor="@color/white"
android:textSize="17sp"
+ android:id="@+id/tv_title"
app:layout_constraintBottom_toBottomOf="@id/im_back"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
diff --git a/app/src/main/res/layout/activity_play.xml b/app/src/main/res/layout/activity_play.xml
index 43cf9bc..d897204 100644
--- a/app/src/main/res/layout/activity_play.xml
+++ b/app/src/main/res/layout/activity_play.xml
@@ -209,7 +209,7 @@
android:indeterminateTint="@color/panel_bg"
android:progressBackgroundTint="@color/panel_bg"
android:progressTint="@color/panel_bg"
- android:visibility="visible" />
+ android:visibility="gone" />
Songs %d
Offline %d
Favorite Songs
+ Offline Songs
\ No newline at end of file