├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── libraries │ ├── animated_vector_drawable_24_2_1.xml │ ├── appcompat_v7_24_2_1.xml │ ├── hamcrest_core_1_3.xml │ ├── junit_4_12.xml │ ├── support_annotations_24_2_1.xml │ ├── support_compat_24_2_1.xml │ ├── support_core_ui_24_2_1.xml │ ├── support_core_utils_24_2_1.xml │ ├── support_fragment_24_2_1.xml │ ├── support_media_compat_24_2_1.xml │ ├── support_v13_24_2_1.xml │ ├── support_v4_24_2_1.xml │ └── support_vector_drawable_24_2_1.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── tutsplus │ │ └── backgroundaudio │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── tutsplus │ │ │ └── backgroundaudio │ │ │ ├── BackgroundAudioService.java │ │ │ ├── MainActivity.java │ │ │ └── MediaStyleHelper.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── raw │ │ └── warner_tautz_off_broadway.mp3 │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── tutsplus │ └── backgroundaudio │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | 39 | # Keystore files 40 | *.jks 41 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | BackgroundAudio -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/libraries/animated_vector_drawable_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/appcompat_v7_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/libraries/hamcrest_core_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/junit_4_12.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/support_annotations_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/support_compat_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/libraries/support_core_ui_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/libraries/support_core_utils_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/support_fragment_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/libraries/support_media_compat_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/libraries/support_v13_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/support_v4_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/libraries/support_vector_drawable_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.8 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Tuts+ 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Envato Tuts+ Tutorial: [Background Audio in Android with MediaSessionCompat][published url] 2 | ## Instructor: [Paul Trebilcox-Ruiz][instructor url] 3 | 4 | 5 | One of the most popular uses for mobile devices is playing back audio through music streaming services, downloaded podcasts, or any other number of audio sources. While this is a fairly common feature, it's hard to implement, with lots of parts that need to be built correctly in order to give your user the full Android experience. In this tutorial you will learn about MediaSessionCompat from the Android support library, and how it can be used to create a proper background audio service for your users. 6 | 7 | ------ 8 | 9 | These are source files for the Envato Tuts+ tutorial: [Background Audio in Android with MediaSessionCompat][published url] 10 | 11 | Available on [Envato Tuts+](https://tutsplus.com). Teaching skills to millions worldwide. 12 | 13 | [published url]: http://code.tutsplus.com/tutorials/background-audio-in-android-with-mediasessioncompat--cms-27030 14 | [instructor url]: https://tutsplus.com/authors/paul-trebilcox-ruiz 15 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.tutsplus.backgroundaudio" 9 | minSdkVersion 16 10 | targetSdkVersion 24 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:24.2.1' 26 | compile 'com.android.support:support-v13:24.2.1' 27 | 28 | } 29 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/paultr/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/tutsplus/backgroundaudio/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.tutsplus.backgroundaudio; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/tutsplus/backgroundaudio/BackgroundAudioService.java: -------------------------------------------------------------------------------- 1 | package com.tutsplus.backgroundaudio; 2 | 3 | import android.app.PendingIntent; 4 | import android.content.BroadcastReceiver; 5 | import android.content.ComponentName; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.IntentFilter; 9 | import android.content.res.AssetFileDescriptor; 10 | import android.graphics.BitmapFactory; 11 | import android.media.AudioManager; 12 | import android.media.MediaPlayer; 13 | import android.os.Bundle; 14 | import android.os.PowerManager; 15 | import android.os.ResultReceiver; 16 | import android.support.annotation.NonNull; 17 | import android.support.annotation.Nullable; 18 | import android.support.v4.app.NotificationManagerCompat; 19 | import android.support.v4.media.MediaBrowserCompat; 20 | import android.support.v4.media.MediaBrowserServiceCompat; 21 | import android.support.v4.media.MediaMetadataCompat; 22 | import android.support.v4.media.session.MediaButtonReceiver; 23 | import android.support.v4.media.session.MediaSessionCompat; 24 | import android.support.v4.media.session.PlaybackStateCompat; 25 | import android.support.v7.app.NotificationCompat; 26 | import android.text.TextUtils; 27 | 28 | import java.io.IOException; 29 | import java.util.List; 30 | 31 | public class BackgroundAudioService extends MediaBrowserServiceCompat implements MediaPlayer.OnCompletionListener, AudioManager.OnAudioFocusChangeListener { 32 | 33 | public static final String COMMAND_EXAMPLE = "command_example"; 34 | 35 | private MediaPlayer mMediaPlayer; 36 | private MediaSessionCompat mMediaSessionCompat; 37 | 38 | private BroadcastReceiver mNoisyReceiver = new BroadcastReceiver() { 39 | @Override 40 | public void onReceive(Context context, Intent intent) { 41 | if( mMediaPlayer != null && mMediaPlayer.isPlaying() ) { 42 | mMediaPlayer.pause(); 43 | } 44 | } 45 | }; 46 | 47 | private MediaSessionCompat.Callback mMediaSessionCallback = new MediaSessionCompat.Callback() { 48 | 49 | @Override 50 | public void onPlay() { 51 | super.onPlay(); 52 | if( !successfullyRetrievedAudioFocus() ) { 53 | return; 54 | } 55 | 56 | mMediaSessionCompat.setActive(true); 57 | setMediaPlaybackState(PlaybackStateCompat.STATE_PLAYING); 58 | 59 | showPlayingNotification(); 60 | mMediaPlayer.start(); 61 | } 62 | 63 | @Override 64 | public void onPause() { 65 | super.onPause(); 66 | 67 | if( mMediaPlayer.isPlaying() ) { 68 | mMediaPlayer.pause(); 69 | setMediaPlaybackState(PlaybackStateCompat.STATE_PAUSED); 70 | showPausedNotification(); 71 | } 72 | } 73 | 74 | @Override 75 | public void onPlayFromMediaId(String mediaId, Bundle extras) { 76 | super.onPlayFromMediaId(mediaId, extras); 77 | 78 | try { 79 | AssetFileDescriptor afd = getResources().openRawResourceFd(Integer.valueOf(mediaId)); 80 | if( afd == null ) { 81 | return; 82 | } 83 | 84 | try { 85 | mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); 86 | 87 | } catch( IllegalStateException e ) { 88 | mMediaPlayer.release(); 89 | initMediaPlayer(); 90 | mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); 91 | } 92 | 93 | afd.close(); 94 | initMediaSessionMetadata(); 95 | 96 | } catch (IOException e) { 97 | return; 98 | } 99 | 100 | try { 101 | mMediaPlayer.prepare(); 102 | } catch (IOException e) {} 103 | 104 | //Work with extras here if you want 105 | } 106 | 107 | @Override 108 | public void onCommand(String command, Bundle extras, ResultReceiver cb) { 109 | super.onCommand(command, extras, cb); 110 | if( COMMAND_EXAMPLE.equalsIgnoreCase(command) ) { 111 | //Custom command here 112 | } 113 | } 114 | 115 | @Override 116 | public void onSeekTo(long pos) { 117 | super.onSeekTo(pos); 118 | } 119 | 120 | }; 121 | 122 | @Override 123 | public void onCreate() { 124 | super.onCreate(); 125 | 126 | initMediaPlayer(); 127 | initMediaSession(); 128 | initNoisyReceiver(); 129 | } 130 | 131 | private void initNoisyReceiver() { 132 | //Handles headphones coming unplugged. cannot be done through a manifest receiver 133 | IntentFilter filter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); 134 | registerReceiver(mNoisyReceiver, filter); 135 | } 136 | 137 | @Override 138 | public void onDestroy() { 139 | super.onDestroy(); 140 | AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 141 | audioManager.abandonAudioFocus(this); 142 | unregisterReceiver(mNoisyReceiver); 143 | mMediaSessionCompat.release(); 144 | NotificationManagerCompat.from(this).cancel(1); 145 | } 146 | 147 | private void initMediaPlayer() { 148 | mMediaPlayer = new MediaPlayer(); 149 | mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); 150 | mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); 151 | mMediaPlayer.setVolume(1.0f, 1.0f); 152 | } 153 | 154 | private void showPlayingNotification() { 155 | NotificationCompat.Builder builder = MediaStyleHelper.from(BackgroundAudioService.this, mMediaSessionCompat); 156 | if( builder == null ) { 157 | return; 158 | } 159 | 160 | 161 | builder.addAction(new NotificationCompat.Action(android.R.drawable.ic_media_pause, "Pause", MediaButtonReceiver.buildMediaButtonPendingIntent(this, PlaybackStateCompat.ACTION_PLAY_PAUSE))); 162 | builder.setStyle(new NotificationCompat.MediaStyle().setShowActionsInCompactView(0).setMediaSession(mMediaSessionCompat.getSessionToken())); 163 | builder.setSmallIcon(R.mipmap.ic_launcher); 164 | NotificationManagerCompat.from(BackgroundAudioService.this).notify(1, builder.build()); 165 | } 166 | 167 | private void showPausedNotification() { 168 | NotificationCompat.Builder builder = MediaStyleHelper.from(this, mMediaSessionCompat); 169 | if( builder == null ) { 170 | return; 171 | } 172 | 173 | builder.addAction(new NotificationCompat.Action(android.R.drawable.ic_media_play, "Play", MediaButtonReceiver.buildMediaButtonPendingIntent(this, PlaybackStateCompat.ACTION_PLAY_PAUSE))); 174 | builder.setStyle(new NotificationCompat.MediaStyle().setShowActionsInCompactView(0).setMediaSession(mMediaSessionCompat.getSessionToken())); 175 | builder.setSmallIcon(R.mipmap.ic_launcher); 176 | NotificationManagerCompat.from(this).notify(1, builder.build()); 177 | } 178 | 179 | 180 | private void initMediaSession() { 181 | ComponentName mediaButtonReceiver = new ComponentName(getApplicationContext(), MediaButtonReceiver.class); 182 | mMediaSessionCompat = new MediaSessionCompat(getApplicationContext(), "Tag", mediaButtonReceiver, null); 183 | 184 | mMediaSessionCompat.setCallback(mMediaSessionCallback); 185 | mMediaSessionCompat.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS ); 186 | 187 | Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 188 | mediaButtonIntent.setClass(this, MediaButtonReceiver.class); 189 | PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0); 190 | mMediaSessionCompat.setMediaButtonReceiver(pendingIntent); 191 | 192 | setSessionToken(mMediaSessionCompat.getSessionToken()); 193 | } 194 | 195 | private void setMediaPlaybackState(int state) { 196 | PlaybackStateCompat.Builder playbackstateBuilder = new PlaybackStateCompat.Builder(); 197 | if( state == PlaybackStateCompat.STATE_PLAYING ) { 198 | playbackstateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_PAUSE); 199 | } else { 200 | playbackstateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_PLAY); 201 | } 202 | playbackstateBuilder.setState(state, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 0); 203 | mMediaSessionCompat.setPlaybackState(playbackstateBuilder.build()); 204 | } 205 | 206 | private void initMediaSessionMetadata() { 207 | MediaMetadataCompat.Builder metadataBuilder = new MediaMetadataCompat.Builder(); 208 | //Notification icon in card 209 | metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)); 210 | metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)); 211 | 212 | //lock screen icon for pre lollipop 213 | metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)); 214 | metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Display Title"); 215 | metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Display Subtitle"); 216 | metadataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, 1); 217 | metadataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, 1); 218 | 219 | mMediaSessionCompat.setMetadata(metadataBuilder.build()); 220 | } 221 | 222 | private boolean successfullyRetrievedAudioFocus() { 223 | AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 224 | 225 | int result = audioManager.requestAudioFocus(this, 226 | AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); 227 | 228 | return result == AudioManager.AUDIOFOCUS_GAIN; 229 | } 230 | 231 | 232 | //Not important for general audio service, required for class 233 | @Nullable 234 | @Override 235 | public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { 236 | if(TextUtils.equals(clientPackageName, getPackageName())) { 237 | return new BrowserRoot(getString(R.string.app_name), null); 238 | } 239 | 240 | return null; 241 | } 242 | 243 | //Not important for general audio service, required for class 244 | @Override 245 | public void onLoadChildren(@NonNull String parentId, @NonNull Result> result) { 246 | result.sendResult(null); 247 | } 248 | 249 | @Override 250 | public void onAudioFocusChange(int focusChange) { 251 | switch( focusChange ) { 252 | case AudioManager.AUDIOFOCUS_LOSS: { 253 | if( mMediaPlayer.isPlaying() ) { 254 | mMediaPlayer.stop(); 255 | } 256 | break; 257 | } 258 | case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: { 259 | mMediaPlayer.pause(); 260 | break; 261 | } 262 | case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: { 263 | if( mMediaPlayer != null ) { 264 | mMediaPlayer.setVolume(0.3f, 0.3f); 265 | } 266 | break; 267 | } 268 | case AudioManager.AUDIOFOCUS_GAIN: { 269 | if( mMediaPlayer != null ) { 270 | if( !mMediaPlayer.isPlaying() ) { 271 | mMediaPlayer.start(); 272 | } 273 | mMediaPlayer.setVolume(1.0f, 1.0f); 274 | } 275 | break; 276 | } 277 | } 278 | } 279 | 280 | @Override 281 | public void onCompletion(MediaPlayer mediaPlayer) { 282 | if( mMediaPlayer != null ) { 283 | mMediaPlayer.release(); 284 | } 285 | } 286 | 287 | @Override 288 | public int onStartCommand(Intent intent, int flags, int startId) { 289 | MediaButtonReceiver.handleIntent(mMediaSessionCompat, intent); 290 | return super.onStartCommand(intent, flags, startId); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /app/src/main/java/com/tutsplus/backgroundaudio/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.tutsplus.backgroundaudio; 2 | 3 | import android.content.ComponentName; 4 | import android.os.RemoteException; 5 | import android.support.v4.media.MediaBrowserCompat; 6 | import android.support.v4.media.session.MediaControllerCompat; 7 | import android.support.v4.media.session.PlaybackStateCompat; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.os.Bundle; 10 | import android.view.View; 11 | import android.widget.Button; 12 | 13 | public class MainActivity extends AppCompatActivity { 14 | 15 | private static final int STATE_PAUSED = 0; 16 | private static final int STATE_PLAYING = 1; 17 | 18 | private int mCurrentState; 19 | 20 | private MediaBrowserCompat mMediaBrowserCompat; 21 | private MediaControllerCompat mMediaControllerCompat; 22 | 23 | private Button mPlayPauseToggleButton; 24 | 25 | private MediaBrowserCompat.ConnectionCallback mMediaBrowserCompatConnectionCallback = new MediaBrowserCompat.ConnectionCallback() { 26 | 27 | @Override 28 | public void onConnected() { 29 | super.onConnected(); 30 | try { 31 | mMediaControllerCompat = new MediaControllerCompat(MainActivity.this, mMediaBrowserCompat.getSessionToken()); 32 | mMediaControllerCompat.registerCallback(mMediaControllerCompatCallback); 33 | setSupportMediaController(mMediaControllerCompat); 34 | getSupportMediaController().getTransportControls().playFromMediaId(String.valueOf(R.raw.warner_tautz_off_broadway), null); 35 | 36 | } catch( RemoteException e ) { 37 | 38 | } 39 | } 40 | }; 41 | 42 | private MediaControllerCompat.Callback mMediaControllerCompatCallback = new MediaControllerCompat.Callback() { 43 | 44 | @Override 45 | public void onPlaybackStateChanged(PlaybackStateCompat state) { 46 | super.onPlaybackStateChanged(state); 47 | if( state == null ) { 48 | return; 49 | } 50 | 51 | switch( state.getState() ) { 52 | case PlaybackStateCompat.STATE_PLAYING: { 53 | mCurrentState = STATE_PLAYING; 54 | break; 55 | } 56 | case PlaybackStateCompat.STATE_PAUSED: { 57 | mCurrentState = STATE_PAUSED; 58 | break; 59 | } 60 | } 61 | } 62 | }; 63 | 64 | @Override 65 | protected void onCreate(Bundle savedInstanceState) { 66 | super.onCreate(savedInstanceState); 67 | setContentView(R.layout.activity_main); 68 | 69 | mPlayPauseToggleButton = (Button) findViewById(R.id.button); 70 | 71 | mMediaBrowserCompat = new MediaBrowserCompat(this, new ComponentName(this, BackgroundAudioService.class), 72 | mMediaBrowserCompatConnectionCallback, getIntent().getExtras()); 73 | 74 | mMediaBrowserCompat.connect(); 75 | 76 | mPlayPauseToggleButton.setOnClickListener(new View.OnClickListener() { 77 | @Override 78 | public void onClick(View view) { 79 | if( mCurrentState == STATE_PAUSED ) { 80 | getSupportMediaController().getTransportControls().play(); 81 | mCurrentState = STATE_PLAYING; 82 | } else { 83 | if( getSupportMediaController().getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING ) { 84 | getSupportMediaController().getTransportControls().pause(); 85 | } 86 | 87 | mCurrentState = STATE_PAUSED; 88 | } 89 | } 90 | }); 91 | 92 | } 93 | 94 | @Override 95 | protected void onDestroy() { 96 | super.onDestroy(); 97 | if( getSupportMediaController().getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING ) { 98 | getSupportMediaController().getTransportControls().pause(); 99 | } 100 | 101 | mMediaBrowserCompat.disconnect(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/tutsplus/backgroundaudio/MediaStyleHelper.java: -------------------------------------------------------------------------------- 1 | package com.tutsplus.backgroundaudio; 2 | 3 | import android.content.Context; 4 | import android.support.v4.content.ContextCompat; 5 | import android.support.v4.media.MediaDescriptionCompat; 6 | import android.support.v4.media.MediaMetadataCompat; 7 | import android.support.v4.media.session.MediaButtonReceiver; 8 | import android.support.v4.media.session.MediaControllerCompat; 9 | import android.support.v4.media.session.MediaSessionCompat; 10 | import android.support.v4.media.session.PlaybackStateCompat; 11 | import android.support.v7.app.NotificationCompat; 12 | 13 | /** 14 | * Helper APIs for constructing MediaStyle notifications 15 | */ 16 | public class MediaStyleHelper { 17 | /** 18 | * Build a notification using the information from the given media session. Makes heavy use 19 | * of {@link MediaMetadataCompat#getDescription()} to extract the appropriate information. 20 | * @param context Context used to construct the notification. 21 | * @param mediaSession Media session to get information. 22 | * @return A pre-built notification with information from the given media session. 23 | */ 24 | public static NotificationCompat.Builder from( 25 | Context context, MediaSessionCompat mediaSession) { 26 | MediaControllerCompat controller = mediaSession.getController(); 27 | MediaMetadataCompat mediaMetadata = controller.getMetadata(); 28 | MediaDescriptionCompat description = mediaMetadata.getDescription(); 29 | 30 | NotificationCompat.Builder builder = new NotificationCompat.Builder(context); 31 | builder 32 | .setContentTitle(description.getTitle()) 33 | .setContentText(description.getSubtitle()) 34 | .setSubText(description.getDescription()) 35 | .setLargeIcon(description.getIconBitmap()) 36 | .setContentIntent(controller.getSessionActivity()) 37 | .setDeleteIntent( 38 | MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP)) 39 | .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); 40 | return builder; 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 |