问题描述
所以我有以下代码,并且工作正常,但是我想在用户的“通知”区域中显示媒体控件,以便他们可以在应用程序处于后台时随意播放和停止音乐。
我想知道这是怎么做的?
代码:
package com.radiomedia.a1liferadio;
import androidx.appcompat.app.AppCompatActivity;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private ImageView imagePlayPause;
private TextView textCurrentTime,textTotalDuration;
private SeekBar playerSeekBar;
private MediaPlayer mediaPlayer;
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imagePlayPause = findViewById(R.id.imagePlayPause);
textCurrentTime = findViewById(R.id.textCurrentTime);
textTotalDuration = findViewById(R.id.textTotalDuration);
playerSeekBar = findViewById(R.id.playerSeekBar);
mediaPlayer = new MediaPlayer();
playerSeekBar.setMax(100);
imagePlayPause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mediaPlayer.isPlaying())
{
handler.removeCallbacks(updater);
mediaPlayer.pause();
imagePlayPause.setImageResource(R.drawable.ic_play);
}else{
mediaPlayer.start();
imagePlayPause.setImageResource(R.drawable.ic_pause);
updateSeekBar();
}
}
});
prepareMediaPlayer();
}
private void prepareMediaPlayer() {
try {
mediaPlayer.setDataSource("http://stream.radiomedia.com.au:8003/stream"); //url of media
mediaPlayer.prepare();
textTotalDuration.setText(milliSecondsToTimer(mediaPlayer.getDuration()));
} catch (Exception exception){
Toast.makeText(this,exception.getMessage(),Toast.LENGTH_SHORT).show();
}
};
private Runnable updater = new Runnable() {
@Override
public void run() {
updateSeekBar();
long currentDuration = mediaPlayer.getCurrentPosition();
textCurrentTime.setText(milliSecondsToTimer(currentDuration));
}
};
private void updateSeekBar(){
if(mediaPlayer.isPlaying()) {
playerSeekBar.setProgress((int) (((float) mediaPlayer.getCurrentPosition() / mediaPlayer.getDuration()) * 100));
handler.postDelayed(updater,1000);
}
};
private String milliSecondsToTimer(long milliSeconds) {
String timerString = "";
String secondsString;
int hours = (int)(milliSeconds / (1000 * 60 * 60));
int minutes = (int)(milliSeconds % (1000 * 60 * 60)) / (1000 *60);
int seconds = (int)((milliSeconds % (1000 * 60 *60)) % (1000 * 60) / 1000);
if(hours > 0)
{
timerString = hours + ":";
}
if(seconds < 10)
{
secondsString = "0" + seconds;
}else{
secondsString = "" + seconds;
}
timerString = timerString + minutes + ":" + secondsString;
return timerString;
}
}
解决方法
如果要继续使用MediaPlayer,则应遵循此old google mediaplayer sample(如果主版本崩溃,请使用此commit)。
但是,如果您不介意使用ExoPlayer,则应该改用new one,因为ExoPlayer已经可以处理部分通知内容。
但是在这些示例项目中,您可以看到:
- 这不仅仅是通知,它还与人们按下耳机上的播放按钮等有关。
- 您确实需要为此服务来绑定到通知以处理操作。MEDIA_BUTTON意图。
像您正在使用的那样,使用MediaSession添加此类通知并使用MediaPlayer(而不是ExoPlayer)添加服务的最小代码如下所示: 将媒体兼容库添加到应用的构建包中:
dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation "androidx.media:media:1.1.0"
}
然后创建一个用于创建通知的类:
package com.radiomedia.a1liferadio;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import androidx.media.app.NotificationCompat.MediaStyle;
import androidx.media.session.MediaButtonReceiver;
/**
* Keeps track of a notification and updates it automatically for a given MediaSession. This is
* required so that the music service don't get killed during playback.
*/
public class MediaNotificationManager {
public static final int NOTIFICATION_ID = 412;
private static final String TAG = MediaNotificationManager.class.getSimpleName();
private static final String CHANNEL_ID = "com.example.android.musicplayer.channel";
private static final int REQUEST_CODE = 501;
private final MediaSessionService mService;
private final NotificationCompat.Action mPlayAction;
private final NotificationCompat.Action mPauseAction;
private final NotificationManager mNotificationManager;
public MediaNotificationManager(MediaSessionService musicContext) {
mService = musicContext;
mNotificationManager =
(NotificationManager) mService.getSystemService(Service.NOTIFICATION_SERVICE);
mPlayAction =
new NotificationCompat.Action(
R.drawable.ic_play,"play",MediaButtonReceiver.buildMediaButtonPendingIntent(
mService,PlaybackStateCompat.ACTION_PLAY));
mPauseAction =
new NotificationCompat.Action(
R.drawable.ic_pause,"pause",PlaybackStateCompat.ACTION_PAUSE));
// Cancel all notifications to handle the case where the Service was killed and
// restarted by the system.
mNotificationManager.cancelAll();
}
public void onDestroy() {
Log.d(TAG,"onDestroy: ");
}
public NotificationManager getNotificationManager() {
return mNotificationManager;
}
public Notification getNotification(MediaMetadataCompat metadata,@NonNull PlaybackStateCompat state,MediaSessionCompat.Token token) {
boolean isPlaying = state.getState() == PlaybackStateCompat.STATE_PLAYING;
MediaDescriptionCompat description = metadata.getDescription();
NotificationCompat.Builder builder =
buildNotification(state,token,isPlaying,description);
return builder.build();
}
private NotificationCompat.Builder buildNotification(@NonNull PlaybackStateCompat state,MediaSessionCompat.Token token,boolean isPlaying,MediaDescriptionCompat description) {
// Create the (mandatory) notification channel when running on Android Oreo.
if (isAndroidOOrHigher()) {
createChannel();
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(mService,CHANNEL_ID);
builder.setStyle(
new MediaStyle()
.setMediaSession(token)
.setShowActionsInCompactView(0)
// For backwards compatibility with Android L and earlier.
.setShowCancelButton(true)
.setCancelButtonIntent(
MediaButtonReceiver.buildMediaButtonPendingIntent(
mService,PlaybackStateCompat.ACTION_STOP)))
.setColor(ContextCompat.getColor(mService,R.color.colorPrimary))
.setSmallIcon(R.drawable.ic_play)
// Pending intent that is fired when user clicks on notification.
.setContentIntent(createContentIntent())
// Title - Usually Song name.
.setContentTitle(description.getTitle())
// When notification is deleted (when playback is paused and notification can be
// deleted) fire MediaButtonPendingIntent with ACTION_PAUSE.
.setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(
mService,PlaybackStateCompat.ACTION_PAUSE));
builder.addAction(isPlaying ? mPauseAction : mPlayAction);
return builder;
}
// Does nothing on versions of Android earlier than O.
@RequiresApi(Build.VERSION_CODES.O)
private void createChannel() {
if (mNotificationManager.getNotificationChannel(CHANNEL_ID) == null) {
// The user-visible name of the channel.
CharSequence name = "MediaSession";
// The user-visible description of the channel.
String description = "MediaSession and MediaPlayer";
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID,name,importance);
// Configure the notification channel.
mChannel.setDescription(description);
mChannel.enableLights(true);
// Sets the notification light color for notifications posted to this
// channel,if the device supports this feature.
mChannel.setLightColor(Color.RED);
mChannel.enableVibration(true);
mChannel.setVibrationPattern(
new long[]{100,200,300,400,500,400});
mNotificationManager.createNotificationChannel(mChannel);
Log.d(TAG,"createChannel: New channel created");
} else {
Log.d(TAG,"createChannel: Existing channel reused");
}
}
private boolean isAndroidOOrHigher() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
private PendingIntent createContentIntent() {
Intent openUI = new Intent(mService,MainActivity.class);
openUI.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
return PendingIntent.getActivity(
mService,REQUEST_CODE,openUI,PendingIntent.FLAG_CANCEL_CURRENT);
}
}
然后创建一个用于处理播放并按下通知按钮的Service类:
package com.radiomedia.a1liferadio;
import android.app.Notification;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.os.SystemClock;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.view.KeyEvent;
import androidx.annotation.Nullable;
public class MediaSessionService extends Service {
public MediaPlayer mediaPlayer;
public static final String TAG = "MediaSessionService";
public static final int NOTIFICATION_ID = 888;
private MediaNotificationManager mMediaNotificationManager;
private MediaSessionCompat mediaSession;
@Override
public void onCreate() {
super.onCreate();
mediaPlayer = new MediaPlayer();
mMediaNotificationManager = new MediaNotificationManager(this);
mediaSession = new MediaSessionCompat(this,"SOME_TAG");
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mediaSession.setCallback(new MediaSessionCompat.Callback() {
@Override
public void onPlay() {
mediaPlayer.start();
}
@Override
public void onPause() {
mediaPlayer.pause();
}
});
Notification notification =
mMediaNotificationManager.getNotification(
getMetadata(),getState(),mediaSession.getSessionToken());
startForeground(NOTIFICATION_ID,notification);
}
public MediaMetadataCompat getMetadata() {
MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
builder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST,"artist");
builder.putString(MediaMetadataCompat.METADATA_KEY_TITLE,"title");
builder.putLong(
MediaMetadataCompat.METADATA_KEY_DURATION,mediaPlayer.getDuration()
);
return builder.build();
}
private PlaybackStateCompat getState() {
long actions = mediaPlayer.isPlaying() ? PlaybackStateCompat.ACTION_PAUSE : PlaybackStateCompat.ACTION_PLAY;
int state = mediaPlayer.isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED;
final PlaybackStateCompat.Builder stateBuilder = new PlaybackStateCompat.Builder();
stateBuilder.setActions(actions);
stateBuilder.setState(state,mediaPlayer.getCurrentPosition(),1.0f,SystemClock.elapsedRealtime());
return stateBuilder.build();
}
@Override
public int onStartCommand(Intent intent,int flags,int startId) {
if ("android.intent.action.MEDIA_BUTTON".equals(intent.getAction())) {
KeyEvent keyEvent = (KeyEvent) intent.getExtras().get("android.intent.extra.KEY_EVENT");
if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PAUSE) {
mediaPlayer.pause();
} else {
mediaPlayer.start();
}
}
return super.onStartCommand(intent,flags,startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
更新您的清单以声明服务并发送操作。MEDIA_BUTTON意图。
<service
android:name=".MediaSessionService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON"/>
</intent-filter>
</service>
<receiver android:name="androidx.media.session.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON"/>
</intent-filter>
</receiver>
OREO之前的设备需要上面的MediaButtonReceiver,它将事件转发给那些平台上的服务。
然后从您的主要活动中启动服务。
ContextCompat.startForegroundService(
MainActivity.this.getApplicationContext(),new Intent(MainActivity.this.getApplicationContext(),MediaSessionService.class));
这里仍然要做的事情是使您的主要活动以某种方式将动作发送到您的服务或服务中的播放器,在更改时更新通知和服务状态,但是您应该能够使用此代码和图来工作从Google示例代码中删除其余部分。
,有两种情况:
第一
-
如果要在后台播放歌曲,则必须启动Forground服务。 forground服务带有通知。(将导航栏位置,播放暂停状态,播放列表或视频网址传递给服务)。
-
使用媒体控件创建自定义通知后,所需的任何内容
-
您必须添加要播放的媒体播放器,还必须将位置,播放暂停状态等参数从先前的活动播放器状态传递到服务播放器。
-
点击通知后,您必须将相同的参数传递给活动。
第二
通过附加播放器启动forground服务并将其绑定到活动,并同时处理通知和活动中的所有控件。
-
您可以通过froground服务将播放器控件传递给通知,以便在后台开始播放歌曲。
-
您可以使用首选项或tinydb以获得最佳性能,以使参数在第一种情况下可以通过。