捕获 Android 媒体按钮事件

2023-11-24

我有一个应用程序启动前台服务来播放一些媒体,我希望能够使用智能手表/耳机上的媒体按钮来控制它,并通过媒体样式通知等来控制它。

但我无法让媒体按钮始终如一地工作。在日志中,我可以看到它们经常被发送到其他应用程序,即使我最后启动了 MediaSession 和播放。

但尽管有一个媒体会话,我在其中 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_???");
            }
        }
    }
}

构建.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'
...

我已经坚持这个问题有一段时间了,所以任何帮助将不胜感激。

Logs:

? 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)

我最终发现为什么这对我不起作用。 Android Oreo 似乎存在一个错误,即尽管请求焦点并使用 TTS 引擎播放音频,但仍无法正确获得媒体焦点。

解决方法是在开始 TTS 之前从系统媒体播放器播放无声 wav 文件

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

捕获 Android 媒体按钮事件 的相关文章

  • Gradle 错误:在操作系统独立路径“META-INF/androidx.localbroadcastmanager_localbroadcastmanager.version”中找到多个文件

    我需要android 图像裁剪器我的 Android 应用程序的库 所以我将其添加到 gradle 依赖项中 同步后 出现了一些错误 这是一个 gradle 无法修复 发现多个文件具有与操作系统无关的路径 META INF androidx
  • Android 卷页动画

    我对 Android 动画有点陌生 目前我正在开发一个故事活动 需要像 iPhone 中那样使用卷页动画 我发现 iPhone 中有一种方法可以做到这一点 但我仍然找不到在android中做的方法 所以请帮我解决这个问题 谢谢大家 谷歌代码
  • PopupWindow onitemclick 中的 Android Listview 在某些设备上不起作用

    我的 ListView 在 PopupWindow 内 当我显示 PopupWindow 并单击设备 ASUS K00z 中的 Listview 行时 fonepad 工作得很好 但在 HTC Z715e 中不起作用 项目单击事件未触发 1
  • 有没有一种方法可以在不使用意图的情况下在活动之间发送数据?

    我有一个对于每个用户来说都是唯一的用户名 我想将其发送到某个活动 但我不想使用意图 create an intent and sends username Intent intent new Intent RegisterOwner thi
  • Twowayview 滚动时自动添加内边距

    我在用双向视图 https github com lucasr twoway view在我的一个项目中 这是android的扩展回收者视图 https developer android com reference android supp
  • Android 片段之间的阴影分隔符

    我有一个类似于平板电脑的 ICS Gmail 应用程序的布局 ListFragment左边是内容 右边是内容 我想知道如何构建布局 使两个片段之间有一个阴影分隔符 就像在 Gmail 应用程序中一样 如下所示 另外 由于这适用于这个问题 我
  • 如何在Android中访问现有的sqlite数据库?

    到目前为止 我们已经在 Android 中开发了在运行时创建数据库的应用程序 我们想知道如何在 Android 应用程序中访问预构建或现有的数据库 sqlite 文件 请提供详细信息 查看文档android database sqlite
  • 尝试获取屏幕上绘制的每个随机圆圈的 x、y 坐标

    您好 我正在制作一款游戏 该游戏将在屏幕上创建随机圆圈 随机创建的圆圈的值为红色或绿色 我的问题是 我希望不仅能够确定用户何时单击其中一个圆圈 而且还能够确定他们最终单击的圆圈 红色或绿色 下面是我的代码 我的主要问题是试图找到将要绘制的圆
  • 作为附件的 Android 设备

    我有 2 个 Android 设备 我想用 USB OTG 电缆连接它们 并在两个设备之间进行来回通信 据我了解 一台 Android 设备将充当 USB 主机 运行 4 4 的 Nexus 7 另一台 Android 设备将充当 USB
  • Android ViewModel LiveData 在按钮单击时更新视图

    我正在关注这个tutorial https developer android com topic libraries architecture guide html common problems faced by app develop
  • Android WebView文件上传

    我正在开发一个 Android 应用程序 基本上它是一个WebView和一个进度条 Facebook 的移动网站 m facebook com 已加载到WebView 当我单击 选择文件 按钮上传图像时 没有任何反应 我已经尝试了所有的解决
  • 合并 dex 程序类型已存在时出错:android.support.v4.os.ResultReceiver$MyResultReceiver

    合并dex时出错 以下是依赖项 ext anko version 0 10 5 support lib 1 0 0 alpha1 room lib 1 1 0 dependencies implementation org jetbrain
  • 垂直从上到下线手势检测器

    我用的是 手势工具 注意到对于垂直从上到下的线无法检测 因为我在代码中使用生成的手势文件 如下所示 但无法检测垂直从上到下的线手势检测 import java util ArrayList import android app Activi
  • phonegap html5 android 同步文件系统 IO

    如何使用 PhoneGaps 文件系统 API 同步读写文件 有可用的同步包装器吗 无法通过提供的 api 同步访问文件 从phonegap的实现方式猜测 我怀疑您是否可以编写一个插件来同步执行此操作
  • 如何使用 MotionLayout 调整 TextView 的大小

    我正在尝试创建一个CollapsingToolbar动画使用MotionLayout 我已经成功地将所有内容设置为动画 使其表现得像CollapsingToolbar具有高度的灵活性 这意味着我可以轻松创建很棒的动画 而无需编写大量代码 我
  • 可用屏幕的尺寸

    我使用的是 Nexus 7 1280x800 android 4 2 2 API 17 我想获取屏幕的大小 将其划分为相同高度和宽度的正方形部分 我正在使用 FrameLayout 我的方块是 ImageView 的子类 我这样做 cont
  • 在循环中按名称访问变量

    我正在开发一个 Android 项目 并且有很多可绘制对象 这些绘图的名称都类似于icon 0 png icon 1 png icon 100 png 我想将这些可绘制对象的所有资源 ID 添加到整数 ArrayList 中 对于那些不了解
  • Android Studio 中自动打开“运行设备”选项卡

    在 Android Studio Flamingo 中有一个名为跑步设备并且它会在每次之后自动打开运行应用程序 有办法禁止这个自动打开吗 我尝试禁用启用物理 Android 设备的镜像选项 但选项卡仍然自动打开 基于此issue https
  • Android - 如何简单地拖放按钮?

    我在这里找到了一个适合初学者的教程 http androidrox wordpress com 2011 05 13 android sample app drag and drop image using touch http andro
  • 如何减少 Android 中浮动 editText 提示和 editText 框之间的空间?

    我有一个带有浮动提示的 EditText 但我想知道如何减少浮动提示和 EditText 框之间的空间 现在我的用户界面看起来像https i stack imgur com ltfra jpg https i stack imgur co

随机推荐