增加下载列表

This commit is contained in:
litingting 2024-11-01 18:32:19 +08:00
parent 39020493e6
commit 64728f5096
19 changed files with 739 additions and 53 deletions

View File

@ -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

View File

@ -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
}
],
"lastEntityId": "1:7260744904384376845",
"relations": []
}
],
"lastEntityId": "2:2417298624721077393",
"lastIndexId": "0:0",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",

View File

@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission
@ -66,6 +67,17 @@
<action android:name="androidx.media3.session.MediaSessionService" />
</intent-filter>
</service>
<service
android:name=".media3.MyDownloadService"
android:exported="false"
android:foregroundServiceType="dataSync"
tools:ignore="ForegroundServicePermission">
<intent-filter>
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
<service
android:name=".service.MusicPlayerForegroundService"
android:enabled="true"

View File

@ -3,9 +3,13 @@ package com.hi.music.player;
import android.app.Application;
import android.content.Context;
import androidx.annotation.OptIn;
import androidx.media3.common.util.UnstableApi;
import com.hi.music.player.api.MediaControllerStatusListener;
import com.hi.music.player.helper.CommonUtils;
import com.hi.music.player.javabean.MyObjectBox;
import com.hi.music.player.media3.MyDownloadService;
import com.hi.music.player.media3.MyMediaControllerManager;
import com.hi.music.player.objectbox.ObjectBoxManager;
@ -30,11 +34,13 @@ public class MusicApplication extends Application {
return visitorData;
}
@OptIn(markerClass = UnstableApi.class)
@Override
public void onCreate() {
super.onCreate();
myApplication = this;
ObjectBoxManager.init(this);
MyDownloadService.init(this);
MyMediaControllerManager.getInstance().init(new MediaControllerStatusListener() {
@Override
public void onMediaControllerComplete(boolean isOk) {

View File

@ -0,0 +1,76 @@
package com.hi.music.player.adapter;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.media3.exoplayer.offline.Download;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
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.hi.music.player.MusicApplication;
import com.hi.music.player.R;
import com.hi.music.player.databinding.ItemLikeSongBinding;
import com.hi.music.player.helper.CommonUtils;
import com.hi.music.player.javabean.BoxDownloadSong;
import com.hi.music.player.javabean.BoxLikeSong;
public class AdapterDownloadSong extends BaseAdapter<Download, ItemLikeSongBinding> {
@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<ItemLikeSongBinding> itemHolder = (VHolder<ItemLikeSongBinding>) 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<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target<Drawable> target, boolean isFirstResource) {
CommonUtils.LogMsg(e.getMessage());
return false;
}
@Override
public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target<Drawable> 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);
}
}
});
}
}

View File

@ -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){
}
}

View File

@ -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;
@ -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();
@ -240,14 +247,13 @@ public class CommonUtils {
);
view.setBackground(gradientDrawable);
}
/**
* 当前媒体项是否有有效的播放地址
*
* @param mediaItem
* @return
*/
@ -273,4 +279,13 @@ public class CommonUtils {
}
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);
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<Download> 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<Download> downloads, int notMetRequirements) {
// 构建用于显示下载进度的通知
return buildNotification(downloads, notMetRequirements);
}
private Notification buildNotification(
List<Download> downloads, int notMetRequirements) {
return new DownloadNotificationHelper(this, CHANNEL_ID)
.buildProgressNotification(this, R.drawable.ic_download, null, null, downloads, notMetRequirements);
}
}

View File

@ -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);

View File

@ -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,13 +29,21 @@ public class LikeSongActivity extends BaseActivity<ActivityLikeSongBinding> impl
private List<BoxLikeSong> boxLikeSongs;
private int mType;
@Override
protected ActivityLikeSongBinding getViewBinding() {
return ActivityLikeSongBinding.inflate(getLayoutInflater());
}
@OptIn(markerClass = UnstableApi.class)
@Override
protected void onCreateInit() {
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);
@ -36,6 +51,26 @@ public class LikeSongActivity extends BaseActivity<ActivityLikeSongBinding> impl
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<List<Download>>() {
@Override
public void onChanged(List<Download> 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<ActivityLikeSongBinding> impl
}
@Override
public void onClickDownloadSong(Download download, int index) {
}
}

View File

@ -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<ActivityPlayBinding> implements SeekBar.OnSeekBarChangeListener {
@ -114,7 +122,7 @@ public class PlayActivity extends BaseActivity<ActivityPlayBinding> 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<ActivityPlayBinding> implements S
vb.btnLoop.setOnClickListener(this);
vb.layoutLike.setOnClickListener(this);
vb.layoutDownload.setOnClickListener(this);
}
@ -597,11 +606,62 @@ public class PlayActivity extends BaseActivity<ActivityPlayBinding> 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<ActivityPlayBinding> implements S
public void onStopTrackingTouch(SeekBar seekBar) {
}
@OptIn(markerClass = UnstableApi.class)
private void addDownloadListener(){
MyDownloadService.addDownloadListener(vmApplication);
}
}

View File

@ -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<Integer> playStatus = _playStatus;
private MutableLiveData<List<Download>> _downloadData = new MutableLiveData<>();
public LiveData<List<Download>> downloadData = _downloadData;
/**
* 重置播放列表
* @param list
@ -44,4 +57,17 @@ public class VMApplication extends ViewModel {
}
@OptIn(markerClass = UnstableApi.class)
public void setDownloadData( List<Download> 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<String, String> stringStringPair = new Pair(covert,count);
_downloadData.setValue(downloadList);
}
}

View File

@ -1,28 +1,26 @@
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;
@ -34,23 +32,27 @@ public class ProfileFragment extends BaseFragment<FragmentProfileBinding> implem
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() {
vmApplication = getApplicationScopeViewModel(VMApplication.class);
dataSubscription = ObjectBoxManager.setLikeDataListener(new LikeSongListener() {
@Override
public void onLikeSongChange(List<BoxLikeSong> 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,6 +70,32 @@ public class ProfileFragment extends BaseFragment<FragmentProfileBinding> implem
}
}
});
MyDownloadService.updateDownloadUi(vmApplication);
vmApplication.downloadData.observe(getViewLifecycleOwner(), new Observer<List<Download>>() {
@Override
public void onChanged(List<Download> 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);
@ -98,8 +126,15 @@ public class ProfileFragment extends BaseFragment<FragmentProfileBinding> implem
@Override
public void onClick(View v) {
if (v.equals(Vb.relayoutLike)) {
if(likeSongSCount>0){
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);
}
}

View File

@ -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"

View File

@ -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" />
<TextView
android:layout_width="wrap_content"

View File

@ -37,4 +37,5 @@
<string name="like_song">Songs %d</string>
<string name="download_song">Offline %d</string>
<string name="text_like_song">Favorite Songs</string>
<string name="text_offline_song">Offline Songs</string>
</resources>