我有一个应用程序,启动一个前台服务播放一些媒体内容。我希望能够通过智能手表或耳机上的媒体按钮来控制它,并且还能从媒体样式通知中控制它。
然而,我发现无法始终正确地使用这些媒体按钮。在日志中,我可以看到它们经常被发送到其他应用程序,即使我先启动了MediaSession和播放也是如此。
尽管我已经定义了一个MediaCallback并且设置setActive(true),但我仍然无法让它正常工作。
清单文件:
然而,我发现无法始终正确地使用这些媒体按钮。在日志中,我可以看到它们经常被发送到其他应用程序,即使我先启动了MediaSession和播放也是如此。
尽管我已经定义了一个MediaCallback并且设置setActive(true),但我仍然无法让它正常工作。
清单文件:
<service
android:name=".services.MediaControllerService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.FOREGROUND_SERVICE">
<intent-filter android:priority="999">
<action android:name="android.intent.action.MEDIA_BUTTON"/>
</intent-filter>
</service>
代码(注意包,这在Android 10中很困难,需要找到正确的包组合,才能一起使用并给我提供MediaStyle)...
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.media.MediaMetadata;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.view.KeyEvent;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.media.session.MediaButtonReceiver;
public class TextToSpeechMediaControllerService extends Service {
public static final String START_SERVICE_ACTION_INTENT = "serviceStart";
private TextToSpeechPlayer player;
private MediaMetadataCompat mediaMetaData;
private MediaSessionCompat mediaSession;
private TextToSpeechMediaControllerService.AudioFocusHelper mAudioFocusHelper;
private AudioManager mAudioManager;
private final String NOTIFICATION_CHANNEL_TEXT_TO_SPEECH_CONTROLS = "fp_tts_media_controls";
public MediaControllerCompat.TransportControls transportControls;
private String pageTitle;
private String pageAddress;
private String pageDomain;
private SpeechBank currentSpeechBank;
private MediaButtonReceiver mediaButtonReceiver;
private AudioFocusRequest audioFocusRequest;
private AudioAttributes playbackAttributes;
private Handler handler;
public TextToSpeechMediaControllerService() {
}
@Override
public void onCreate() {
Log.d("TTSMEDIAPLAYER", "---------- STARTING SERVICE ----------");
player = new TextToSpeechPlayer(this);
mAudioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
mAudioFocusHelper = new TextToSpeechMediaControllerService.AudioFocusHelper();
mediaSession = new MediaSessionCompat(this, "fpt2s");
mediaSession.setCallback(callback);
mediaSession.setActive(true);
handler = new Handler(); // something to do with handling delayed focus https://developer.android.com/guide/topics/media-apps/audio-focus#audio-focus-change
transportControls = mediaSession.getController().getTransportControls();
NotificationChannel channel = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
channel = new NotificationChannel(NOTIFICATION_CHANNEL_TEXT_TO_SPEECH_CONTROLS,
getString(R.string.media_controls_notification_channel_title),
NotificationManager.IMPORTANCE_HIGH);
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
}
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("TTSMEDIAPLAYER", "---------- onStartCOmmand ("+intent.getAction()+", startId) ----------");
MediaButtonReceiver.handleIntent(mediaSession, intent); // Required to catch media button events and send them to mediasession callback
if (intent !=null && intent.getExtras()!=null){
int closeCommand = intent.getExtras().getInt("swipeToClose", 0);
if(closeCommand==1){
Log.d("TTSMEDIAPLAYER", "Close service intent received.");
// Pre-lollipop media style close button. Unable to test
stopSelf();
return super.onStartCommand(intent, flags, startId);
}
//Request audio focus
if (false ) { // mAudioFocusHelper.requestAudioFocus() == false not needed because we request focus onPlay
Log.d("TTSMEDIAPLAYER", "Starting and requesting focus...");
//Could not gain focus
stopSelf();
}else {
Log.d("TTSMEDIAPLAYER", "Focued. Starting...");
boolean isPlaying = player.isPlaying();
String nodesAsJsonString = intent.getExtras().getString("nodesAsJsonString", "[]");
if (nodesAsJsonString != null && !nodesAsJsonString.equals("[]")) {
isPlaying = true;
// New TTS playback has been requested
pageTitle = intent.getExtras().getString("pageTitle", "");
pageAddress = intent.getExtras().getString("pageAddress", "");
pageDomain = UrlHelper.getDomain(pageAddress, true, true, true);
final Locale languageToSpeak = UrlHelper.getLanguageFromAddress(pageAddress);
try {
currentSpeechBank = new SpeechBank(nodesAsJsonString, languageToSpeak);
} catch (JSONException e) {
stopSelf();
return super.onStartCommand(intent, flags, startId);
}
mediaMetaData = new MediaMetadataCompat.Builder()
// TODO i guessed at these, I think this might be used on things like bluetooth speakers that have a display
.putString(MediaMetadata.METADATA_KEY_ARTIST, pageAddress)
.putString(MediaMetadata.METADATA_KEY_TITLE, pageTitle)
.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, pageTitle)
.build();
mediaSession.setMetadata(mediaMetaData);
}
Log.d("TTSMEDIAPLAYER", "isPlaying: " + isPlaying);
/*mediaSession.setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | // apparently no longer needed
//MediaSession.FLAG_HANDLES_QUEUE_COMMANDS | //
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
);*/
if(intent.getAction()!=null && intent.getAction().equals(START_SERVICE_ACTION_INTENT)) {
Log.d("TTSMEDIAPLAYER", "START_SERVICE_ACTION_INTENT detected, triggering transportcontrols.play");
transportControls.play();
}
}
}
return super.onStartCommand(intent, flags, startId); // START_NOT_STICKY;?
}
private void updateNotificationAndMediaButtons(boolean isPlaying) {
// ... notification stuff ...
startForeground(1, notificationBuilder.build());
}
@Override
public void onDestroy() {
mediaSession.release();
mAudioFocusHelper.abandonAudioFocus();
player.freeUpResources();
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private MediaSessionCompat.Callback callback = new MediaSessionCompat.Callback() {
@Override
public void onSkipToNext() {
Log.d("TTSMEDIAPLAYER", "SKIP TO NEXT");
super.onSkipToNext();
handleFastForward();
}
@Override
public void onPlay() {
Log.d("TTSMEDIAPLAYER", "onPLAY!");
if(mAudioFocusHelper.requestAudioFocus()) {
if(player.isPaused()){
Log.d("TTSMEDIAPLAYER", "(resuming)");
player.resume();
}else{
Log.d("TTSMEDIAPLAYER", "(not started? playNew)");
player.playNew(currentSpeechBank);
}
// TODO TTS textToSpeechPlayer.play();
PlaybackStateCompat state = new PlaybackStateCompat.Builder()
// Supported actions in current state
.setActions(
PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_STOP
| PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND
)
// Current state
.setState(PlaybackStateCompat.STATE_PLAYING, player.getCurrentPosition(), 1, SystemClock.elapsedRealtime())
.build();
mediaSession.setPlaybackState(state);
updateNotificationAndMediaButtons(true);
}
super.onPlay();
}
@Override
public void onPause() {
Log.d("TTSMEDIAPLAYER", "onPAUSE!");
player.pause();
PlaybackStateCompat state = new PlaybackStateCompat.Builder()
// Set supported actions in current state
.setActions(
PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_STOP |
PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND)
// Set current state
.setState(PlaybackStateCompat.STATE_PAUSED, player.getCurrentPosition(), 1, SystemClock.elapsedRealtime())
.build();
mediaSession.setPlaybackState(state);
updateNotificationAndMediaButtons(false);
super.onPause();
}
@Override
public void onSkipToPrevious() {
Log.d("TTSMEDIAPLAYER", "SKIP TRACK PREV!");
player.rewind();
super.onSkipToPrevious();
}
@Override
public void onFastForward() {
Log.d("TTSMEDIAPLAYER", "FAST FORWARD!");
super.onFastForward();
handleFastForward();
}
@Override
public void onRewind() {
Log.d("TTSMEDIAPLAYER", "REWIND!");
player.rewind();
PlaybackStateCompat state = new PlaybackStateCompat.Builder()
// Set supported actions in current state
.setActions(
PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_STOP |
PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND)
// Set current state
.setState(PlaybackStateCompat.STATE_PLAYING, player.getCurrentPosition(), 1, SystemClock.elapsedRealtime())
.build();
mediaSession.setPlaybackState(state);
updateNotificationAndMediaButtons(true);
super.onRewind();
}
@Override
public void onStop() {
Log.d("TTSMEDIAPLAYER", "STOP!");
player.stop();
PlaybackStateCompat state = new PlaybackStateCompat.Builder()
// Set supported actions in current state
// .setActions(null)
// Set current state
.setState(PlaybackStateCompat.STATE_STOPPED, player.getCurrentPosition(), 1, SystemClock.elapsedRealtime())
.build();
mediaSession.setPlaybackState(state);
mAudioFocusHelper.abandonAudioFocus();
stopSelf();
//super.onStop();
}
};
private void handleFastForward() {
boolean hasReachedEnd = player.fastForward();
if(hasReachedEnd){
transportControls.stop();
}else{
PlaybackStateCompat state = new PlaybackStateCompat.Builder()
// Set supported actions in current state
.setActions(
PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_STOP |
PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND)
// Set current state
.setState(PlaybackStateCompat.STATE_PLAYING, player.getCurrentPosition(), 1, SystemClock.elapsedRealtime())
.build();
mediaSession.setPlaybackState(state);
updateNotificationAndMediaButtons(true);
}
}
/**
* Helper class for managing audio focus related tasks.
*/
private final class AudioFocusHelper
implements AudioManager.OnAudioFocusChangeListener {
private boolean mPlayOnAudioFocus = false;
private boolean requestAudioFocus() {
Log.d("TTSMEDIAPLAYER", "requestAudioFocus()...");
playbackAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(playbackAttributes)
.setAcceptsDelayedFocusGain(false)
.setOnAudioFocusChangeListener(mAudioFocusHelper, handler)
.build();
int res = mAudioManager.requestAudioFocus(audioFocusRequest);
if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
Log.d("TTSMEDIAPLAYER", "audio focus failed...");
return false;
} else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d("TTSMEDIAPLAYER", "audio focus granted...");
return true;
} else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
Log.d("TTSMEDIAPLAYER", "audio focus DELAYED...");
// use case for this is imagine being in a phone call that has focus,
// then the user opens a game. The game
// should start playing audio once the call finishes.
return false; // todo?
}
}else{
final int result = mAudioManager.requestAudioFocus(this,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
Log.d("TTSMEDIAPLAYER", "audio focus returning default!?");
return false;
}
private void abandonAudioFocus() {
Log.d("TTSMEDIAPLAYER", "abandonAudioFocus()");
mAudioManager.abandonAudioFocus(this);
}
@Override
public void onAudioFocusChange(int focusChange) {
Log.d("TTSMEDIAPLAYER", "Audio focus changed...");
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
Log.d("TTSMEDIAPLAYER", "Audio focus gained!");
if (mPlayOnAudioFocus && player.isPaused()) {
player.resume();
//} else if (isPlaying()) {
// setVolume(MEDIA_VOLUME_DEFAULT);
}
mPlayOnAudioFocus = false;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
Log.d("TTSMEDIAPLAYER", "Something about ducks!?");
// this might be for dropping the sound while something else happens (text notifications)
//setVolume(MEDIA_VOLUME_DUCK);
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
Log.d("TTSMEDIAPLAYER", "AUDIOFOCUS_LOSS_TRANSIENT!");
if (player.isPlaying()) {
// I think this is for temporary loss of focus e.g. calls/notificaitons
mPlayOnAudioFocus = true;
player.pause();
}
break;
case AudioManager.AUDIOFOCUS_LOSS:
// Seems to be triggered when you press play in another media app (i.e. they requested focus)
Log.d("TTSMEDIAPLAYER", "AUDIOFOCUS_LOSS! abandoning focus, pausing speech");
mAudioManager.abandonAudioFocus(this);
if (player.isPlaying()) {
player.pause();
mPlayOnAudioFocus = false;
}
updateNotificationAndMediaButtons(false);
break;
default:
Log.d("TTSMEDIAPLAYER", "AUDIOFOCUS_???");
}
}
}
}
build.gradle
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.media:media:1.1.0'
...
我在这个问题上卡了一段时间,所以非常感谢任何帮助。
日志:
? I/MusicController: [MediaSessionMonitor.java:153:onActiveSessionsChanged()] oooooo
? I/MusicController: [MediaSessionMonitor.java:302:clear()] oooooo
? D/MusicController: [MediaSessionMonitor.java:99:clearMediaContorllersMap()] oooooo Controller is already empty
? W/MusicController: [MediaSessionMonitor.java:171:updateMediaControllers()] oooooo Controller is empty
? I/MediaFocusControl: AudioFocus requestAudioFocus() from uid/pid 10345/8858 clientId=android.media.AudioManagerEx@63b6ee9fishpowered.bar.services.TextToSpeechMediaControllerService$AudioFocusHelper@44f676e req=1 flags=0x0
? I/MusicController: [MediaSessionMonitor.java:153:onActiveSessionsChanged()] oooooo
? I/MusicController: [MediaSessionMonitor.java:174:updateMediaControllers()] oooooo List size :1
? I/MusicController: [MediaSessionMonitor.java:178:updateMediaControllers()] oooooo MediaController received packageName foo.bar
? D/MusicController: [MediaSessionMonitor.java:74:addToMediaContorllersMap()] oooooo Added = android.media.session.MediaSession$Token@986e7dd
? I/MusicController: [MediaSessionMonitor.java:78:addToMediaContorllersMap()] oooooo mMediaContorllersMap.size() = 1
? I/MusicController: [MediaSessionMonitor.java:222:checkAndUpdateMusicController()] oooooo is MusicController Updated = false
? I/MusicController: [MediaSessionMonitor.java:182:updateMediaControllers()] oooooo mMediaContorllersMap size = 1
? I/MediaFocusControl: AudioFocus requestAudioFocus() from uid/pid 10345/8858 clientId=android.media.AudioManagerEx@63b6ee9fishpowered.bar.services.TextToSpeechMediaControllerService$AudioFocusHelper@44f676e req=1 flags=0x0
? I/MusicController: [MediaControllerCallbackWrapper.java:55:onMetadataChanged()] oooooo This callback is from foo.bar
? I/MusicController: [MediaControllerCallbackWrapper.java:47:onPlaybackStateChanged()] oooooo This callback is from foo.bar
? D/MusicController: [MediaSessionMonitor.java:123:handleMessage()] oooooo MediaSessionMonitor.MsgHandler msg = 2
? D/MusicController: [MediaSessionMonitor.java:205:checkAndUpdateMusicController()] oooooo state = 3, action = 585
? I/MusicController: [MediaSessionMonitor.java:234:isValidStateToRegister()] oooooo isValidStateToRegister = true
? I/MusicController: [MediaSessionMonitor.java:257:isValidMetadataToRegister()] oooooo isValidMetadataToRegister = true
? I/MusicController: [MediaSessionMonitor.java:245:isValidActionToRegister()] oooooo isValidActionToRegister = false
? I/MusicController: [MediaSessionMonitor.java:222:checkAndUpdateMusicController()] oooooo is MusicController Updated = false
? D/MusicController: [MediaSessionMonitor.java:123:handleMessage()] oooooo MediaSessionMonitor.MsgHandler msg = 1
? D/MusicController: [MediaSessionMonitor.java:205:checkAndUpdateMusicController()] oooooo state = 3, action = 585
? I/MusicController: [MediaSessionMonitor.java:234:isValidStateToRegister()] oooooo isValidStateToRegister = true
? I/MusicController: [MediaSessionMonitor.java:257:isValidMetadataToRegister()] oooooo isValidMetadataToRegister = true
? I/MusicController: [MediaSessionMonitor.java:245:isValidActionToRegister()] oooooo isValidActionToRegister = false
? I/MusicController: [MediaSessionMonitor.java:222:checkAndUpdateMusicController()] oooooo is MusicController Updated = false
? I/MusicController: [MediaControllerCallbackWrapper.java:47:onPlaybackStateChanged()] oooooo This callback is from foo.bar
? D/MusicController: [MediaSessionMonitor.java:123:handleMessage()] oooooo MediaSessionMonitor.MsgHandler msg = 1
? D/MusicController: [MediaSessionMonitor.java:205:checkAndUpdateMusicController()] oooooo state = 3, action = 585
? I/MusicController: [MediaSessionMonitor.java:234:isValidStateToRegister()] oooooo isValidStateToRegister = true
? I/MusicController: [MediaSessionMonitor.java:257:isValidMetadataToRegister()] oooooo isValidMetadataToRegister = true
? I/MusicController: [MediaSessionMonitor.java:245:isValidActionToRegister()] oooooo isValidActionToRegister = false
? I/MusicController: [MediaSessionMonitor.java:222:checkAndUpdateMusicController()] oooooo is MusicController Updated = false
? V/MediaRouter: Adding route: RouteInfo{ name=Phone, description=null, status=null, category=RouteCategory{ name=System types=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO groupable=false }, supportedTypes=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO , presentationDisplay=null }
? V/MediaRouter: Adding route: RouteInfo{ name=DummyDevice, description=Bluetooth audio, status=null, category=RouteCategory{ name=System types=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO groupable=false }, supportedTypes=ROUTE_TYPE_LIVE_AUDIO , presentationDisplay=null }
? V/MediaRouter: Selecting route: RouteInfo{ name=DummyDevice, description=Bluetooth audio, status=null, category=RouteCategory{ name=System types=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO groupable=false }, supportedTypes=ROUTE_TYPE_LIVE_AUDIO , presentationDisplay=null }
? V/MediaRouter: Audio routes updated: AudioRoutesInfo{ type=SPEAKER, bluetoothName=DummyDevice }, a2dp=true
? W/MediaSessionCompat: Couldn't find a unique registered media button receiver in the given context.
? I/MusicController: [MediaSessionMonitor.java:153:onActiveSessionsChanged()] oooooo
? I/MusicController: [MediaSessionMonitor.java:174:updateMediaControllers()] oooooo List size :1
? I/MusicController: [MediaSessionMonitor.java:178:updateMediaControllers()] oooooo MediaController received packageName foo.bar
? D/MusicController: [MediaSessionMonitor.java:76:addToMediaContorllersMap()] oooooo Already exist = android.media.session.MediaSession$Token@986e7dd
? I/MusicController: [MediaSessionMonitor.java:78:addToMediaContorllersMap()] oooooo mMediaContorllersMap.size() = 1
? D/MusicController: [MediaSessionMonitor.java:205:checkAndUpdateMusicController()] oooooo state = 3, action = 585
? I/MusicController: [MediaSessionMonitor.java:234:isValidStateToRegister()] oooooo isValidStateToRegister = true
? I/MusicController: [MediaSessionMonitor.java:257:isValidMetadataToRegister()] oooooo isValidMetadataToRegister = true
? I/MusicController: [MediaSessionMonitor.java:245:isValidActionToRegister()] oooooo isValidActionToRegister = false
? I/MusicController: [MediaSessionMonitor.java:222:checkAndUpdateMusicController()] oooooo is MusicController Updated = false
? I/MusicController: [MediaSessionMonitor.java:182:updateMediaControllers()] oooooo mMediaContorllersMap size = 1
? V/MediaRouter: Selecting route: RouteInfo{ name=DummyDevice, description=Bluetooth audio, status=null, category=RouteCategory{ name=System types=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO groupable=false }, supportedTypes=ROUTE_TYPE_LIVE_AUDIO , presentationDisplay=null }
? V/MediaRouter: Adding route: RouteInfo{ name=Phone, description=null, status=null, category=RouteCategory{ name=System types=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO groupable=false }, supportedTypes=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO , presentationDisplay=null }
? V/MediaRouter: Adding route: RouteInfo{ name=DummyDevice, description=Bluetooth audio, status=null, category=RouteCategory{ name=System types=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO groupable=false }, supportedTypes=ROUTE_TYPE_LIVE_AUDIO , presentationDisplay=null }
? V/MediaRouter: Selecting route: RouteInfo{ name=DummyDevice, description=Bluetooth audio, status=null, category=RouteCategory{ name=System types=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO groupable=false }, supportedTypes=ROUTE_TYPE_LIVE_AUDIO , presentationDisplay=null }
? V/MediaRouter: Audio routes updated: AudioRoutesInfo{ type=SPEAKER, bluetoothName=DummyDevice }, a2dp=true
? V/MediaRouter: Selecting route: RouteInfo{ name=DummyDevice, description=Bluetooth audio, status=null, category=RouteCategory{ name=System types=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO groupable=false }, supportedTypes=ROUTE_TYPE_LIVE_AUDIO , presentationDisplay=null }
我还发现了一个音频权限错误,但我不确定是否与此相关:
W/MediaListnrAuthObsrvr: registration failed - not an approved notification listener yet?
java.lang.SecurityException: Missing permission to control media.
at android.os.Parcel.readException(Parcel.java:1951)
at android.os.Parcel.readException(Parcel.java:1897)
at android.media.session.ISessionManager$Stub$Proxy.addSessionsListener(ISessionManager.java:342)
at android.media.session.MediaSessionManager.addOnActiveSessionsChangedListener(MediaSessionManager.java:226)
at android.media.session.MediaSessionManager.addOnActiveSessionsChangedListener(MediaSessionManager.java:189)
at com.google.android.clockwork.common.media.DefaultMediaSessionManagerWrapper.addOnActiveSessionsChangedListener(AW771527612:8)
at com.google.android.clockwork.companion.mediacontrols.api21.MediaSessionListenerAuthorizationObserver.register(AW771527612:12)
at com.google.android.clockwork.companion.mediacontrols.api21.MediaSessionListenerAuthorizationObserver.<init>(AW771527612:5)
at com.google.android.clockwork.companion.mediacontrols.api21.MediaRemoteControllerApi21.start(AW771527612:13)