Android MediaSession架构中Media Player与SeekBar的通信

Android communication between MediaPlayer and SeekBar in MediaSession Architecuture

我正在开发音频流应用程序。我按照 android 描述 here. In my app, I have one activity, MainActivity, which loads fragments according to selected functions. In one of these fragments, I provide a ReplayPlayer where I would like to let users seek through the streamed audio, play/pause the stream, etc. I found this 的方式设计了我的应用程序,并设计了我的应用程序,使我的 StreamService 控制 MediaPlayer,因此 MediaPlayer 为我的应用位于 StreamService.

问题是我试图将 ReplayPlayer 中的 SeekBarStreamServiceMediaPlayer 中的媒体相关联。据我了解,MediaBrowserService 不能绑定,不像 Service,所以我不能在 StreamService 中访问我的 MediaPlayer 的 currentPosition。所以,我对如何从我的 ReplayPlayer 片段访问这个 MediaPlayercurrentPosition 感到困惑。

由于其他音乐播放器应用程序清楚地显示了歌曲中的当前位置,我感觉到有一种方法可以实现我目前正在努力解决的问题。我该怎么做?

在此先感谢您的帮助。

AndroidManifest.xml

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/TransparentTheme"
    android:usesCleartextTraffic="true">
    <activity android:name=".MainActivity" android:windowSoftInputMode="stateVisible|adjustResize"/>

    <service android:name=".StreamService">
        <intent-filter>
            <action android:name="android.media.browse.MediaBrowserService" />
            <action android:name="android.intent.action.MEDIA_BUTTON" />
            <action android:name="android.media.browse.AUDIO_BECOMING_NOISY" />
        </intent-filter>
    </service>

    <receiver android:name="androidx.media.session.MediaButtonReceiver">
        <intent-filter>
            <action android:name="android.intent.action.MEDIA_BUTTON" />
            <action android:name="android.media.AUDIO_BECOMING_NOISY" />
        </intent-filter>
    </receiver>

    <activity android:name=".SplashActivity" android:noHistory="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

StreamService.java

public class StreamService extends MediaBrowserServiceCompat implements MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, AudioManager.OnAudioFocusChangeListener{

    private static final String TAG = "StreamService";

    private MediaPlayer mediaPlayer;
    private MediaSessionCompat mediaSessionCompat;
    private BroadcastReceiver mNoisyReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if(mediaPlayer != null && mediaPlayer.isPlaying()) mediaPlayer.pause();
        }
    };

    private WifiManager.WifiLock wifiLock;

    @Override
    public void onCreate() {
        super.onCreate();

        wifiLock =((WifiManager)getApplicationContext().getSystemService(Context.WIFI_SERVICE)).createWifiLock(WifiManager.WIFI_MODE_FULL, "myLock");

        initMediaSession();
        initNoisyReceiver();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        audioManager.abandonAudioFocus(this);
        unregisterReceiver(mNoisyReceiver);
        mediaSessionCompat.release();
        if(mediaPlayer!=null) mediaPlayer.release();
        stopSelf();
    }

    private void initMediaSession(){
        ComponentName mediaButtonReceiver = new ComponentName(getApplicationContext(), MediaButtonReceiver.class);
        mediaSessionCompat = new MediaSessionCompat(getApplicationContext(), "Tag", mediaButtonReceiver, null);

        mediaSessionCompat.setCallback(mediaSessionCallbacks);
        mediaSessionCompat.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS );

        Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
        mediaButtonIntent.setClass(this, MediaButtonReceiver.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0);
        mediaSessionCompat.setMediaButtonReceiver(pendingIntent);

        setSessionToken(mediaSessionCompat.getSessionToken());
    }

    private void initNoisyReceiver(){
        //Handles headphones coming unplugged. cannot be done through a manifest receiver
        IntentFilter filter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
        registerReceiver(mNoisyReceiver, filter);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        MediaButtonReceiver.handleIntent(mediaSessionCompat, intent);
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
        if(TextUtils.equals(clientPackageName, getPackageName())) {
            return new BrowserRoot(getString(R.string.app_name), null);
        }

        return null;
    }

    @Override
    public void onLoadChildren(@NonNull String parentId, @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
        result.sendResult(null);
    }

    private MediaSessionCompat.Callback mediaSessionCallbacks = new MediaSessionCompat.Callback() {
        @Override
        public void onPlay() {
            super.onPlay();
            if( !successfullyRetrievedAudioFocus() ) {
                return;
            }
            mediaPlayer.start();
            setMediaPlaybackState(PlaybackStateCompat.STATE_PLAYING);
        }

        @Override
        public void onPause() {
            super.onPause();

            if( mediaPlayer.isPlaying() ) {
                mediaPlayer.pause();
                setMediaPlaybackState(PlaybackStateCompat.STATE_PAUSED);
            }
        }

        @Override
        public void onPlayFromUri(Uri uri, Bundle extras) {
            if(mediaPlayer != null) mediaPlayer.release();
            super.onPlayFromUri(uri, extras);

            try {
                mediaPlayer = new MediaPlayer();
                mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
                mediaPlayer.setDataSource(uri.toString());
                mediaPlayer.setVolume(1.0f, 1.0f);

                if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
                    mediaPlayer.setAudioAttributes(new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build());
                else // setAudioStreamType deprecated past Oreo
                    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
                mediaPlayer.setOnPreparedListener(StreamService.this);
                setMediaSessionMetadata(extras);

                mediaPlayer.prepareAsync();
                wifiLock.acquire();
            } catch(IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onStop() {
            super.onStop();
            Log.d(TAG, "onStop called");
            if(mediaPlayer.isPlaying()) {
                mediaPlayer.release();
                setMediaPlaybackState(PlaybackStateCompat.STATE_STOPPED);
            }
        }
    };

    @Override
    public void onPrepared(MediaPlayer mp) {
        Log.d(TAG, "mediaPlayer prepared");
        mediaSessionCompat.setActive(true);
        mediaPlayer.start();
        setMediaPlaybackState(PlaybackStateCompat.STATE_PLAYING);
        successfullyRetrievedAudioFocus();
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        mp = null;
        wifiLock.release();
        return false;
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        if(mp != null) mp.release();
    }

    @Override
    public void onAudioFocusChange(int focusChange) {
        switch( focusChange ) {
            case AudioManager.AUDIOFOCUS_LOSS: {
                Log.d(TAG, "audio focus loss");
                if( mediaPlayer.isPlaying() ) {
                    mediaPlayer.stop();
                }
                break;
            }
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: {
                Log.d(TAG, "audio focus loss transient");
                mediaPlayer.pause();
                break;
            }
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
                Log.d(TAG, "audio focus loss transient can duck");
                if( mediaPlayer != null ) {
                    mediaPlayer.setVolume(0.3f, 0.3f);
                }
                break;
            }
            case AudioManager.AUDIOFOCUS_GAIN: {
                Log.d(TAG, "audio focus gain");
                if( mediaPlayer != null ) {
                    if( !mediaPlayer.isPlaying() ) {
                        mediaPlayer.start();
                    }
                    mediaPlayer.setVolume(1.0f, 1.0f);
                }
                break;
            }
        }
    }

    private boolean successfullyRetrievedAudioFocus() {
        AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        AudioAttributes attr = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TY    PE_MUSIC).build();
        int result = -1;
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            result = audioManager.requestAudioFocus(new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).setAudioAttributes(attr).setAcceptsDelayedFocusGain(true).setOnAudioFocusChangeListener(this).build());
            synchronized(this) {
                return result==AudioManager.AUDIOFOCUS_GAIN;
            }
        } else {
            result = audioManager.requestAudioFocus(this, AudioAttributes.CONTENT_TYPE_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
            return result == AudioManager.AUDIOFOCUS_GAIN;
        }
    }

    private void setMediaPlaybackState(int state) {
        PlaybackStateCompat.Builder playbackstateBuilder = new PlaybackStateCompat.Builder();
        if( state == PlaybackStateCompat.STATE_PLAYING ) {
            playbackstateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_PAUSE);
        } else {
            playbackstateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_PLAY);
        }
        playbackstateBuilder.setState(state, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 0);
        mediaSessionCompat.setPlaybackState(playbackstateBuilder.build());
    }

    private void setMediaSessionMetadata(Bundle extras) {
        MediaMetadataCompat.Builder metadataBuilder = new MediaMetadataCompat.Builder();

        if(extras.getParcelable("Track")!=null) {
            Track track = extras.getParcelable("Track");
            Log.d(TAG, String.valueOf(track.getID()));
            metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, String.valueOf(track.getID()));
            metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ART, track.getArtworkURL());
            metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, track.getTitle());
            metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI, track.getStreamURL());
            metadataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, track.getDuration());
        }

        mediaSessionCompat.setMetadata(metadataBuilder.build());
    }
}

您可以 post 在服务端的 MediaSessionCompat 中更新您的 MediaPlayer 的位置,然后在您的片段中您将在 MediaControllerCompat.Callback 的 onPlaybackStateChanged(PlaybackStateCompat state) 方法中收到播放状态更新。 使用处理程序 post MediaPlayer 定期更新。像这样:

public void onPlay() {
    if (!mMediaPlayer.isPlaying()) {
        mMediaPlayer.start();
        updateCurrentPosition();
    }
}

public void onPause() {
    super.onPause();
    if (mMediaPlayer.isPlaying()) {
        mMediaPlayer.pause();
        PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder()
                .setActions(PAUSE_ACTIONS)
                .setState(PlaybackStateCompat.STATE_PAUSED, mMediaPlayer.getCurrentPosition()
                        , 0)
                .build();
        stopPlaybackStateUpdate();
    }
}


private void updateCurrentPosition() {
    if (mMediaPlayer == null) {
        return;
    }
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            int currentPosition = mMediaPlayer.getCurrentPosition();
            PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder()
                    .setActions(PLAYING_ACTIONS)
                    .setState(PlaybackStateCompat.STATE_PLAYING, currentPosition, 1)
                    .build();
            mMediaSession.setPlaybackState(playbackState);
            updateCurrentPosition();
        }
    }, 1000);
}

private void stopPlaybackStateUpdate() {
    if (handler != null) {
        handler.removeCallbacksAndMessages(null);
    }
}

您可以使用 MediaSessionCompat.setMetadata(元数据)设置媒体文件的总持续时间。

MediaMetadataCompat mediaMetadata = new MediaMetadataCompat.Builder()
            .putString(MediaMetadata.METADATA_KEY_TITLE, "XYZ"))
            .putLong(MediaMetadata.METADATA_KEY_DURATION, mMediaPlayer.getDuration())
            .build();

在您的 Fragment/Activity 中,您可以使用 MediaController.Callback 中收到的位置更新来设置 MediaPlayer 当前位置的进度(即 Seekbar.setProgress())。同时使用当前播放媒体的总持续时间设置 Seekbar.setMax。

    private MediaControllerCompat.Callback mMediaControllerCallback =

        new MediaControllerCompat.Callback() {
            @Override
            public void onMetadataChanged(MediaMetadataCompat metadata) {
                super.onMetadataChanged(metadata);
                int totalDuration = (int) metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
                mSeekBar.setMax(totalDuration);
            }

            @Override
            public void onPlaybackStateChanged(PlaybackStateCompat state) {
                mSeekBar.setMax(state.getPosition()); //You will receive MediaPlayer's current position every 1 second here.
            }

        };