Search code examples
javaandroidandroid-mediasession

Lock Screen Player Controls and Meta Data


I'm trying to use MediaSessionCompat in order to add lock screen player controls and meta data for my app. Everything I tried doesn't work. The lock screen doesn't show any controls or meta data while playing. Please see my current code below and any help is appreciated.

StreamService.java:

public class StreamService extends Service implements MediaPlayer.OnCuePointReceivedListener, MediaPlayer.OnStateChangedListener,
        MediaPlayer.OnInfoListener, AudioManager.OnAudioFocusChangeListener {

    private WifiManager.WifiLock wifiLock;
    private static String LOG_TAG = "StreamService";
    public static final String BROADCAST_PLAYER_STATE = "com.test.BROADCAST_PLAYER_STATE";
    public static final String BROADCAST_PLAYER_META = "com.test.BROADCAST_PLAYER_META";
    public static final String BROADCAST_PLAYER_ALBUM = "com.test.BROADCAST_PLAYER_ALBUM";
    public static final int NOTIFICATION_ID = 999999;
    private MediaSessionCompat mediaSession;
    private boolean audioInterrupted = false;

    public StreamService() {
    }

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

        setupMediaPlayer();

        setupMediaSession();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public boolean onUnbind(Intent intent){
        releasePlayer();
        return false;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_NOT_STICKY;
    }

    private void setupMediaPlayer() {
        // Recreate player
        Bundle playerSettings = (BrandedApplication.getContext().getmTritonPlayer() == null) ? null : BrandedApplication.getContext().getmTritonPlayer().getSettings();
        Bundle inputSettings = createPlayerSettings();

        if (!Utility.bundleEquals(inputSettings, playerSettings)) {
            releasePlayer();
            createPlayer(inputSettings);
        }

        // Start the playback
        play();
    }

    private void setupMediaSession() {
        ComponentName receiver = new ComponentName(getPackageName(), RemoteReceiver.class.getName());
        mediaSession = new MediaSessionCompat(this, "StreamService", receiver, null);
        mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
                MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
        mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
                .setState(PlaybackStateCompat.STATE_PAUSED, 0, 0)
                .setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE)
                .build());
        mediaSession.setMetadata(new MediaMetadataCompat.Builder()
                .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "Test Artist")
                .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, "Test Album")
                .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Test Track Name")
                .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, 10000)
                .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART,
                        BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                //.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Test Artist")
                .build());

        AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        audioManager.requestAudioFocus(new AudioManager.OnAudioFocusChangeListener() {
            @Override
            public void onAudioFocusChange(int focusChange) {
                // Ignore
            }
        }, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);

        mediaSession.setActive(true);
    }

    synchronized private void play() {
        audioInterrupted = false;
        BrandedApplication.getContext().getmTritonPlayer().play();
        if(wifiLock != null) {
            wifiLock.acquire();
        }

        if(mediaSession != null) {
            mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
                    .setState(PlaybackStateCompat.STATE_PLAYING, 0, 1.0f)
                    .setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE).build());
        }
    }

    synchronized private void stop() {
        BrandedApplication.getContext().getmTritonPlayer().stop();
        if(wifiLock != null) {
            wifiLock.release();
        }

        if(mediaSession != null) {
            mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
                    .setState(PlaybackStateCompat.STATE_PAUSED, 0, 0.0f)
                    .setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE).build());
        }
    }

    private void createPlayer(Bundle settings)
    {
        BrandedApplication.getContext().setmTritonPlayer(new TritonPlayer(this, settings));
        wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
                .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
        AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
                AudioManager.AUDIOFOCUS_GAIN);
        BrandedApplication.getContext().getmTritonPlayer().setOnCuePointReceivedListener(this);
        BrandedApplication.getContext().getmTritonPlayer().setOnInfoListener(this);
        BrandedApplication.getContext().getmTritonPlayer().setOnStateChangedListener(this);
    }


    protected void releasePlayer() {
        if (BrandedApplication.getContext().getmTritonPlayer() != null) {
            if(BrandedApplication.getContext().isPlaying()) {
                stop();
            }
            BrandedApplication.getContext().getmTritonPlayer().release();
            BrandedApplication.getContext().setmTritonPlayer(null);
        }
        stopForeground(true);
    }

    protected Bundle createPlayerSettings() {
        // Player Settings
        Bundle settings = new Bundle();
        // AAC
        settings.putString(TritonPlayer.SETTINGS_STATION_MOUNT, getResources().getString(R.string.station_stream_mount) + "AAC");
        // MP3
        //settings.putString(TritonPlayer.SETTINGS_STATION_MOUNT, mountID);
        settings.putString(TritonPlayer.SETTINGS_STATION_BROADCASTER, getResources().getString(R.string.app_name));
        settings.putString(TritonPlayer.SETTINGS_STATION_NAME, getResources().getString(R.string.app_name));
        return settings;
    }

    @Override
    public void onCuePointReceived(MediaPlayer mediaPlayer, Bundle bundle) {
        //System.out.println("TRITON PLAYER BUNDLE " + bundle);
        String trackName = "";
        String artistName = "";
        if(bundle != null) {
            if(bundle.containsKey("cue_title") && bundle.containsKey("track_artist_name")) {
                if (!bundle.getString("cue_title").isEmpty()) {
                    trackName = bundle.getString("cue_title");
                }
                if (!bundle.getString("track_artist_name").isEmpty()) {
                    artistName = bundle.getString("track_artist_name");
                }
            }
        }
        // broadcast out the meta data
        Intent i = new Intent(BROADCAST_PLAYER_META);
        i.putExtra("trackName", trackName);
        i.putExtra("artistName", artistName);
        sendBroadcast(i);

        // send notification and start as foreground service
        PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0, new Intent(getApplicationContext(), MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
        Bitmap icon = BitmapFactory.decodeResource(getResources(),
                R.drawable.logo);
        String tickerString = "";
        String contentString = "Playing";
        if(!artistName.isEmpty() && !trackName.isEmpty()) {
            tickerString = artistName + " - " + trackName;
            contentString += ": " + artistName + " - " + trackName;
        }

        Intent pauseIntent = new Intent(BROADCAST_PLAYER_PAUSE);
    PendingIntent pausePendingIntent = PendingIntent.getBroadcast(this, 0, pauseIntent, 0);

    NotificationCompat.Builder notification = new NotificationCompat.Builder(this)
            .setContentTitle(getResources().getString(R.string.app_name))
            .setTicker(tickerString)
            .setContentText(contentString)
            .setSmallIcon(R.drawable.ic_launcher)
            //.setAutoCancel(true)
            //.setLargeIcon(
            //        Bitmap.createScaledBitmap(icon, 128, 128, false))
            .addAction(R.drawable.ic_media_pause, "Pause", pausePendingIntent)
            .setContentIntent(pi)
            .setStyle(new android.support.v7.app.NotificationCompat.MediaStyle()
                    //.setShowActionsInCompactView(0)
                    .setMediaSession(mediaSession.getSessionToken()))
            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            .setOngoing(true);
    //notification.setPriority(Notification.PRIORITY_MIN);
    notification.setPriority(Notification.PRIORITY_DEFAULT);
    startForeground(NOTIFICATION_ID, notification.build());
    }

    @Override
    public void onInfo(MediaPlayer mediaPlayer, int i, int i1) {

    }

    @Override
    public void onStateChanged(MediaPlayer mediaPlayer, int state) {
        Log.i(LOG_TAG, "onStateChanged: " + TritonPlayer.debugStateToStr(state));
        // broadcast out the player state
        Intent i = new Intent(BROADCAST_PLAYER_STATE);
        i.putExtra("state", state);
        sendBroadcast(i);
    }

    @Override
    public void onAudioFocusChange(int focusChange) {
        switch (focusChange) {
            case AudioManager.AUDIOFOCUS_GAIN:
                // resume playback
                System.out.println("AUDIO FOCUS GAIN");
                if(audioInterrupted) {
                    audioInterrupted = false;
                    if (BrandedApplication.getContext().getmTritonPlayer() == null) {
                        setupMediaPlayer();
                    } else if (!BrandedApplication.getContext().isPlaying()) {
                        setupMediaPlayer();
                    }
                }
                break;

            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
            case AudioManager.AUDIOFOCUS_LOSS:
                System.out.println("AUDIO FOCUS LOSS");
                // Lost focus for an unbounded amount of time: stop playback and release media player
                if (BrandedApplication.getContext().isPlaying()) {
                    audioInterrupted = true;
                    releasePlayer();
                }
                break;
        }
    }

    @Override
    public void onDestroy() {
        System.out.println("SERVICE STOPPED");
        releasePlayer();
        mediaSession.release();
    }
}

And here's RemoteReceiver.java:

public class RemoteReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
            final KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);

            if (event != null && event.getAction() == KeyEvent.ACTION_DOWN) {
                switch (event.getKeyCode()) {
                    case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
                        context.startService(new Intent(context, StreamService.class));
                        break;
                }
            }
        }
    }
}

Solution

  • Okay, from the additional information you provided, I believe I know what the issue is. In Android 5.0 Lock Screen Controls were removed. They are now implemented via the Notification API. So try adding the following to your notification builder.

    notification.setStyle(new NotificationCompat.MediaStyle()
                    .setShowActionsInCompactView(0)
                    .setMediaSession(mediaSession));
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
    

    That should place it on your lock screen. I would also suggest changing the Notification.PRIORITY_DEFAULT as well as include an action to your notification otherwise you won't be able to control the playback.