diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 71beea1..2412e32 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,11 +4,12 @@
-
+
-
@@ -31,12 +32,12 @@
android:exported="false" />
+ android:exported="false"
+ android:screenOrientation="portrait" />
+ android:exported="true"
+ android:screenOrientation="portrait">
@@ -45,7 +46,7 @@
+ android:exported="false">
+ android:exported="true"
+ android:foregroundServiceType="mediaPlayback">
-
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/hi/music/player/adapter/A_ImportFragmentAdapter.java b/app/src/main/java/com/hi/music/player/adapter/A_ImportFragmentAdapter.java
new file mode 100644
index 0000000..2ad1765
--- /dev/null
+++ b/app/src/main/java/com/hi/music/player/adapter/A_ImportFragmentAdapter.java
@@ -0,0 +1,100 @@
+package com.hi.music.player.adapter;
+
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.hi.music.player.R;
+import com.hi.music.player.javabean.A_data.AudioItem;
+import com.hi.music.player.ui.activity.A_PlayActivity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class A_ImportFragmentAdapter extends RecyclerView.Adapter {
+
+ private Context context;
+ private List audioFiles = new ArrayList<>();
+ private OnDeleteClickListener onDeleteClickListener;
+
+ public A_ImportFragmentAdapter(Context context) {
+ this.context = context;
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_a_import, parent, false));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ AudioItem audioItem = audioFiles.get(position);
+
+ holder.title.setText(audioItem.getName());
+ holder.time.setText(audioItem.getDuration());
+ Log.d("Adapter", "onBindViewHolder: " + audioItem.getDuration());
+
+ holder.deleteButton.setOnClickListener(v -> {
+ if (onDeleteClickListener != null) {
+ onDeleteClickListener.onDeleteClick(audioItem.getFile());
+ }
+ });
+
+ holder.itemView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(context, A_PlayActivity.class);
+ intent.putExtra("Path", audioItem);
+ context.startActivity(intent);
+ }
+ });
+
+ }
+
+ @Override
+ public int getItemCount() {
+ return audioFiles.size();
+ }
+
+ public void setAudioFiles(List audioFiles) {
+ this.audioFiles = audioFiles;
+ notifyDataSetChanged(); // Update UI when the data changes
+ }
+
+ public void setOnDeleteClickListener(OnDeleteClickListener listener) {
+ this.onDeleteClickListener = listener;
+ }
+
+ public interface OnDeleteClickListener {
+ void onDeleteClick(String filePath);
+ }
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+
+ ImageView deleteButton;
+ TextView title;
+ TextView time;
+
+ public ViewHolder(@NonNull View itemView) {
+ super(itemView);
+
+ deleteButton = itemView.findViewById(R.id.options);
+ title = itemView.findViewById(R.id.title);
+ time = itemView.findViewById(R.id.time);
+
+ }
+
+ }
+}
+
+
diff --git a/app/src/main/java/com/hi/music/player/helper/CircularProgressBar.java b/app/src/main/java/com/hi/music/player/helper/CircularProgressBar.java
index e824383..a22885e 100644
--- a/app/src/main/java/com/hi/music/player/helper/CircularProgressBar.java
+++ b/app/src/main/java/com/hi/music/player/helper/CircularProgressBar.java
@@ -16,6 +16,8 @@ public class CircularProgressBar extends View {
private int fixedSize = 47; // 固定圆环的直径(dp)
private int progressWidth = 5; // 固定进度条宽度(dp)
+ private OnProgressChangeListener listener; // 自定义监听器
+
public CircularProgressBar(Context context) {
super(context);
init();
@@ -35,7 +37,7 @@ public class CircularProgressBar extends View {
// 初始化用于绘制进度条的画笔
progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
progressPaint.setStyle(Paint.Style.STROKE);
- progressPaint.setStrokeWidth(dpToPx(progressWidth)); // 设置进度条宽度(10dp)
+ progressPaint.setStrokeWidth(dpToPx(progressWidth)); // 设置进度条宽度(dp)
progressPaint.setColor(Color.WHITE); // 设置进度条颜色为白色
// 初始化用于绘制背景环的画笔
@@ -85,6 +87,9 @@ public class CircularProgressBar extends View {
public void setProgress(float progress) {
this.progress = Math.max(0, Math.min(progress, maxProgress)); // 限制进度值在 0 到 maxProgress 之间
+ if (listener != null) {
+ listener.onProgressChanged(this, (int) progress); // 通知监听器进度变化
+ }
invalidate(); // 请求重新绘制视图
}
@@ -94,6 +99,16 @@ public class CircularProgressBar extends View {
invalidate(); // 请求重新绘制视图
}
+ // 添加设置监听器的方法
+ public void setOnProgressChangeListener(OnProgressChangeListener listener) {
+ this.listener = listener;
+ }
+
+ // 自定义进度变化监听器接口
+ public interface OnProgressChangeListener {
+ void onProgressChanged(CircularProgressBar progressBar, int progress);
+ }
+
// 将 dp 转换为 px
private int dpToPx(int dp) {
float density = getResources().getDisplayMetrics().density;
diff --git a/app/src/main/java/com/hi/music/player/javabean/A_data/AudioItem.java b/app/src/main/java/com/hi/music/player/javabean/A_data/AudioItem.java
index 9a222ae..52068db 100644
--- a/app/src/main/java/com/hi/music/player/javabean/A_data/AudioItem.java
+++ b/app/src/main/java/com/hi/music/player/javabean/A_data/AudioItem.java
@@ -6,6 +6,7 @@ public class AudioItem implements Serializable {
private String name;
private String file;
private String image;
+ private String duration;
public AudioItem(String name, String file, String image) {
this.name = name;
@@ -36,4 +37,12 @@ public class AudioItem implements Serializable {
public void setImage(String image) {
this.image = image;
}
+
+ public String getDuration() {
+ return duration;
+ }
+
+ public void setDuration(String duration) {
+ this.duration = duration;
+ }
}
diff --git a/app/src/main/java/com/hi/music/player/service/MusicPlayerForegroundService.java b/app/src/main/java/com/hi/music/player/service/MusicPlayerForegroundService.java
new file mode 100644
index 0000000..2b68c98
--- /dev/null
+++ b/app/src/main/java/com/hi/music/player/service/MusicPlayerForegroundService.java
@@ -0,0 +1,166 @@
+package com.hi.music.player.service;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+
+import androidx.annotation.Nullable;
+import androidx.core.app.NotificationCompat;
+import androidx.lifecycle.MutableLiveData;
+
+import android.util.Log;
+
+import com.hi.music.player.R;
+import com.hi.music.player.ui.activity.A_PlayActivity;
+
+import java.io.IOException;
+
+public class MusicPlayerForegroundService extends Service {
+
+ public static final String CHANNEL_ID = "MusicPlayerChannel";
+ private MediaPlayer mediaPlayer;
+ private final IBinder binder = new MusicBinder();
+ private MutableLiveData isPlaying = new MutableLiveData<>(false);
+
+ public class MusicBinder extends Binder {
+ public MusicPlayerForegroundService getService() {
+ return MusicPlayerForegroundService.this;
+ }
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return binder;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ String audioPath = intent.getStringExtra("Path");
+ if (audioPath != null) {
+ initializePlayer(audioPath);
+ }
+
+ startForeground(1, createNotification("Playing Audio", "Your audio is playing"));
+
+ return START_NOT_STICKY;
+ }
+
+ private Notification createNotification(String title, String content) {
+ createNotificationChannel();
+ Intent notificationIntent = new Intent(this, A_PlayActivity.class);
+ int flags = PendingIntent.FLAG_UPDATE_CURRENT;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ flags |= PendingIntent.FLAG_IMMUTABLE;
+ }
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, flags);
+
+ return new NotificationCompat.Builder(this, CHANNEL_ID)
+ .setContentTitle(title)
+ .setContentText(content)
+ .setSmallIcon(R.drawable.home_select)
+ .setContentIntent(pendingIntent)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .build();
+ }
+
+ private void createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel serviceChannel = new NotificationChannel(
+ CHANNEL_ID, "Music Player Channel", NotificationManager.IMPORTANCE_LOW);
+ NotificationManager manager = getSystemService(NotificationManager.class);
+ manager.createNotificationChannel(serviceChannel);
+ }
+ }
+
+ private void initializePlayer(String path) {
+ try {
+ Log.d("MediaPlayerError","path: "+path);
+
+ if (mediaPlayer == null) {
+ mediaPlayer = new MediaPlayer();
+ } else {
+ mediaPlayer.reset();
+ }
+
+ if (path.startsWith("content://")) {
+ Uri contentUri = Uri.parse(path);
+ ContentResolver contentResolver = getContentResolver();
+ AssetFileDescriptor afd = contentResolver.openAssetFileDescriptor(contentUri, "r");
+ if (afd != null) {
+ mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+ afd.close();
+ }
+ } else {
+ AssetFileDescriptor afd = getAssets().openFd(path);
+ mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+ afd.close();
+ }
+
+ mediaPlayer.prepare();
+ mediaPlayer.setOnPreparedListener(mp -> mediaPlayer.start());
+ isPlaying.postValue(true);
+ } catch (IOException e) {
+ Log.e("MediaPlayerError", "Could not open file: " + path, e);
+ }
+ }
+
+ public void pauseAudio() {
+ if (mediaPlayer != null && mediaPlayer.isPlaying()) {
+ mediaPlayer.pause();
+ isPlaying.postValue(false);
+ }
+ }
+
+ public void resumeAudio() {
+ if (mediaPlayer != null && !mediaPlayer.isPlaying()) {
+ mediaPlayer.start();
+ isPlaying.postValue(true);
+ }
+ }
+
+ public MutableLiveData getIsPlaying() {
+ return isPlaying;
+ }
+
+ // 添加获取当前播放位置的方法
+ public int getCurrentPosition() {
+ if (mediaPlayer != null) {
+ return mediaPlayer.getCurrentPosition();
+ }
+ return 0; // 如果 mediaPlayer 为 null,返回 0
+ }
+
+ // 添加获取总时长的方法
+ public int getDuration() {
+ if (mediaPlayer != null) {
+ return mediaPlayer.getDuration();
+ }
+ return 0; // 如果 mediaPlayer 为 null,返回 0
+ }
+
+ public void seekTo(int position) {
+ if (mediaPlayer != null) {
+ mediaPlayer.seekTo(position);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mediaPlayer != null) {
+ mediaPlayer.release();
+ mediaPlayer = null;
+ }
+ }
+}
diff --git a/app/src/main/java/com/hi/music/player/ui/activity/A_HomeActivity.java b/app/src/main/java/com/hi/music/player/ui/activity/A_HomeActivity.java
index 4f030d2..7fe4375 100644
--- a/app/src/main/java/com/hi/music/player/ui/activity/A_HomeActivity.java
+++ b/app/src/main/java/com/hi/music/player/ui/activity/A_HomeActivity.java
@@ -1,21 +1,24 @@
package com.hi.music.player.ui.activity;
-import android.os.Bundle;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
+import androidx.lifecycle.ViewModelProvider;
+
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import com.hi.music.player.R;
import com.hi.music.player.adapter.A_HomeViewPagerAdapter;
import com.hi.music.player.databinding.ActivityAhomeBinding;
import com.hi.music.player.databinding.HomeTabCustomBinding;
+import com.hi.music.player.helper.CircularProgressBar;
+import com.hi.music.player.ui.activity.viewmodel.A_VMPlay;
import java.util.Objects;
public class A_HomeActivity extends BaseActivity {
- // 图标数组定义为类成员,避免重复
private final int[] defaultIcons = {
R.drawable.home_unselect,
R.drawable.import_unselect,
@@ -26,11 +29,7 @@ public class A_HomeActivity extends BaseActivity {
R.drawable.import_select,
};
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- }
+ private A_VMPlay viewModel;
@Override
protected ActivityAhomeBinding getViewBinding() {
@@ -39,9 +38,9 @@ public class A_HomeActivity extends BaseActivity {
@Override
protected void onCreateInit() {
-
+ viewModel = new ViewModelProvider(this).get(A_VMPlay.class);
initData();
-
+ setupObservers();
}
@Override
@@ -59,42 +58,73 @@ public class A_HomeActivity extends BaseActivity {
return false;
}
- public void initData(){
-
+ public void initData() {
A_HomeViewPagerAdapter adapter = new A_HomeViewPagerAdapter(this);
vb.homeViewpager.setAdapter(adapter);
+ vb.homeViewpager.setUserInputEnabled(false);
- // 设置TabLayout的图标
new TabLayoutMediator(vb.homeTabLayout, vb.homeViewpager, (tab, position) -> {
HomeTabCustomBinding tabBinding = HomeTabCustomBinding.inflate(LayoutInflater.from(this));
tab.setCustomView(tabBinding.getRoot());
- tabBinding.homeIcon.setImageResource(defaultIcons[position]); // 默认图标
+ tabBinding.homeIcon.setImageResource(defaultIcons[position]);
}).attach();
- // 添加Tab选中与未选中事件监听器
vb.homeTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
- updateTabIcon(tab, true); // 更新选中的图标
+ updateTabIcon(tab, true);
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
- updateTabIcon(tab, false); // 恢复未选中图标
+ updateTabIcon(tab, false);
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
- // 可选:重复选择Tab时的操作
}
});
- // 设置默认选中第一个
TabLayout.Tab firstTab = vb.homeTabLayout.getTabAt(0);
if (firstTab != null) {
firstTab.select();
- updateTabIcon(firstTab, true); // 设置选中的图标
+ updateTabIcon(firstTab, true);
}
+
+ vb.circularProgressBar.setMaxProgress(100); // 假设最大值为100
+
+ vb.circularProgressBar.setOnProgressChangeListener(new CircularProgressBar.OnProgressChangeListener() {
+ @Override
+ public void onProgressChanged(CircularProgressBar progressBar, int progress) {
+ viewModel.seekTo(progress); // 当用户改变进度时,调用 ViewModel 方法
+ }
+ });
+
+ vb.pause.setOnClickListener(v -> {
+ viewModel.togglePlay();
+ });
+ }
+
+ private void setupObservers() {
+ viewModel.getCurrentTime().observe(this, currentTime -> {
+ vb.bottomText.setText(currentTime);
+ });
+
+ viewModel.getTotalTime().observe(this, totalTime -> {
+ vb.topText.setText(totalTime);
+ });
+
+ viewModel.getProgress().observe(this, progress -> {
+ if (progress >= 0 && progress <= 100) {
+ vb.circularProgressBar.setProgress(progress); // 更新进度条
+ } else {
+ Log.e("A_HomeActivity", "Progress out of range: " + progress);
+ }
+ });
+
+ viewModel.isPlaying().observe(this, isPlaying -> {
+ vb.pause.setImageResource(isPlaying ? R.drawable.play : R.drawable.pause);
+ });
}
private void updateTabIcon(TabLayout.Tab tab, boolean isSelected) {
@@ -108,4 +138,3 @@ public class A_HomeActivity extends BaseActivity {
}
}
-
diff --git a/app/src/main/java/com/hi/music/player/ui/activity/A_PlayActivity.java b/app/src/main/java/com/hi/music/player/ui/activity/A_PlayActivity.java
index 77094ce..3c9357d 100644
--- a/app/src/main/java/com/hi/music/player/ui/activity/A_PlayActivity.java
+++ b/app/src/main/java/com/hi/music/player/ui/activity/A_PlayActivity.java
@@ -1,23 +1,46 @@
package com.hi.music.player.ui.activity;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
-import android.media.MediaPlayer;
+import android.content.ServiceConnection;
+import android.os.IBinder;
import android.util.Log;
import android.view.View;
-import android.widget.SeekBar;
-
-import androidx.lifecycle.ViewModelProvider;
import com.hi.music.player.R;
import com.hi.music.player.databinding.ActivityAplayBinding;
import com.hi.music.player.javabean.A_data.AudioItem;
-import com.hi.music.player.ui.activity.viewmodel.A_VMPlay;
+import com.hi.music.player.service.MusicPlayerForegroundService;
+
+import java.util.Locale;
public class A_PlayActivity extends BaseActivity {
- private AudioItem audioItem;
- private MediaPlayer mediaPlayer;
- private A_VMPlay playViewModel;
+ private MusicPlayerForegroundService musicService;
+ private boolean isBound = false;
+
+ private final ServiceConnection serviceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ MusicPlayerForegroundService.MusicBinder binder = (MusicPlayerForegroundService.MusicBinder) service;
+ musicService = binder.getService();
+ isBound = true;
+
+ // 观察服务中的播放状态
+ musicService.getIsPlaying().observe(A_PlayActivity.this, isPlaying -> {
+ vb.playButton.setBackgroundResource(isPlaying ? R.drawable.play : R.drawable.pause);
+ });
+
+ // 更新进度和总时长
+ startUpdatingProgress();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ isBound = false;
+ }
+ };
@Override
protected ActivityAplayBinding getViewBinding() {
@@ -26,68 +49,74 @@ public class A_PlayActivity extends BaseActivity {
@Override
protected void onCreateInit() {
-
Intent intent = getIntent();
- audioItem = (AudioItem) intent.getSerializableExtra("Path");
+ AudioItem audioItem = (AudioItem) intent.getSerializableExtra("Path");
if (audioItem == null) {
+ finish(); // 如果没有音频项目,结束活动
return;
}
- String path = audioItem.getFile();
- Log.d("------", "--------" + path);
+ // 启动前台服务
+ Intent serviceIntent = new Intent(this, MusicPlayerForegroundService.class);
+ serviceIntent.putExtra("Path", audioItem.getFile());
+ Log.d("A_PlayActivity", "Path: " + audioItem.getFile());
+ startService(serviceIntent);
+ bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
- playViewModel = new ViewModelProvider(this).get(A_VMPlay.class);
- mediaPlayer = new MediaPlayer();
-
- playViewModel.initializePlayer(path);
-
- // 观察进度更新
- playViewModel.getProgress().observe(this, progress -> {
- vb.songSeekbar.setProgress(progress);
- });
-
- // 观察播放状态
- playViewModel.isPlaying().observe(this, isPlaying -> {
- vb.playButton.setBackgroundResource(isPlaying ? R.drawable.play : R.drawable.pause);
- });
-
- // 观察当前时间
- playViewModel.getCurrentTime().observe(this, currentTime -> {
- vb.current.setText(currentTime);
- });
-
- // 观察总时间
- playViewModel.getTotalTime().observe(this, totalTime -> {
- vb.time.setText(totalTime);
+ vb.playButton.setOnClickListener(v -> {
+ if (isBound) {
+ if (musicService.getIsPlaying().getValue() != null && musicService.getIsPlaying().getValue()) {
+ musicService.pauseAudio();
+ } else {
+ musicService.resumeAudio();
+ }
+ }
});
+ // 启动定期更新播放进度
+ startUpdatingProgress();
}
@Override
protected void onInitClick() {
- vb.playButton.setOnClickListener(v -> playViewModel.togglePlay());
-
- vb.songSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- if (fromUser) {
- playViewModel.seekTo(progress); // 拖动进度条时控制音频
- }
- }
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- // 暂停更新进度
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- // 恢复更新进度
- }
- });
}
+ private void startUpdatingProgress() {
+ new Thread(() -> {
+ while (isBound && musicService != null) {
+ if (musicService.getIsPlaying().getValue() != null && musicService.getIsPlaying().getValue()) {
+ int currentPosition = musicService.getCurrentPosition(); // 获取当前播放位置
+ int duration = musicService.getDuration(); // 获取总时长
+ runOnUiThread(() -> {
+ vb.songSeekbar.setProgress((int) ((currentPosition / (float) duration) * 100)); // 更新进度条
+ vb.current.setText(formatTime(currentPosition)); // 更新当前时间文本
+ vb.time.setText(formatTime(duration)); // 更新总时间文本
+ });
+ }
+ try {
+ Thread.sleep(1000); // 每秒更新一次
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }).start();
+ }
+
+ private String formatTime(int milliseconds) {
+ int minutes = (milliseconds / 1000) / 60;
+ int seconds = (milliseconds / 1000) % 60;
+ return String.format(Locale.getDefault(), "%d:%02d", minutes, seconds);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (isBound) {
+ unbindService(serviceConnection);
+ isBound = false;
+ }
+ }
@Override
public boolean isFullScreen() {
diff --git a/app/src/main/java/com/hi/music/player/ui/activity/viewmodel/A_VMPlay.java b/app/src/main/java/com/hi/music/player/ui/activity/viewmodel/A_VMPlay.java
index bd64ae4..6111335 100644
--- a/app/src/main/java/com/hi/music/player/ui/activity/viewmodel/A_VMPlay.java
+++ b/app/src/main/java/com/hi/music/player/ui/activity/viewmodel/A_VMPlay.java
@@ -1,26 +1,33 @@
package com.hi.music.player.ui.activity.viewmodel;
import android.app.Application;
-import android.content.res.AssetFileDescriptor;
-import android.media.MediaPlayer;
-import android.util.Log;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
-import java.io.IOException;
+import com.hi.music.player.service.MusicPlayerForegroundService;
+
+import java.util.Locale;
public class A_VMPlay extends AndroidViewModel {
- private MediaPlayer mediaPlayer;
private final MutableLiveData currentTime = new MutableLiveData<>();
private final MutableLiveData totalTime = new MutableLiveData<>();
+ private final MutableLiveData fileName = new MutableLiveData<>();
private final MutableLiveData isPlaying = new MutableLiveData<>(false);
private final MutableLiveData progress = new MutableLiveData<>(0); // 添加进度 LiveData
+ private MusicPlayerForegroundService musicService;
+ private boolean isBound = false;
+
public A_VMPlay(Application application) {
super(application);
- mediaPlayer = new MediaPlayer();
+ bindToMusicService();
}
// 获取进度的 LiveData
@@ -40,37 +47,54 @@ public class A_VMPlay extends AndroidViewModel {
return isPlaying;
}
- public void initializePlayer(String path) {
- try {
- AssetFileDescriptor afd = getApplication().getAssets().openFd(path);
- mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
- mediaPlayer.prepare();
- mediaPlayer.setOnPreparedListener(mp -> {
- totalTime.setValue(formatTime(mediaPlayer.getDuration()));
- startUpdatingProgress();
- });
- } catch (IOException e) {
- Log.e("MediaPlayerError", "Could not open asset file: " + path, e);
- }
+ public LiveData getFileName() {
+ return fileName;
}
+ private void bindToMusicService() {
+ Intent serviceIntent = new Intent(getApplication(), MusicPlayerForegroundService.class);
+ getApplication().bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ private final ServiceConnection serviceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ MusicPlayerForegroundService.MusicBinder binder = (MusicPlayerForegroundService.MusicBinder) service;
+ musicService = binder.getService();
+ isBound = true;
+
+ // 观察服务中的播放状态
+ musicService.getIsPlaying().observeForever(isPlaying::setValue);
+
+ // 开始监听播放进度
+ startUpdatingProgress();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ isBound = false;
+ }
+ };
+
public void togglePlay() {
- if (mediaPlayer.isPlaying()) {
- mediaPlayer.pause();
- isPlaying.setValue(false);
- } else {
- mediaPlayer.start();
- isPlaying.setValue(true);
+ if (isBound) {
+ if (musicService.getIsPlaying().getValue() != null && musicService.getIsPlaying().getValue()) {
+ musicService.pauseAudio();
+ } else {
+ musicService.resumeAudio();
+ }
}
}
private void startUpdatingProgress() {
new Thread(() -> {
- while (mediaPlayer != null) {
- if (mediaPlayer.isPlaying()) {
- int currentPosition = mediaPlayer.getCurrentPosition();
+ while (isBound && musicService != null) {
+ if (musicService.getIsPlaying().getValue() != null && musicService.getIsPlaying().getValue()) {
+ int currentPosition = musicService.getCurrentPosition(); // 从服务获取当前播放位置
currentTime.postValue(formatTime(currentPosition));
- progress.postValue((int) ((currentPosition / (float) mediaPlayer.getDuration()) * 100)); // 更新进度
+ int duration = musicService.getDuration(); // 从服务获取音频总时长
+ progress.postValue((int) ((currentPosition / (float) duration) * 100)); // 更新进度
+ totalTime.postValue(formatTime(duration));
}
try {
Thread.sleep(1000);
@@ -82,24 +106,25 @@ public class A_VMPlay extends AndroidViewModel {
}
public void seekTo(int progress) {
- if (mediaPlayer != null) {
- int duration = mediaPlayer.getDuration();
- mediaPlayer.seekTo((int) (duration * progress / 100.0));
+ if (isBound && musicService != null) {
+ int duration = musicService.getDuration();
+ musicService.seekTo((int) (duration * progress / 100.0));
}
}
private String formatTime(int milliseconds) {
int minutes = (milliseconds / 1000) / 60;
int seconds = (milliseconds / 1000) % 60;
- return String.format("%02d:%02d", minutes, seconds);
+ return String.format(Locale.getDefault(), "%d:%02d", minutes, seconds);
}
@Override
protected void onCleared() {
super.onCleared();
- if (mediaPlayer != null) {
- mediaPlayer.release();
- mediaPlayer = null;
+ if (isBound) {
+ getApplication().unbindService(serviceConnection);
+ isBound = false;
}
}
}
+
diff --git a/app/src/main/java/com/hi/music/player/ui/fragmnt/A_ImportFragment.java b/app/src/main/java/com/hi/music/player/ui/fragmnt/A_ImportFragment.java
index 4e39bb1..f42667c 100644
--- a/app/src/main/java/com/hi/music/player/ui/fragmnt/A_ImportFragment.java
+++ b/app/src/main/java/com/hi/music/player/ui/fragmnt/A_ImportFragment.java
@@ -7,19 +7,30 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
+import android.os.Environment;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import com.hi.music.player.adapter.A_ImportFragmentAdapter;
import com.hi.music.player.databinding.FragmentAImportBinding;
+import com.hi.music.player.javabean.A_data.AudioItem;
import com.hi.music.player.ui.activity.A_SettingActivity;
+import com.hi.music.player.ui.fragmnt.viewmodel.A_VMImport;
+
+import java.io.IOException;
+import java.util.List;
public class A_ImportFragment extends BaseFragment {
private static final int REQUEST_CODE_READ_MEDIA_AUDIO = 1001;
private static final int REQUEST_CODE_PICK_AUDIO = 1002;
+ private A_ImportFragmentAdapter adapter;
+ private A_VMImport viewModel;
@Override
protected FragmentAImportBinding getFragmentVb() {
@@ -28,47 +39,59 @@ public class A_ImportFragment extends BaseFragment {
@Override
protected void initView() {
+ viewModel = new ViewModelProvider(this).get(A_VMImport.class);
+ adapter = new A_ImportFragmentAdapter(requireContext());
- initData();
+ Vb.importRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
+ Vb.importRecycler.setAdapter(adapter);
+
+ // 监听音频文件列表变化
+ viewModel.getAudioFiles().observe(getViewLifecycleOwner(), this::updateAudioList);
initEvent();
-
}
- public void initData() {
-
+ // 更新音频文件列表
+ private void updateAudioList(List audioFiles) {
+ adapter.setAudioFiles(audioFiles);
}
+ // 初始化事件
public void initEvent() {
-
Vb.setting.setOnClickListener(v -> {
Intent intent = new Intent(getContext(), A_SettingActivity.class);
startActivity(intent);
});
- Vb.add.setOnClickListener(v -> {
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_MEDIA_AUDIO) != PackageManager.PERMISSION_GRANTED) {
- ActivityCompat.requestPermissions(requireActivity(),
- new String[]{Manifest.permission.READ_MEDIA_AUDIO},
- REQUEST_CODE_READ_MEDIA_AUDIO);
- } else {
- openAudioPicker();
- }
- } else {
- if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
- ActivityCompat.requestPermissions(requireActivity(),
- new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
- REQUEST_CODE_READ_MEDIA_AUDIO);
- } else {
- openAudioPicker();
- }
- }
-
+ // 添加按钮的点击事件,打开文件选择器
+ Vb.add.setOnClickListener(v -> checkAndOpenAudioPicker());
+ // 适配器中的删除按钮点击事件
+ adapter.setOnDeleteClickListener(filePath -> {
+ viewModel.markAudioAsDeleted(filePath);
+ Toast.makeText(getContext(), "Audio marked as deleted", Toast.LENGTH_SHORT).show();
});
+ }
+ // 检查并请求权限,然后打开音频选择器
+ private void checkAndOpenAudioPicker() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_MEDIA_AUDIO)
+ != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(requireActivity(),
+ new String[]{Manifest.permission.READ_MEDIA_AUDIO}, REQUEST_CODE_READ_MEDIA_AUDIO);
+ } else {
+ openAudioPicker(); // 权限已授予,打开选择器
+ }
+ } else {
+ if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(requireActivity(),
+ new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_CODE_READ_MEDIA_AUDIO);
+ } else {
+ openAudioPicker(); // 权限已授予,打开选择器
+ }
+ }
}
// 处理权限请求结果
@@ -77,6 +100,7 @@ public class A_ImportFragment extends BaseFragment {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_READ_MEDIA_AUDIO) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // 权限已授予,打开音频选择器
openAudioPicker();
} else {
Toast.makeText(requireContext(), "Permission denied", Toast.LENGTH_SHORT).show();
@@ -86,26 +110,48 @@ public class A_ImportFragment extends BaseFragment {
// 打开音频选择器
private void openAudioPicker() {
+ // 检查外部存储状态
+ String state = Environment.getExternalStorageState();
+ if (!Environment.MEDIA_MOUNTED.equals(state)) {
+ Toast.makeText(getContext(), "外部存储不可用", Toast.LENGTH_LONG).show();
+ return;
+ }
+
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+ intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); // 持久 URI 权限
intent.setType("audio/*"); // 设置 MIME 类型为音频
intent.addCategory(Intent.CATEGORY_OPENABLE); // 限制为可打开的文件
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 授予 URI 读取权限
startActivityForResult(intent, REQUEST_CODE_PICK_AUDIO);
}
-
- // 处理 Activity 结果
+ // 处理音频选择器结果
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_PICK_AUDIO && resultCode == RESULT_OK) {
if (data != null) {
- Uri audioUri = data.getData();
- // 处理音频文件,例如播放或显示在列表中
- Toast.makeText(requireContext(), "Audio Selected: " + audioUri.toString(), Toast.LENGTH_SHORT).show();
+ Uri selectedAudioUri = data.getData();
+ if (selectedAudioUri != null) {
+ // 授予 URI 读取权限
+ requireActivity().grantUriPermission(requireActivity().getPackageName(), selectedAudioUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ // 将音频添加到 ViewModel 中
+ try {
+ viewModel.addAudioFile(selectedAudioUri);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
}
}
-
+ // 刷新音频列表
+ @Override
+ public void onResume() {
+ super.onResume();
+ // 重新获取音频文件列表并更新 UI
+ viewModel.getAudioFiles().observe(getViewLifecycleOwner(), this::updateAudioList);
+ }
}
-
diff --git a/app/src/main/java/com/hi/music/player/ui/fragmnt/viewmodel/A_VMImport.java b/app/src/main/java/com/hi/music/player/ui/fragmnt/viewmodel/A_VMImport.java
index cd90a5d..01fb1cb 100644
--- a/app/src/main/java/com/hi/music/player/ui/fragmnt/viewmodel/A_VMImport.java
+++ b/app/src/main/java/com/hi/music/player/ui/fragmnt/viewmodel/A_VMImport.java
@@ -1,9 +1,127 @@
package com.hi.music.player.ui.fragmnt.viewmodel;
-import androidx.lifecycle.ViewModel;
+import android.app.Application;
+import android.database.Cursor;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.provider.MediaStore;
-public class A_VMImport extends ViewModel {
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.MutableLiveData;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.hi.music.player.javabean.A_data.AudioItem;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import android.content.SharedPreferences;
+import android.content.Context;
+import android.util.Log;
+import android.widget.Toast;
+
+public class A_VMImport extends AndroidViewModel {
+
+ private static final String PREFS_NAME = "audio_files_prefs";
+ private static final String AUDIO_FILES_KEY = "audio_files";
+
+ private final MutableLiveData> audioFilesLiveData = new MutableLiveData<>();
+ private final SharedPreferences sharedPreferences;
+ private final Gson gson;
+ private final List audioFiles;
+
+ public A_VMImport(Application application) {
+ super(application);
+ sharedPreferences = application.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ gson = new Gson();
+ audioFiles = loadAudioFiles(); // 加载已保存的音频文件列表
+ audioFilesLiveData.setValue(new ArrayList<>(audioFiles)); // 设置 LiveData 的初始值
+ }
+
+ // 添加选中的音频文件
+ public void addAudioFile(Uri audioUri) throws IOException {
+ String fileName = null;
+ long duration = 0;
+
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ try {
+ retriever.setDataSource(getApplication(), audioUri);
+ fileName = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
+ String durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
+ if (durationStr != null) {
+ duration = Long.parseLong(durationStr);
+ }
+ } catch (Exception e) {
+ Log.e("ViewModel", "Failed to retrieve audio metadata", e);
+ } finally {
+ retriever.release(); // 释放资源
+ }
+
+ // 如果 fileName 为空,使用 URI 的最后一部分作为文件名
+ if (fileName == null || fileName.isEmpty()) {
+ fileName = audioUri.getLastPathSegment();
+ }
+
+ if (duration > 0) {
+ AudioItem newItem = new AudioItem(fileName, audioUri.toString(), "");
+ newItem.setDuration(formatDuration(duration)); // 格式化时长
+
+ // 防止重复添加
+ if (!audioFiles.contains(newItem)) {
+ audioFiles.add(newItem);
+ saveAudioFiles(); // 每次添加后保存到 SharedPreferences
+ audioFilesLiveData.setValue(new ArrayList<>(audioFiles)); // 更新 LiveData
+ } else {
+ Toast.makeText(getApplication(), "Audio file already exists in the list.", Toast.LENGTH_SHORT).show();
+ }
+ } else {
+ Toast.makeText(getApplication(), "Failed to retrieve audio duration.", Toast.LENGTH_SHORT).show();
+ }
+ }
+ // 保存音频文件列表到 SharedPreferences
+ private void saveAudioFiles() {
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ String json = gson.toJson(audioFiles);
+ editor.putString(AUDIO_FILES_KEY, json);
+ editor.apply();
+ }
+
+ // 从 SharedPreferences 加载音频文件列表
+ private List loadAudioFiles() {
+ String json = sharedPreferences.getString(AUDIO_FILES_KEY, null);
+ Type type = new TypeToken>() {}.getType();
+ List loadedFiles = gson.fromJson(json, type);
+ return loadedFiles != null ? loadedFiles : new ArrayList<>(); // 返回加载的文件列表或空列表
+ }
+
+ // 标记音频文件为已删除
+ public void markAudioAsDeleted(String filePath) {
+ // 从列表中移除该音频
+ for (AudioItem item : new ArrayList<>(audioFiles)) {
+ if (item.getFile().equals(filePath)) {
+ audioFiles.remove(item);
+ break;
+ }
+ }
+
+ // 更新 LiveData
+ audioFilesLiveData.setValue(new ArrayList<>(audioFiles)); // 更新 LiveData
+ saveAudioFiles(); // 更新 SharedPreferences
+ }
+
+ // 获取音频文件列表
+ public MutableLiveData> getAudioFiles() {
+ return audioFilesLiveData;
+ }
+
+ // 格式化时长
+ private String formatDuration(long duration) {
+ long minutes = (duration / 1000) / 60;
+ long seconds = (duration / 1000) % 60;
+ return String.format(Locale.getDefault(), "%d:%02d", minutes, seconds);
+ }
}
diff --git a/app/src/main/res/drawable/options.xml b/app/src/main/res/drawable/options.xml
new file mode 100644
index 0000000..80184ea
--- /dev/null
+++ b/app/src/main/res/drawable/options.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_ahome.xml b/app/src/main/res/layout/activity_ahome.xml
index b097461..0bfe410 100644
--- a/app/src/main/res/layout/activity_ahome.xml
+++ b/app/src/main/res/layout/activity_ahome.xml
@@ -33,7 +33,6 @@
android:layout_marginBottom="-5dp"
android:background="@drawable/round_rectangle"
android:backgroundTint="#80F988"
- android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/home_tab_layout">
@@ -60,7 +59,7 @@
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
- android:src="@mipmap/cover" />
+ android:src="@mipmap/default_image" />
diff --git a/app/src/main/res/layout/activity_aplay.xml b/app/src/main/res/layout/activity_aplay.xml
index 8099962..a6b0e2d 100644
--- a/app/src/main/res/layout/activity_aplay.xml
+++ b/app/src/main/res/layout/activity_aplay.xml
@@ -36,7 +36,7 @@
android:layout_height="300dp"
android:layout_marginTop="66dp"
android:contentDescription="Record"
- android:src="@mipmap/cover"
+ android:src="@mipmap/default_image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/now_playing_text" />
diff --git a/app/src/main/res/layout/fragment_a_import.xml b/app/src/main/res/layout/fragment_a_import.xml
index 6222ef5..9495e53 100644
--- a/app/src/main/res/layout/fragment_a_import.xml
+++ b/app/src/main/res/layout/fragment_a_import.xml
@@ -14,6 +14,7 @@
android:layout_marginTop="32sp"
android:text="Parents voice"
android:textSize="32sp"
+ android:textColor="@color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@@ -30,7 +31,7 @@
app:layout_constraintTop_toTopOf="parent" />
diff --git a/app/src/main/res/layout/item_a_home_1.xml b/app/src/main/res/layout/item_a_home_1.xml
index 83f2070..34c14b9 100644
--- a/app/src/main/res/layout/item_a_home_1.xml
+++ b/app/src/main/res/layout/item_a_home_1.xml
@@ -16,7 +16,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
- android:src="@mipmap/cover" />
+ android:src="@mipmap/default_image" />
+ android:src="@mipmap/default_image" />
diff --git a/app/src/main/res/layout/item_a_home_3.xml b/app/src/main/res/layout/item_a_home_3.xml
index 395777d..2dce966 100644
--- a/app/src/main/res/layout/item_a_home_3.xml
+++ b/app/src/main/res/layout/item_a_home_3.xml
@@ -21,7 +21,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
- android:src="@mipmap/cover" />
+ android:src="@mipmap/default_image" />
diff --git a/app/src/main/res/layout/item_a_import.xml b/app/src/main/res/layout/item_a_import.xml
new file mode 100644
index 0000000..7e363ec
--- /dev/null
+++ b/app/src/main/res/layout/item_a_import.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-xxxhdpi/default_image.png b/app/src/main/res/mipmap-xxxhdpi/default_image.png
new file mode 100644
index 0000000..a4e6713
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/default_image.png differ