A MediaPlayer Service

The Activity in this app creates a Service which plays music with a MediaPlayer. The Service and its music keep playing even after the Activity has been stopped or destroyed (e.g., by a change of orientation). If the Activity is reborn, it’s smart enough to re-start the Service only if the Service is not already running.

By default, the Service is executed by the same thread as the Activity, and we have done nothing to change this default. For a simple example of a Service executed by a different thread, see IntentService and Extending the IntentService class.

Source code in MediaPlayerService.zip

  1. MainActivity.java uses an Intent to start a Service if the Service is not already running. We saw an Intent here.
  2. MediaPlayerService.java
  3. activity_main.xml
  4. strings.xml
  5. musette.mid. Bach Musette in D major, BWV Anh. 126.
  6. AndroidManifest.xml. When we created class MediaPlayerService, Android Studio added a <service> element to the <application> element.
  7. build.gradle (Module: app)

Create the project

Place musette.mid in app/res/raw, as we did here.

In the Android Studio project view, select the folder edu.nyu.scps.mediaplayerservice.
File → New → Service → Service
Class Name: MediaPlayerService
Finish

Documentation

  1. Playing audio with a Service.
  2. Services in general, vs. Activities.
  3. Bound Services.
  4. Classes Service and IntentService.

Start and stop a Service in ApiDemos

App → Service → Local Service Controller

  1. platform_development/samples/ApiDemos/src/com/example/android/apis/app/LocalServiceActivities.java defines class LocalServiceActivities.Controller (lines 33–78).

  2. platform_development/samples/ApiDemos/src/com/example/android/apis/app/LocalService.java defines class LocalService.

  3. platform_development/samples/ApiDemos/res/layout/local_service_controller.xml

  4. platform_development/samples/ApiDemos/res/values/strings.xml
    1. local_service_started (line 244) is displayed in the status bar by the showNotification method of LocalService.
    2. local_service_stopped (line 245) is displayed in a Toast by the onDestroy method of LocalService.
    3. activity_local_service_controller (line 248)
    4. start_service (line 251) on the start button
    5. stop_service (line 252) on the stop button
    6. local_service_binding (line 255)

  5. stat_sample.png is displayed by the showNotification method of LocalService. It’s invisible against the black status bar, but it shoves the other icons to the right.
    1. platform_development/samples/ApiDemos/res/drawable-mdpi/stat_sample.png
    2. platform_development/samples/ApiDemos/res/drawable-hdpi/stat_sample.png

  6. platform_development/samples/ApiDemos/AndroidManifest.xml declares class LocalServiceActivities.Controller in lines 523–530.

Things to try

  1. Get a list of all the services running. In the onCreate method of the MainActivity,
            ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
            List<ActivityManager.RunningServiceInfo> runningServices = activityManager.(Integer.MAX_VALUE);
    
            for (ActivityManager.RunningServiceInfo runningServiceInfo: runningServices) {
                Log.d("myTag", runningServiceInfo.service.getClassName());
            }
    
    com.google.android.backup.BackupTransportService
    com.android.systemui.ImageWallpaper
    com.google.android.location.geocode.GeocodeService
    com.google.android.gms.gcm.GcmService
    com.android.inputmethod.latin.LatinIME
    com.android.keyguard.KeyguardService
    com.google.android.location.fused.service.FusedProviderService
    com.google.android.gms.config.ConfigFetchService
    com.google.android.location.fused.FusedLocationService
    com.google.android.location.geofencer.service.GeofenceProviderService
    com.google.android.libraries.hangouts.video.CallService
    com.google.android.gms.icing.service.LightweightIndexService
    com.android.systemui.SystemUIService
    android.hardware.location.GeofenceHardwareService
    com.google.android.gms.icing.service.IndexWorkerService
    com.google.android.location.internal.PendingIntentCallbackService
    com.google.android.gms.icing.service.LightweightIndexService$LightweightWorkerService
    com.google.android.location.internal.server.GoogleLocationService
    com.google.android.location.network.NetworkLocationService
    com.google.android.location.internal.GoogleLocationManagerService
    com.google.android.gms.deviceconnection.service.DeviceConnectionServiceBroker
    com.android.smspush.WapPushManager
    com.google.android.gms.security.snet.SafetyNetClientService
    com.android.internal.backup.LocalTransportService
    com.google.android.gms.playlog.service.PlayLogBrokerService
    com.google.android.gms.common.stats.GmsCoreStatsService
    com.google.android.gms.clearcut.service.ClearcutLoggerService
    com.google.android.gms.config.ConfigService
    com.android.phone.TelephonyDebugService
    com.google.android.gms.icing.service.IndexService
    

  2. Get a list of all the services running from adb. Type the following commands while the MediaPlayerService is running. Minus lowercase L for list, minus lowercase H for help. On Genymotion Samsung Galaxy S5:
    adb -e shell dumpsys -l
    account
    activity
    alarm
    etc.
    
    adb -e shell dumpsys activity -h
    adb -e shell dumpsys activity services
    adb -e shell dumpsys activity services MediaPlayerService
    
    ACTIVITY MANAGER SERVICES (dumpsys activity services)
      User 0 active services:
      * ServiceRecord{52d80768 u0 edu.nyu.scps.mediaplayerservice/.MediaPlayerService}
        intent={cmp=edu.nyu.scps.mediaplayerservice/.MediaPlayerService}
        packageName=edu.nyu.scps.mediaplayerservice
        processName=edu.nyu.scps.mediaplayerservice
        baseDir=/data/app/edu.nyu.scps.mediaplayerservice-1.apk
        dataDir=/data/data/edu.nyu.scps.mediaplayerservice
        app=ProcessRecord{52aabd30 4717:edu.nyu.scps.mediaplayerservice/u0a98}
        createTime=-2s534ms startingBgTimeout=--
        lastActivity=-2s534ms restartTime=-2s534ms createdFromFg=true
        startRequested=true delayedStop=false stopIfKilled=false callStart=true lastStartId=1
    

  3. An app should play sound only when the AudioManager gives it permission to do so. Compliance is voluntary, but let’s be good citizens of the global community. See Handling audio focus and Managing Audio Focus.

    Class MediaPlayerService should implement the interface AudioManager.OnAudioFocusChangeListener. Add the following method to class MediaPlayerService.

        @Override
        public void onAudioFocusChange(int focusChange) {
            switch (focusChange) {
                case AudioManager.AUDIOFOCUS_GAIN:
                    //Resume playback.
                    //Called only when focus is regained, not the first time focus is acquired.
                    if (mediaPlayer == null) {
                        mediaPlayer = MediaPlayer.create(this, R.raw.musette);
                    }
                    mediaPlayer.setVolume(1.0f, 1.0f);
                    if (!mediaPlayer.isPlaying()) {
                        mediaPlayer.start();
                    }
                    break;
    
                case AudioManager.AUDIOFOCUS_LOSS:
                    //Lost focus for a long time.
                    if (mediaPlayer != null) {
                        if (mediaPlayer.isPlaying()) {
                            mediaPlayer.stop();
                        }
                        mediaPlayer.release();
                        mediaPlayer = null;
                    }
                    break;
    
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                    //Lost focus for short time, but playback is likely to resume.
                    if (mediaPlayer != null && mediaPlayer.isPlaying()) {
                        mediaPlayer.pause();
                    }
                    break;
    
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                    //Lost focus for short time, but can keep playing quietly.
                    if (mediaPlayer != null && mediaPlayer.isPlaying()) {
                        mediaPlayer.setVolume(0.1f, 0.1f);
                    }
                    break;
    
                default:
                    break;
            }
        }
    
    In the Service’s onCreate, after creating the MediaPlayer:

            mediaPlayer.setVolume(1.0f, 1.0f);
    

    In onStartCommand, replace the mediaPlayer.start() with the following.

            AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
            int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
            if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
                mediaPlayer.start();
            } else {
                Toast toast = Toast.makeText(MediaPlayerService.this, "focus not granted", Toast.LENGTH_LONG);
                toast.show();
            }
    

  4. What would happen if an Activity called startService to start a MediaPlayerService that was already playing music? Would two copies of the music be playing at the same time?

  5. What happens when you’re listening to music through the headphones and someone suddenly pulls them out? Read about Audio Becoming Noisy.