通知传输控件似乎没有任何作用



我创建了一个应用程序,可以使用MediaBrowserServiceCompat和MediaSessionCompat播放音频。根据安卓开发者网站上的说明,我在MediaSessionCompat.Callback((.onPlay((方法中创建了一个通知,该方法使用MediaStyle提供传输控件,当提供适当的令牌时,这些控件应该连接到我的媒体会话。应用程序内控件可按预期播放和暂停工作,即使在应用程序关闭并再次打开时也是如此。该服务似乎按预期运行。

然而,问题是,尽管通知按预期显示,但包含的暂停按钮似乎无法执行任何操作。尽管android开发者的例子表明应该存在取消按钮,但事实并非如此。此外,该示例还指出,应该通过取消通知来停止服务,但事实并非如此。

可以说,下面的代码片段中没有任何内容可以正常工作。但事实上,通知确实出现了。

private NotificationCompat.Builder getMediaNotificationBuilder() {
Intent contentIntent = new Intent(mContext, MainActivity.class);
contentIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingContentIntent = PendingIntent.getActivity(mContext, 0, contentIntent, 0);
MediaControllerCompat controller = mMediaSession.getController();
NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, "PODCAST");
builder
.setContentTitle("PODCAST")
.setContentText("THIS IS A PLACE HOLDER.")
.setSubText("Still a place holder.")
// Enable launching the player by clicking the notification
.setContentIntent(pendingContentIntent)
// Stop the service when the notification is swiped away
.setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(mContext, PlaybackStateCompat.ACTION_STOP))
// Make the transport controls visible on the lockscreen
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
// Add an app icon and set its accent color
// Be careful about the color
.setSmallIcon(R.drawable.ic_launcher_background)
.setColor(ContextCompat.getColor(mContext, R.color.colorPrimaryDark))
// Add a pause button
.addAction(new NotificationCompat.Action(
R.drawable.ic_pause, "Pause",
MediaButtonReceiver.buildMediaButtonPendingIntent(mContext,
PlaybackStateCompat.ACTION_PAUSE)))
// Take advantage of MediaStyle features
.setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mMediaSession.getSessionToken())
.setShowActionsInCompactView(0)
// Add a cancel button
.setShowCancelButton(true)
.setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(mContext,
PlaybackStateCompat.ACTION_STOP)));
return builder;
}

然后我将此通知传递给

startForground(1, getMediaNotificationBuilder().build())

然后启动服务。

如果有必要,我很乐意分享整个应用程序的源代码。我确信我错过了一些非常简单的东西。

我怀疑我遗漏了一些非常简单的东西。为了让我的MediaBrowserServiceCompat子类对我的通知控件做出反应,我需要覆盖Service基类中的onStartCommand,并将Intent传递给我的MediaSessionCompat对象。完成此操作后,MediaSessionCompat.Callback应该处理该命令,假设它已经被编程为这样做。这就是我的MediaBrowserService类中的代码。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(LOG_TAG, "onStartCommand(): received intent " + intent.getAction() + " with flags " + flags + " and startId " + startId);
MediaButtonReceiver.handleIntent(mMediaSession, intent);
return super.onStartCommand(intent, flags, startId);
}

添加完这些代码后,您应该也可以在logcat中看到该方法。万一有人仍然遗漏了什么,你至少会知道代码对你按下的按钮做出了响应。

编辑:

至于通过刷通知来停止服务,我误解了通知和用户之间的互动。通知可以被用户刷走,但前提是媒体首先被暂停。标准媒体播放器应用程序的通知控件进一步支持了这种模式。这是有道理的,因为用户在听其他内容的过程中可能会意外地滑动控件。

此外,我决定包含MediaBrowserServiceCompat类的全部源代码,希望这些附加信息为讨论提供一些上下文

public class MediaPlaybackService extends MediaBrowserServiceCompat {
private static final String LOG_TAG = "MediaPlaybackService";
private static final String MY_MEDIA_ROOT_ID = "media_root_id";
private static final String MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id";
// Volume levels: Normal and Duck
// VOLUME_DUCK is the volume we set the media player to when we lose audio focus, but are allowed to reduce the volume instead of stopping playback.
public static final float VOLUME_DUCK = 0.2f;
public static final float VOLUME_NORMAL = 1.0f;
private MediaSessionCompat mMediaSession;
private MediaPlayer        mMediaPlayer;
// Current local media player state
private PlaybackStateCompat.Builder mStateBuilder;
private int                         mState = PlaybackStateCompat.STATE_NONE;
private final class MediaSessionCallback extends MediaSessionCompat.Callback implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener, AudioManager.OnAudioFocusChangeListener{
private Context mContext;
private AudioManager mAudioManager;
// Declare the "SHIT THAT'S LOUD" intent, any broadcast receiver
// that is connected to it will trigger when the headphones come unplugged
private IntentFilter shitThatsLoudIntentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
private BroadcastReceiver shitThatsLoudBroadcastReceiver = new BroadcastReceiver() {
// TODO: Put me in a separate class
@Override
public void onReceive(Context context, Intent intent) {
Log.d(LOG_TAG, "SHIT THATS LOUD! The headphones have come unplugged!");
}
};
private MediaSessionCallback(Context context) {
super();
mContext = context;
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
initMediaPlayer();
}
private void initMediaPlayer() {
try {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setDataSource("https://www.blogtalkradio.com/kylekulinski/2018/10/15/the-kyle-kulinski-show.mp3");
mMediaPlayer.setOnPreparedListener  (this);
mMediaPlayer.setOnCompletionListener(this);
mMediaPlayer.setOnErrorListener     (this);
mMediaPlayer.prepare();
} catch (IOException e) {
Log.e(LOG_TAG, ".initMediaPlayer(): IOException: "+e.toString());
}
}
private void mediaPlay() {
registerReceiver(shitThatsLoudBroadcastReceiver, shitThatsLoudIntentFilter);
if (mAudioManager.requestAudioFocus(getAudioFocusRequest()) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d(LOG_TAG, "Audio focus request granted.");
mState = PlaybackStateCompat.STATE_PLAYING;
mStateBuilder.setActions(PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_STOP);
mStateBuilder.setState(mState, mMediaPlayer.getCurrentPosition(), 1.0f, SystemClock.elapsedRealtime());
mMediaSession.setPlaybackState(mStateBuilder.build());
mMediaSession.setActive(true);
mMediaPlayer.start();
startService(new Intent(mContext, MediaPlaybackService.class));
startForeground(1, getMediaNotificationBuilder().build());
}
}
private void mediaPause() {
unregisterReceiver(shitThatsLoudBroadcastReceiver);
mState = PlaybackStateCompat.STATE_PAUSED;
mStateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_STOP);
mStateBuilder.setState(mState, mMediaPlayer.getCurrentPosition(), 1.0f, SystemClock.elapsedRealtime());
mMediaSession.setPlaybackState(mStateBuilder.build());
mMediaPlayer.pause();
stopForeground(false);
}
private void releaseResources() {
mMediaSession.setActive(false);
mAudioManager.abandonAudioFocusRequest(getAudioFocusRequest());
unregisterReceiver(shitThatsLoudBroadcastReceiver);
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.reset();
mMediaPlayer.release();
mMediaPlayer = null;
}
stopSelf();
stopForeground(true);
}
private NotificationCompat.Builder getMediaNotificationBuilder() {
Intent contentIntent = new Intent(mContext, MainActivity.class);
contentIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingContentIntent = PendingIntent.getActivity(mContext, 0, contentIntent, 0);
MediaControllerCompat controller = mMediaSession.getController();
NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, "PODCAST");
builder
.setContentTitle("PODCAST")
.setContentText("THIS IS A PLACE HOLDER.")
.setSubText("Still a place holder.")
// Enable launching the player by clicking the notification
.setContentIntent(pendingContentIntent)
// Stop the service when the notification is swiped away
.setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(mContext, PlaybackStateCompat.ACTION_STOP))
// Make the transport controls visible on the lockscreen
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
// Add an app icon and set its accent color
// Be careful about the color
.setSmallIcon(R.drawable.ic_launcher_background)
.setColor(ContextCompat.getColor(mContext, R.color.colorPrimaryDark))
// Add a pause button
.addAction(new NotificationCompat.Action(
R.drawable.ic_pause, "Pause",
MediaButtonReceiver.buildMediaButtonPendingIntent(mContext,
PlaybackStateCompat.ACTION_PLAY_PAUSE)))
// Take advantage of MediaStyle features
.setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mMediaSession.getSessionToken())
.setShowActionsInCompactView(0)
// Add a cancel button
.setShowCancelButton(true)
.setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(mContext,
PlaybackStateCompat.ACTION_STOP)));
return builder;
}
@Override
public void onPlay() {
super.onPlay();
Log.d(LOG_TAG, "I tried to play music");
mediaPlay();
}
@Override
public void onPause() {
super.onPause();
Log.d(LOG_TAG, "I Tried to pause");
mediaPause();
}
@Override
public void onStop() {
super.onStop();
releaseResources();
}
private AudioFocusRequest getAudioFocusRequest() {
// Request audio focus for playback, this registers the afChangeListener
AudioAttributes attrs = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
AudioFocusRequest audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setOnAudioFocusChangeListener(this)
.setAudioAttributes(attrs)
.build();
return audioFocusRequest;
}
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
Log.d(LOG_TAG, "Audio focus has been restored after it was transiently arrested by and intrusive app.  We can now start playing audio normally again.");
mMediaPlayer.setVolume(VOLUME_NORMAL, VOLUME_NORMAL);
mediaPlay();
break;
case AudioManager.AUDIOFOCUS_LOSS:
Log.d(LOG_TAG, "Audio focus was lost flat out. Save what we were doing so we don't forget about it later.");
mediaPause();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
Log.d(LOG_TAG, "Audio focus was lost (Transient) but we might get it back later, still stop and save though.");
mediaPause();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
Log.d(LOG_TAG, "Audio focus was lost but was just need to keep it down instead of stopping.");
mMediaPlayer.setVolume(VOLUME_DUCK, VOLUME_DUCK);
break;
default:
Log.d(LOG_TAG, "Ignoring unsupported audio focus change: "+focusChange);
break;
}
}
@Override
public void onPrepared(MediaPlayer mp) {
Log.d(LOG_TAG, "MediaSessionCallback.onPrepared(): MediaPlayer is prepared!");
// The media player is done preparing. That means we can start playing if we
// have audio focus.
}
@Override
public void onCompletion(MediaPlayer mp) {
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Log.e(LOG_TAG, "Media player error: what=" + what + ", extra=" + extra);
return false; // true indicates we handled the error
}
}
@Override
public void onCreate() {
super.onCreate();
// Create a MediaSessionCompat
mMediaSession = new MediaSessionCompat(this, LOG_TAG);
// Set the session's token so that client activities can communicate with it.
setSessionToken(mMediaSession.getSessionToken());
// MediaSessionCallback() has methods that handle callbacks from a media controller
mMediaSession.setCallback(new MediaSessionCallback(this));
// Enable callbacks from media buttons and transport controls
mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
);
// Set initial PlaybackState with ACTION_PLAY, so that media buttons start the player
mStateBuilder = new PlaybackStateCompat.Builder()
.setActions(
PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE
);
mMediaSession.setPlaybackState(mStateBuilder.build());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(LOG_TAG, "onStartCommand(): received intent " + intent.getAction() + " with flags " + flags + " and startId " + startId);
MediaButtonReceiver.handleIntent(mMediaSession, intent);
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
return new BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null);
}
@Override
public void onLoadChildren(@NonNull String parentMediaId, @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
//  Browsing not allowed
if (TextUtils.equals(MY_EMPTY_MEDIA_ROOT_ID, parentMediaId)) {
result.sendResult(null);
return;
}
// TODO: If in the future we decide that we do want this class to handle the podcast metadata
// Then we must adapt what ever data podcastFactory produces into a List of MediaBrowserCompat.MediaItem objects
// The constructor of MediaItem requires that a MediaDescription object be passed to it.
// MediaDescription has a builder class which contains methods for setting Title, Artist, Uri, etc...
// MediaDescription.Builder mMediaDescriptionBuilder = new MediaDescription.Builder();
// mMediaDescriptionBuilder.setTitle(String);
// mMediaDescriptionBuilder.setMediaUri(String);
// MediaDescription mMediaDescription = mMediaDescriptionBuilder.build()
// MediaBrowserCompat.MediaItem mMediaItem =
//     new MediaBrowserCompat.MediaItem(
//          mMediaDescription,
//          int flags -> FLAG_BROWSABLE and/or FLAG_PLAYABLE
// );
// add MediaItem to SomeList
// result.sendResult(SomeList);
}

相关内容

  • 没有找到相关文章

最新更新