├── ambience ├── .gitignore ├── libs │ └── picasso-2.4.0.jar ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── app_icon.png │ │ │ │ └── unknown_album.png │ │ │ ├── drawable-xxhdpi │ │ │ │ └── app_icon.png │ │ │ └── values │ │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── tonyostudios │ │ │ └── ambience │ │ │ ├── AmbientTrack.java │ │ │ ├── Ambience.java │ │ │ ├── AmbientService.java │ │ │ └── AmbientMediaBrowserService.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── tonyostudios │ │ └── ambience │ │ └── ApplicationTest.java ├── build.gradle ├── proguard-rules.pro └── ambience.iml └── README.md /ambience/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /ambience/libs/picasso-2.4.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonyofrancis/ambience/HEAD/ambience/libs/picasso-2.4.0.jar -------------------------------------------------------------------------------- /ambience/src/main/res/drawable/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonyofrancis/ambience/HEAD/ambience/src/main/res/drawable/app_icon.png -------------------------------------------------------------------------------- /ambience/src/main/res/drawable/unknown_album.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonyofrancis/ambience/HEAD/ambience/src/main/res/drawable/unknown_album.png -------------------------------------------------------------------------------- /ambience/src/main/res/drawable-xxhdpi/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonyofrancis/ambience/HEAD/ambience/src/main/res/drawable-xxhdpi/app_icon.png -------------------------------------------------------------------------------- /ambience/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Ambience 3 | Unknown Track 4 | Unknown Artist 5 | Unknown Album 6 | 7 | -------------------------------------------------------------------------------- /ambience/src/androidTest/java/com/tonyostudios/ambience/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.tonyostudios.ambience; 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 | } -------------------------------------------------------------------------------- /ambience/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 21 5 | buildToolsVersion "21.1.1" 6 | 7 | defaultConfig { 8 | minSdkVersion 14 9 | targetSdkVersion 21 10 | versionCode 5 11 | versionName "1.5" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | compile 'com.android.support:appcompat-v7:21.0.2' 24 | compile 'com.android.support:support-v4:21.0.2' 25 | compile 'com.squareup.picasso:picasso:2.4.0' 26 | } 27 | -------------------------------------------------------------------------------- /ambience/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/tonyofrancis/Documents/adt-bundle-mac-x86_64-20140702/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 | -------------------------------------------------------------------------------- /ambience/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 22 | 23 | 24 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ambience (DEPRECATED) 2 | ======== 3 | 4 |
The Simplest Audio Player Service Library for Android
5 | 6 |

Introduction

7 |

Ambience is a small and powerful android library that helps you build advanced audio apps in minutes. 8 | Built upon many of android's system components and powerful open source libraries, Ambience is fast, efficient and easy to use.

9 |

Ambience is compatible with android mobile, tv and auto. Pass Ambience a playlist filled with tracks then lean back and relax. 10 | Ambience will continue to play audio even when the app is placed in the background.

11 | 12 |
Ambience.turnOn(context)
13 |         .setPlaylistTo(playlist)
14 |         .play();
15 | 16 |
17 |

Features

18 | 19 |
Playback Controls
20 |

Customize and play any playlist with just one line of code. Play, Skip, Forward, Rewind, Pause and Volume Control are just a few of the playback controls that Ambience provides.

21 | 22 |
Ambience.activeInstance()
23 |         .shuffleAndSetPlaylistTo(playlist)
24 |         .setVolumeTo(0.5f)
25 |         .playFromPosition(3);
26 | 27 | 28 |
Custom Notifications
29 | 30 |

Ambience creates a custom notification for each track in the playlist. Users can use these notifications to control playback options and jump right back into your app. Notifications created by Ambience will also show up on wearable devices that are connected to the app. For android tv and auto apps, a notification will show as a now playing card in the recommendation section.

31 |

To launch an activity from a notification, pass a unique Intent-Filter action name.

32 | 33 |
Ambience.activeInstance().setNotificationLaunchActivity(actionName);
34 | 35 |
Callback Methods
36 |

Get notified when an event occurs or playback options change via the AmbientListener interface. The AmbientListener provides several callback methods that are triggered for the current playing track or when an event occurs in the service.

37 | 38 |
Ambience.activeInstance().listenForUpdatesWith(AmbientListener);
39 | 40 |
AmbientTrack
41 |

Store track meta data with the AmbientTrack class. The AmbientTrack class extends on the android's Parcelable class. This allows an AmbientTrack to be easily integrated or shared between projects.

42 | 43 |
public void bindView(View view, Parcelable object) {
44 |     AmbientTrack track = (AmbientTrack) object;
45 |    ((TextView) view).setText(track.getName());
46 | }
47 | 48 |
49 | 50 |

How it Works

51 |

Ambience - A BroadcastReceiver that sends and receivers request from the AmbientService. This class provides methods for controlling audio playback. Ambience will trigger update methods on a callback object that has implemented the AmbientListener interface.

52 | 53 |

AmbientService - An Android Service that allows audio playback in the background. The AmbientService listens for a request, performs the request on the current playlist and alerts the Ambience class when done.

54 | 55 |

AmbientTrack - A class that holds meta data for a single track.

56 | 57 |

AmbientListener - A callback interface that is triggered when an event has occurred in the AmbientService.

58 | 59 |
60 | 61 |

Version

62 |

Current Version == v1.5

63 |

Visit the Ambience Website for more information.

64 |
65 | 66 |

Sample Code

67 |

Get the sample code here.

68 |
69 |

Contribute

70 |

Before submitting a request, please make sure that your code runs on an android device.

71 | 72 |
73 |

License

74 | Copyright 2014 TonyoStudios.com 75 | 76 | Licensed under the Apache License, Version 2.0 (the "License"); 77 | you may not use this file except in compliance with the License. 78 | You may obtain a copy of the License at 79 | 80 | http://www.apache.org/licenses/LICENSE-2.0 81 | 82 | Unless required by applicable law or agreed to in writing, software 83 | distributed under the License is distributed on an "AS IS" BASIS, 84 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 85 | See the License for the specific language governing permissions and 86 | limitations under the License. 87 |
88 |

Disclaimer

89 |

The Ambience Library uses Picasso a powerful image downloading and caching library for android. Picasso is property of Square Inc and is provided under the Apache 2.0 License. Visit square.github.io/Picasso for more information.

90 | -------------------------------------------------------------------------------- /ambience/ambience.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /ambience/src/main/java/com/tonyostudios/ambience/AmbientTrack.java: -------------------------------------------------------------------------------- 1 | package com.tonyostudios.ambience; 2 | import android.net.Uri; 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import java.util.ArrayList; 7 | 8 | 9 | /** 10 | * This class holds track information. It is passed to Ambience and the AmbientService 11 | * for playback. 12 | * @author TonyoStudios.com. Created on 11/18/2014. Updated on 11/27/2014. 13 | * @version 1.3 14 | */ 15 | public class AmbientTrack implements Parcelable { 16 | 17 | public static final String TAG = "com.tonyostudios.ambience.AmbientTrack"; 18 | 19 | //PRIVATE TRACK FIELDS 20 | private long id = -1; 21 | private String name = ""; 22 | private int duration = -1; 23 | private String artistName = ""; 24 | private long artistId = -1; 25 | private String albumName = ""; 26 | private long albumId = -1; 27 | private int position = -1; 28 | private String releaseDate = ""; 29 | private Uri albumImageUri = Uri.EMPTY; 30 | private Uri audioUri = Uri.EMPTY; 31 | private Uri audioDownloadUri = Uri.EMPTY; 32 | private ArrayList genresList = new ArrayList(); 33 | 34 | /** 35 | * Private Constructor 36 | */ 37 | private AmbientTrack() 38 | { 39 | super(); 40 | } 41 | 42 | /** 43 | * Method used to get a new instance of AmbientTrack 44 | * @return new instance of AmbientTrack 45 | */ 46 | public static AmbientTrack newInstance() 47 | { 48 | return new AmbientTrack(); 49 | } 50 | 51 | // Parcel creator object 52 | public static final Creator CREATOR = new Creator() { 53 | 54 | // Method used to create an AmbientTrack from Parcel 55 | @Override 56 | public AmbientTrack createFromParcel(Parcel in) { 57 | 58 | AmbientTrack AmbientTrack = new AmbientTrack(); 59 | 60 | AmbientTrack.id = in.readLong(); 61 | AmbientTrack.name = in.readString(); 62 | AmbientTrack.duration = in.readInt(); 63 | AmbientTrack.artistName =in.readString(); 64 | AmbientTrack.artistId = in.readLong(); 65 | AmbientTrack.albumName = in.readString(); 66 | AmbientTrack.albumId = in.readLong(); 67 | AmbientTrack.position = in.readInt(); 68 | AmbientTrack.releaseDate = in.readString(); 69 | AmbientTrack.albumImageUri = Uri.parse(in.readString()); 70 | AmbientTrack.audioUri = Uri.parse(in.readString()); 71 | AmbientTrack.audioDownloadUri = Uri.parse(in.readString()); 72 | in.readStringList(AmbientTrack.genresList); 73 | 74 | return AmbientTrack; 75 | } 76 | 77 | /** 78 | * Method used to return an AmbientTrack Array 79 | * @param i Array size 80 | * @return AmbientTrack Array 81 | */ 82 | @Override 83 | public AmbientTrack[] newArray(int i) { 84 | return new AmbientTrack[i]; 85 | } 86 | }; 87 | 88 | /** 89 | * Method that describes parcel content 90 | * @return always returns 0 (Default) 91 | */ 92 | @Override 93 | public int describeContents() { 94 | return 0; 95 | } 96 | 97 | /** 98 | * Method used to Create Parcel from AmbientTrack Object 99 | * @param parcel Parcel item 100 | * @param i Flag 101 | */ 102 | @Override 103 | public void writeToParcel(Parcel parcel, int i) { 104 | 105 | parcel.writeLong(id); 106 | parcel.writeString(name); 107 | parcel.writeInt(duration); 108 | parcel.writeString(artistName); 109 | parcel.writeLong(artistId); 110 | parcel.writeString(albumName); 111 | parcel.writeLong(albumId); 112 | parcel.writeInt(position); 113 | parcel.writeString(releaseDate); 114 | parcel.writeString(albumImageUri.toString()); 115 | parcel.writeString(audioUri.toString()); 116 | parcel.writeString(audioDownloadUri.toString()); 117 | parcel.writeStringList(genresList); 118 | 119 | } 120 | 121 | /** 122 | * Method that returns track id 123 | * @return Track id 124 | */ 125 | public long getId() { 126 | return id; 127 | } 128 | 129 | /** 130 | *Method that returns track name 131 | * @return Track name 132 | */ 133 | public String getName() { 134 | return name; 135 | } 136 | 137 | /** 138 | * Method that returns track duration 139 | * @return Track duration 140 | */ 141 | public int getDuration() { 142 | return duration; 143 | } 144 | 145 | /** 146 | * Method that returns artist name 147 | * @return Artist name 148 | */ 149 | public String getArtistName() { 150 | return artistName; 151 | } 152 | 153 | /** 154 | * Method that returns artist id 155 | * @return Artist id 156 | */ 157 | public long getArtistId() { 158 | return artistId; 159 | } 160 | 161 | /** 162 | * Method that returns album name 163 | * @return Album Name 164 | */ 165 | public String getAlbumName() { 166 | return albumName; 167 | } 168 | 169 | /** 170 | * Method that returns album id 171 | * @return Album id 172 | */ 173 | public long getAlbumId() { 174 | return albumId; 175 | } 176 | 177 | /** 178 | * Method that returns track position 179 | * @return Track position 180 | */ 181 | public int getPosition() { 182 | return position; 183 | } 184 | 185 | /** 186 | * Method that returns track release date 187 | * @return Track releaseDate 188 | */ 189 | public String getReleaseDate() { 190 | return releaseDate; 191 | } 192 | 193 | /** 194 | * Method that returns uri where album image is located 195 | * @return String uri where album image is located 196 | */ 197 | public Uri getAlbumImageUri() { 198 | return albumImageUri; 199 | } 200 | 201 | /** 202 | * Method that returns uri where track is located 203 | * @return String uri where track is located 204 | */ 205 | public Uri getAudioUri() { 206 | return audioUri; 207 | } 208 | 209 | /** 210 | * Method that returns uri where track can be downloaded 211 | * @return String uri where track can be downloaded 212 | */ 213 | public Uri getAudioDownloadUri() { 214 | return audioDownloadUri; 215 | } 216 | 217 | /** 218 | * Method that gets track genres 219 | * @return String array of track genres 220 | */ 221 | public ArrayList getGenresList() { 222 | return genresList; 223 | } 224 | 225 | /** 226 | * Method that sets track id 227 | * @param id Track id 228 | * @return The AmbientTrack instance 229 | */ 230 | public AmbientTrack setId(long id) { 231 | this.id = id; 232 | 233 | return this; 234 | } 235 | 236 | /** 237 | * Method that sets track name 238 | * @param name Track name 239 | * @return The AmbientTrack instance 240 | */ 241 | public AmbientTrack setName(String name) { 242 | 243 | if(name == null) 244 | { 245 | return this; 246 | } 247 | 248 | this.name = name; 249 | 250 | return this; 251 | } 252 | 253 | /** 254 | * Method that sets track duration 255 | * @param duration Track duration 256 | * @return The AmbientTrack instance 257 | */ 258 | public AmbientTrack setDuration(int duration) { 259 | this.duration = duration; 260 | 261 | return this; 262 | } 263 | 264 | /** 265 | * Method that sets artist name 266 | * @param artistName Artist name 267 | * @return The AmbientTrack instance 268 | */ 269 | public AmbientTrack setArtistName(String artistName) { 270 | 271 | if(artistName == null) 272 | { 273 | return this; 274 | } 275 | 276 | this.artistName = artistName; 277 | 278 | return this; 279 | } 280 | 281 | /** 282 | * Method that sets artist id 283 | * @param artistId Artist id 284 | * @return The AmbientTrack instance 285 | */ 286 | public AmbientTrack setArtistId(long artistId) { 287 | this.artistId = artistId; 288 | 289 | return this; 290 | } 291 | 292 | /** 293 | * Method that sets album name 294 | * @param albumName Album Name 295 | * @return The AmbientTrack instance 296 | */ 297 | public AmbientTrack setAlbumName(String albumName) { 298 | 299 | if(albumName == null) 300 | { 301 | return this; 302 | } 303 | 304 | this.albumName = albumName; 305 | 306 | return this; 307 | } 308 | 309 | /** 310 | * Method that sets album id 311 | * @param albumId Album id 312 | * @return The AmbientTrack instance 313 | */ 314 | public AmbientTrack setAlbumId(long albumId) { 315 | this.albumId = albumId; 316 | 317 | return this; 318 | } 319 | 320 | /** 321 | * Method that sets track position 322 | * @param position Track position 323 | * @return The AmbientTrack instance 324 | */ 325 | public AmbientTrack setPosition(int position) { 326 | this.position = position; 327 | 328 | return this; 329 | } 330 | 331 | /** 332 | * Method that sets track release date 333 | * @param releaseDate Track release date 334 | * @return The AmbientTrack instance 335 | */ 336 | public AmbientTrack setReleaseDate(String releaseDate) { 337 | 338 | if(releaseDate == null) 339 | { 340 | return this; 341 | } 342 | 343 | this.releaseDate = releaseDate; 344 | 345 | return this; 346 | } 347 | 348 | /** 349 | * Method that sets album image uri where the album image is located 350 | * @param albumImage String uri where the track album is located 351 | * @return The AmbientTrack instance 352 | */ 353 | public AmbientTrack setAlbumImageUri(Uri albumImage) { 354 | 355 | if(albumImage == null) 356 | { 357 | return this; 358 | } 359 | 360 | this.albumImageUri = albumImage; 361 | 362 | return this; 363 | } 364 | 365 | /** 366 | * Method that sets track uri where track is located 367 | * @param audio String uri where the track is located 368 | * @return The AmbientTrack instance 369 | */ 370 | public AmbientTrack setAudioUri(Uri audio) { 371 | 372 | if(audio == null) 373 | { 374 | return this; 375 | } 376 | 377 | this.audioUri = audio; 378 | 379 | return this; 380 | } 381 | 382 | /** 383 | * Method that sets track uri where track can be downloaded 384 | * @param audioDownload String uri where the track can be downloaded 385 | * @return The AmbientTrack instance 386 | */ 387 | public AmbientTrack setAudioDownloadUri(Uri audioDownload) { 388 | 389 | if(audioDownload == null) 390 | { 391 | return this; 392 | } 393 | 394 | this.audioDownloadUri = audioDownload; 395 | 396 | return this; 397 | } 398 | 399 | /** 400 | * Method that sets track genres 401 | * @param genres String array of track genres 402 | * @return The AmbientTrack instance 403 | */ 404 | public AmbientTrack setGenres(ArrayList genres) { 405 | 406 | if(genres == null) 407 | { 408 | return this; 409 | } 410 | 411 | for(int x = 0; x < genres.size(); x++) 412 | { 413 | if(genres.get(x) == null) 414 | { 415 | throw new NullPointerException(TAG +": cannot contain a null object in the genre list"); 416 | } 417 | } 418 | 419 | this.genresList = genres; 420 | 421 | return this; 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /ambience/src/main/java/com/tonyostudios/ambience/Ambience.java: -------------------------------------------------------------------------------- 1 | package com.tonyostudios.ambience; 2 | 3 | import android.app.UiModeManager; 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.IntentFilter; 8 | import android.content.res.Configuration; 9 | import android.os.Parcelable; 10 | import android.util.Log; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | 15 | /** 16 | * Ambience is a Broadcast Receiver 17 | * registered and used by an Android component (Activity,Fragment or Service) to 18 | * send and receive update playback control status and information 19 | * to and from the AmbientService. 20 | * @author TonyoStudios.com. Created on 11/18/2014. Updated on 12/01/2014. 21 | * @version 1.3 22 | */ 23 | public class Ambience extends BroadcastReceiver { 24 | 25 | /** 26 | * Tag used to identify the Ambience class 27 | */ 28 | private static final String TAG = "com.tonyostudios.ambience.Ambience"; 29 | 30 | /** 31 | * Tag used to identify the Ambience Broadcaster 32 | */ 33 | public static final String AMBIENCE_BROADCASTER = TAG + ".BROADCASTER"; 34 | 35 | /** 36 | * Holds instance of Ambience 37 | */ 38 | private static Ambience mAmbience; 39 | 40 | /** 41 | * Holds context object 42 | */ 43 | private static Context mContext; 44 | 45 | /** 46 | * Holds AmbientListener callback 47 | */ 48 | private static AmbientListener mCallback; 49 | 50 | /** 51 | * Boolean value used to indicate if Ambience is 52 | * listening for updates from the AmbientService 53 | */ 54 | private static boolean isListeningForUpdates = false; 55 | 56 | /** 57 | * Boolean value used to indicate if the AmbientService 58 | * is started or has stopped 59 | */ 60 | private static boolean isAmbientServiceStarted = false; 61 | 62 | /** 63 | * private constructor 64 | */ 65 | private Ambience() 66 | { 67 | super(); 68 | } 69 | 70 | /** 71 | * Method used to initialize the Ambience. This method starts the AmbientService 72 | * if it is not alreadyStarted. 73 | * @param context A context object 74 | * @return An Ambience object 75 | */ 76 | public static Ambience turnOn(Context context) 77 | { 78 | if(context == null) 79 | { 80 | throw new NullPointerException(TAG + " context cannot be null"); 81 | } 82 | 83 | mContext = null; 84 | mContext = context; 85 | 86 | if(mAmbience == null) 87 | { 88 | mAmbience = new Ambience(); 89 | } 90 | 91 | mAmbience.startAmbientService(); 92 | 93 | return mAmbience; 94 | } 95 | 96 | /** 97 | * Method used to access the single Instance of Ambience 98 | * @return An Ambience object 99 | */ 100 | public static Ambience activeInstance() 101 | { 102 | if(mAmbience == null) 103 | { 104 | throw new NullPointerException(TAG + " - No Active Instance of Ambience. Did you forget " + 105 | "to call turnOn?"); 106 | } 107 | 108 | return mAmbience; 109 | } 110 | 111 | /** 112 | * Method used to register Ambience with the Broadcast Manager 113 | * @param callback An Android component that will handle all playback 114 | * control callbacks from the AmbientService. The Android 115 | * component can be an Activity, Fragment or Service. Only 116 | * a single Android component can receive updates from the 117 | * AmbientService. 118 | * 119 | *@return An Ambience object 120 | */ 121 | public Ambience listenForUpdatesWith(AmbientListener callback) 122 | { 123 | if(callback == null) 124 | { 125 | throw new NullPointerException(TAG + ": callback cannot be null"); 126 | } 127 | 128 | // If callback object has changed, replace it with new callback 129 | if(callback != mCallback) 130 | { 131 | mCallback = null; 132 | mCallback = callback; 133 | } 134 | 135 | if(!isListeningForUpdates() && mContext != null && mAmbience != null) 136 | { 137 | IntentFilter filter = new IntentFilter(AMBIENCE_BROADCASTER); 138 | mContext.registerReceiver(mAmbience,filter); 139 | 140 | isListeningForUpdates = true; 141 | } 142 | 143 | return mAmbience; 144 | } 145 | 146 | /** 147 | * Method used to unregister Ambience with the Broadcast Manager. The Callback Listener 148 | * will no longer receive updates. 149 | * @return Instance of Ambient Service Controller 150 | */ 151 | public static Ambience stopListeningForUpdates() 152 | { 153 | if(isListeningForUpdates() && mContext != null && mAmbience != null) 154 | { 155 | mContext.unregisterReceiver(mAmbience); 156 | isListeningForUpdates = false; 157 | } 158 | 159 | return mAmbience; 160 | } 161 | 162 | /** 163 | * Method used to check if Ambience is listening for updates from the AmbientService 164 | * @return A boolean value indicating if Ambience is listening for updates from AmbientService 165 | */ 166 | public static boolean isListeningForUpdates() 167 | { 168 | return isListeningForUpdates; 169 | } 170 | 171 | /** 172 | * Method used to check if the AmbientService has started or stopped 173 | * @return boolean value indicating if the AmbientService has started or stopped 174 | */ 175 | public static boolean hasAmbientServiceStarted() 176 | { 177 | return isAmbientServiceStarted; 178 | } 179 | 180 | /** 181 | * Method used to start the AmbientService 182 | */ 183 | private void startAmbientService() 184 | { 185 | if(!hasAmbientServiceStarted()) 186 | { 187 | if(isAndroidTvOrCar()) 188 | { 189 | mContext.startService(new Intent(mContext, AmbientMediaBrowserService.class)); 190 | isAmbientServiceStarted = true; 191 | return; 192 | } 193 | 194 | 195 | mContext.startService(new Intent(mContext, AmbientService.class)); 196 | isAmbientServiceStarted = true; 197 | } 198 | } 199 | 200 | /** 201 | * Method used to stop the AmbientService 202 | */ 203 | private static void stopAmbientService() 204 | { 205 | if(hasAmbientServiceStarted()) 206 | { 207 | if(isAndroidTvOrCar()) 208 | { 209 | mContext.stopService(new Intent(mContext, AmbientMediaBrowserService.class)); 210 | isAmbientServiceStarted = false; 211 | 212 | return; 213 | } 214 | 215 | mContext.stopService(new Intent(mContext, AmbientService.class)); 216 | isAmbientServiceStarted = false; 217 | } 218 | } 219 | 220 | 221 | /** 222 | * Method used to release all resources held by Ambience. 223 | * This method un-registers Ambience and stops the AmbientService. This is a destructive method. 224 | * All media playback, updates and notifications will be cancelled. 225 | */ 226 | public static void turnOff() 227 | { 228 | //unregister receiver & stop Ambient Service 229 | stopAmbientService(); 230 | stopListeningForUpdates(); 231 | 232 | mContext = null; 233 | mAmbience = null; 234 | mCallback = null; 235 | isListeningForUpdates = false; 236 | isAmbientServiceStarted = false; 237 | } 238 | 239 | /** 240 | * Sends a volume request to the AmbientService 241 | * @param volume The amount to increase or decrease the audio player's volume 242 | * @return An Ambience object 243 | */ 244 | public Ambience setVolumeTo(float volume) 245 | { 246 | if(volume < 0.0f || volume > 1.0f) 247 | { 248 | volume = 0.5f; 249 | } 250 | 251 | Intent intent = getAmbientServiceIntentInstance(); 252 | intent.putExtra(AmbientService.VOLUME_LEVEL, volume); 253 | sendIntentToAmbientService(intent); 254 | 255 | return mAmbience; 256 | } 257 | 258 | 259 | /** 260 | * Sets the Ambience Playlist 261 | * @param playlist Ambient Track Playlist Array 262 | * @return An Ambience object 263 | */ 264 | public Ambience setPlaylistTo(AmbientTrack[] playlist) 265 | { 266 | if(playlist == null) 267 | { 268 | return mAmbience; 269 | } 270 | 271 | sendPlaylistToAmbientService(new ArrayList(Arrays.asList(playlist))); 272 | 273 | return mAmbience; 274 | } 275 | 276 | /** 277 | * Sets the Ambience Playlist 278 | * @param playlist Ambient Track Playlist ArrayList 279 | * @return An Ambience object 280 | */ 281 | public Ambience setPlaylistTo(ArrayList playlist) 282 | { 283 | if(playlist == null) 284 | { 285 | return mAmbience; 286 | } 287 | 288 | ArrayList list = new ArrayList(); 289 | 290 | for(int x = 0; x < playlist.size(); x++) 291 | { 292 | AmbientTrack track = playlist.get(x); 293 | if(track!= null) 294 | { 295 | list.add(track); 296 | } 297 | } 298 | 299 | sendPlaylistToAmbientService(list); 300 | 301 | return mAmbience; 302 | } 303 | 304 | /** 305 | * Method used to set the Ambience Playlist and shuffle it 306 | * @param playlist Ambient Track Playlist ArrayList 307 | * @return An Ambience object 308 | */ 309 | public Ambience shuffleAndSetPlaylistTo(ArrayList playlist) 310 | { 311 | shufflePlaylist(); 312 | setPlaylistTo(playlist); 313 | 314 | return mAmbience; 315 | } 316 | 317 | /** 318 | * Method used to set the Ambience Playlist and shuffle it 319 | * @param playlist Ambient Track Playlist Array 320 | * @return An Ambience object 321 | */ 322 | public Ambience shuffleAndSetPlaylistTo(AmbientTrack[] playlist) 323 | { 324 | shufflePlaylist(); 325 | setPlaylistTo(playlist); 326 | 327 | return mAmbience; 328 | } 329 | 330 | /** 331 | * Method used to send the AmbientTrack playlist to the AmbientSercice 332 | * @param playlist Ambient Track Playlist Parcelable ArrayList 333 | */ 334 | private void sendPlaylistToAmbientService(ArrayList playlist) 335 | { 336 | for(int x = 0; x < playlist.size(); x++) 337 | { 338 | if(playlist.get(x) == null) 339 | { 340 | throw new NullPointerException(TAG + ": AmbientTrack at position" + x + " cannot be null."); 341 | } 342 | } 343 | 344 | Intent intent = getAmbientServiceIntentInstance(); 345 | intent.putParcelableArrayListExtra(AmbientService.PLAYLIST,playlist); 346 | sendIntentToAmbientService(intent); 347 | } 348 | 349 | /** 350 | * Sends a shuffle request to the AmbientService 351 | * @return An Ambience object 352 | */ 353 | public Ambience shufflePlaylist() 354 | { 355 | Intent intent = getAmbientServiceIntentInstance(); 356 | intent.putExtra(AmbientService.SHUFFLE_MODE,AmbientService.ShuffleMode.ON); 357 | sendIntentToAmbientService(intent); 358 | 359 | return mAmbience; 360 | } 361 | 362 | /** 363 | * Sends a unShuffle request to the AmbientService 364 | * @return An Ambience object 365 | */ 366 | public Ambience unShufflePlaylist() 367 | { 368 | Intent intent = getAmbientServiceIntentInstance(); 369 | intent.putExtra(AmbientService.SHUFFLE_MODE,AmbientService.ShuffleMode.OFF); 370 | sendIntentToAmbientService(intent); 371 | 372 | return mAmbience; 373 | } 374 | 375 | /** 376 | * Adds a single track to the AmbientTrack playlist 377 | * @param track A AmbientTrack object 378 | * @return An Ambience object 379 | */ 380 | public Ambience addTrackToPlaylist(AmbientTrack track) 381 | { 382 | if(track == null) 383 | { 384 | return mAmbience; 385 | } 386 | 387 | Intent intent = getAmbientServiceIntentInstance(); 388 | intent.putExtra(AmbientService.ADD_TRACK,track); 389 | sendIntentToAmbientService(intent); 390 | 391 | return mAmbience; 392 | } 393 | 394 | 395 | /** 396 | * Removes a single track in the AmbientTrack playlist 397 | * @param track The AmbientTrack object to remove 398 | * @return An Ambience object 399 | */ 400 | public Ambience removeTrackFromPlaylist(AmbientTrack track) 401 | { 402 | if(track == null) 403 | { 404 | return mAmbience; 405 | } 406 | 407 | Intent intent = getAmbientServiceIntentInstance(); 408 | intent.putExtra(AmbientService.REMOVE_TRACK,track); 409 | sendIntentToAmbientService(intent); 410 | 411 | return mAmbience; 412 | } 413 | 414 | /** 415 | * Sends a request to repeat a single track to the AmbientService 416 | * @return An Ambience object 417 | */ 418 | public Ambience repeatASingleTrack() 419 | { 420 | Intent intent = getAmbientServiceIntentInstance(); 421 | intent.putExtra(AmbientService.REPEAT_MODE,AmbientService.RepeatMode.REPEAT_ONE); 422 | sendIntentToAmbientService(intent); 423 | 424 | return mAmbience; 425 | } 426 | 427 | 428 | /** 429 | * Sends a request to repeat the playlist to the AmbientService 430 | * @return An Ambience object 431 | */ 432 | public Ambience repeatAllTracks() 433 | { 434 | Intent intent = getAmbientServiceIntentInstance(); 435 | intent.putExtra(AmbientService.REPEAT_MODE,AmbientService.RepeatMode.REPEAT_ALL); 436 | sendIntentToAmbientService(intent); 437 | 438 | return mAmbience; 439 | } 440 | 441 | /** 442 | * Sends a request to turn off repeat to the AmbientService 443 | * @return An Ambience object 444 | */ 445 | public Ambience turnRepeatOff() 446 | { 447 | Intent intent = getAmbientServiceIntentInstance(); 448 | intent.putExtra(AmbientService.REPEAT_MODE,AmbientService.RepeatMode.OFF); 449 | sendIntentToAmbientService(intent); 450 | 451 | return mAmbience; 452 | } 453 | 454 | 455 | /** 456 | * Method used to append a launch activity request to the Ambience notification 457 | * @param intentFilterAction intent Filter Action Name to launch a specific activity. This should 458 | * be a unique identifier for your activity 459 | * @return Instance of Ambience 460 | */ 461 | public Ambience setNotificationLaunchActivity(String intentFilterAction) 462 | { 463 | if(intentFilterAction == null) 464 | { 465 | return mAmbience; 466 | } 467 | 468 | Intent intent = getAmbientServiceIntentInstance(); 469 | intent.putExtra(AmbientService.ACTIVITY_LAUNCHER,intentFilterAction); 470 | sendIntentToAmbientService(intent); 471 | 472 | return mAmbience; 473 | } 474 | 475 | /** 476 | * Method used to send a seek request to the AmbientService 477 | * @param progress Seek progress value 478 | * @return Instance of Ambience 479 | */ 480 | public Ambience seekTo(int progress) 481 | { 482 | Intent intent = getAmbientServiceIntentInstance(); 483 | intent.putExtra(AmbientService.SEEK_POSITION,progress); 484 | sendIntentToAmbientService(intent); 485 | 486 | return mAmbience; 487 | } 488 | 489 | 490 | /** 491 | * Helper method used to create a new Intent object 492 | * used to send messages to the AmbientService 493 | * @return A new Intent object used to send messages to the AmbientService 494 | */ 495 | private Intent getAmbientServiceIntentInstance() 496 | { 497 | return new Intent(AmbientService.AMBIENT_SERVICE_BROADCASTER); 498 | } 499 | 500 | /** 501 | * Helper method used to send an intent to AmbientService 502 | * @param intent Intent Object 503 | */ 504 | private void sendIntentToAmbientService(Intent intent) 505 | { 506 | if(mContext != null && intent != null) 507 | { 508 | mContext.sendBroadcast(intent); 509 | } 510 | } 511 | 512 | /** 513 | * Method used to play a track at a certain position 514 | * in the playlist. 515 | * @param position Track position 516 | */ 517 | public void PlayFromPosition(int position) 518 | { 519 | Intent intent = getAmbientServiceIntentInstance(); 520 | intent.putExtra(AmbientService.PLAY_POSITION,position); 521 | sendIntentToAmbientService(intent); 522 | 523 | play(); 524 | } 525 | 526 | /** 527 | * Method used to play a track 528 | */ 529 | public void play() 530 | { 531 | Intent intent = getAmbientServiceIntentInstance(); 532 | intent.putExtra(AmbientService.PLAYBACK_STATE, AmbientService.PlaybackState.PLAY); 533 | sendIntentToAmbientService(intent); 534 | } 535 | 536 | /** 537 | * Method used to stop a current playing track 538 | */ 539 | public void stop() 540 | { 541 | Intent intent = getAmbientServiceIntentInstance(); 542 | intent.putExtra(AmbientService.PLAYBACK_STATE,AmbientService.PlaybackState.STOP); 543 | sendIntentToAmbientService(intent); 544 | } 545 | 546 | /** 547 | * Method used to resume the current track 548 | */ 549 | public void resume() 550 | { 551 | Intent intent = getAmbientServiceIntentInstance(); 552 | intent.putExtra(AmbientService.PLAYBACK_STATE,AmbientService.PlaybackState.RESUME); 553 | sendIntentToAmbientService(intent); 554 | } 555 | 556 | /** 557 | * Method used to skip the current track 558 | */ 559 | public void skip() 560 | { 561 | Intent intent = getAmbientServiceIntentInstance(); 562 | intent.putExtra(AmbientService.PLAYBACK_STATE,AmbientService.PlaybackState.SKIP); 563 | sendIntentToAmbientService(intent); 564 | } 565 | 566 | /** 567 | * Method used to play the previous track 568 | */ 569 | public void previous() 570 | { 571 | Intent intent = getAmbientServiceIntentInstance(); 572 | intent.putExtra(AmbientService.PLAYBACK_STATE,AmbientService.PlaybackState.PREVIOUS); 573 | sendIntentToAmbientService(intent); 574 | } 575 | 576 | /** 577 | * Method used to pause the current track 578 | */ 579 | public void pause() 580 | { 581 | Intent intent = getAmbientServiceIntentInstance(); 582 | intent.putExtra(AmbientService.PLAYBACK_STATE,AmbientService.PlaybackState.PAUSE); 583 | sendIntentToAmbientService(intent); 584 | } 585 | 586 | 587 | /** 588 | * This method receives playback control updates from the AmbientService. 589 | * @param context A context object 590 | * @param intent An intent object 591 | */ 592 | @Override 593 | public void onReceive(Context context, Intent intent) { 594 | if(intent == null || intent.getExtras() == null) 595 | { 596 | Log.e(TAG, "Passed an empty or null intent to Ambience"); 597 | return; 598 | } 599 | 600 | if(intent.getSerializableExtra(AmbientService.PLAYBACK_STATE) == null) 601 | { 602 | Log.e(TAG, "Intent does not contain a known playback state "); 603 | return; 604 | } 605 | 606 | if(mCallback == null) 607 | { 608 | Log.e(TAG, ": The callback(AmbientListener) object is null."); 609 | return; 610 | } 611 | 612 | try 613 | { 614 | AmbientService.PlaybackState state = 615 | (AmbientService.PlaybackState) intent.getSerializableExtra(AmbientService.PLAYBACK_STATE); 616 | 617 | // Alert the callback with the currentPlayback State. 618 | switch (state) 619 | { 620 | case PLAY: mCallback.ambienceTrackIsPlaying(); 621 | break; 622 | case STOP: mCallback.ambienceTrackHasStopped(); 623 | break; 624 | case PAUSE: mCallback.ambienceTrackIsPaused(); 625 | break; 626 | case PREPPING_TRACK: mCallback.ambienceIsPreppingTrack(); 627 | break; 628 | case END_OF_PLAYLIST: mCallback.ambiencePlaylistCompleted(); 629 | break; 630 | case CURRENT_PLAYING_TRACK_INFO: 631 | { 632 | if(intent.hasExtra(AmbientService.TRACK_PROGRESS)) 633 | { 634 | mCallback.ambienceTrackCurrentProgress(intent.getIntExtra(AmbientService.TRACK_PROGRESS,0)); 635 | } 636 | 637 | if(intent.hasExtra(AmbientService.TRACK_DURATION)) 638 | { 639 | mCallback.ambienceTrackDuration(intent.getIntExtra(AmbientService.TRACK_DURATION,0)); 640 | } 641 | 642 | if(intent.hasExtra(AmbientService.CURRENT_TRACK)) 643 | { 644 | mCallback.ambiencePlayingTrack((AmbientTrack)intent.getParcelableExtra(AmbientService.CURRENT_TRACK)); 645 | } 646 | 647 | break; 648 | } 649 | /* 650 | * No specific error message is passed. The callback must assume that the track 651 | * could not be played and attempt to retry or get feedback from the user. 652 | */ 653 | case ERROR: mCallback.ambienceErrorOccurred(); 654 | break; 655 | 656 | case SERVICE_STARTED: 657 | { 658 | mCallback.ambienceServiceStarted(activeInstance()); 659 | isAmbientServiceStarted = true; 660 | break; 661 | } 662 | case SERVICE_STOPPED: 663 | { 664 | mCallback.ambienceServiceStopped(activeInstance()); 665 | isAmbientServiceStarted = false; 666 | break; 667 | } 668 | 669 | default: 670 | throw new IllegalStateException(TAG + ": Unknown Playback State"); 671 | } 672 | }catch (Exception e) 673 | { 674 | e.printStackTrace(); 675 | Log.e(TAG,e.getMessage()); 676 | } 677 | } 678 | 679 | /** 680 | * Called to check if the application is running on an androidTV or androidAuto. This method is used to launch 681 | * the appropriate android service specific for TV or Auto. 682 | * @return boolean value that indicates if an application is running on 683 | * a TV or Auto. 684 | */ 685 | private static boolean isAndroidTvOrCar() 686 | { 687 | if(mContext != null) 688 | { 689 | UiModeManager uiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE); 690 | 691 | if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION 692 | || uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR ) { 693 | 694 | return true; 695 | } 696 | 697 | } 698 | 699 | return false; 700 | } 701 | 702 | 703 | /** 704 | * Interface Listener that must be implemented by An Android Component such as an Activity, Fragment or Service 705 | * to receive updates from Ambience. Some methods in this interface are called 706 | * more frequently than others. 707 | */ 708 | public static abstract interface AmbientListener { 709 | 710 | /** 711 | * Method used to update an Android Component when the track is being prep for play. 712 | */ 713 | public void ambienceIsPreppingTrack(); 714 | 715 | /** 716 | * Method used to update an Android Component with the current playing 717 | * track's duration time. This method is called often. 718 | * @param time Track duration time. 719 | */ 720 | public void ambienceTrackDuration(int time); 721 | 722 | /** 723 | * Method used to update an Android Component with 724 | * the current playing track. This method is called often 725 | * @param track Current playing track 726 | */ 727 | public void ambiencePlayingTrack(AmbientTrack track); 728 | 729 | /** 730 | * Method used to update an Android Component with 731 | * current progress time of the current playing track. This method is called often 732 | * @param time Current progress of the track 733 | */ 734 | public void ambienceTrackCurrentProgress(int time); 735 | 736 | /** 737 | * Method used to alert an Android Component when a track begins playing 738 | */ 739 | public void ambienceTrackIsPlaying(); 740 | 741 | /** 742 | * Method used to alert an Android Component when a track is paused 743 | */ 744 | public void ambienceTrackIsPaused(); 745 | 746 | /** 747 | * Method used to alert an Android Component when a track has stopped 748 | */ 749 | public void ambienceTrackHasStopped(); 750 | 751 | /** 752 | * Method used to update an Android Component when the AmbientService has completed playing 753 | * a playlist. 754 | */ 755 | public void ambiencePlaylistCompleted(); 756 | 757 | /** 758 | * Method used to update an Android Component when an error occurred in the AmbientService. 759 | * No specific error message is passed. The Android Component must assume that the track 760 | * could not be played. Use this method to retry playing the track or take another action. 761 | * @see "Logcat for the specific error message from the AmbientService" 762 | */ 763 | public void ambienceErrorOccurred(); 764 | 765 | 766 | /** 767 | * Called when the AmbientService is started and waiting requests 768 | * @param activeInstance The active instance of ambience 769 | */ 770 | public void ambienceServiceStarted(Ambience activeInstance); 771 | 772 | 773 | /** 774 | * Called when the AmbientService is stopped and is no longer listening for requests 775 | * @param activeInstance The active The active instance of ambience 776 | */ 777 | public void ambienceServiceStopped(Ambience activeInstance); 778 | } 779 | } 780 | -------------------------------------------------------------------------------- /ambience/src/main/java/com/tonyostudios/ambience/AmbientService.java: -------------------------------------------------------------------------------- 1 | package com.tonyostudios.ambience; 2 | 3 | import android.app.PendingIntent; 4 | import android.app.Service; 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.IntentFilter; 9 | import android.graphics.Bitmap; 10 | import android.graphics.drawable.BitmapDrawable; 11 | import android.graphics.drawable.Drawable; 12 | import android.media.AudioManager; 13 | import android.media.MediaPlayer; 14 | import android.net.wifi.WifiManager; 15 | import android.os.Bundle; 16 | import android.os.Handler; 17 | import android.os.IBinder; 18 | import android.os.Parcelable; 19 | import android.os.PowerManager; 20 | import android.support.v4.app.NotificationCompat; 21 | import android.support.v4.app.NotificationManagerCompat; 22 | import android.util.Log; 23 | 24 | import com.squareup.picasso.Picasso; 25 | import com.squareup.picasso.Target; 26 | 27 | 28 | 29 | import java.util.ArrayList; 30 | import java.util.Collections; 31 | 32 | /** 33 | * AmbientService is an Android Service that is used to control media playback 34 | * for audio. Ambient Service is advanced and has all basic audio control 35 | * types like play,pause,stop,resume,previous,skip, shuffle and repeat. The 36 | * Service also creates A notification in the Notification Drawer to control playback. 37 | * Use the Ambience Class to communicate with the AmbientService 38 | * @author TonyoStudios.com. Created on 11/18/2014. Updated on 12/01/2014. 39 | * @version 1.3 40 | * 41 | */ 42 | public class AmbientService extends Service implements MediaPlayer.OnErrorListener, 43 | MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener, 44 | AudioManager.OnAudioFocusChangeListener { 45 | 46 | 47 | /** 48 | * Tag used to identify the AmbientService 49 | */ 50 | public final static String TAG = "com.tonyostudios.ambience.AmbientService"; 51 | 52 | /** 53 | * Tag used to identify the AmbientService incoming broadcast receiver 54 | */ 55 | public final static String AMBIENT_SERVICE_BROADCASTER = TAG + ".INCOMING_REQUEST_BROADCASTER"; 56 | 57 | /** 58 | * Tag used to identify the AmbientService playlist 59 | */ 60 | public final static String PLAYLIST = TAG + ".PLAYLIST"; 61 | 62 | /** 63 | * Tag used to identify the AmbientService play position 64 | */ 65 | public final static String PLAY_POSITION = TAG + ".PLAY_POSITION"; 66 | 67 | /** 68 | * Tag used to identify the AmbientService volume level 69 | */ 70 | public final static String VOLUME_LEVEL = TAG + ".VOLUME_VALUE"; 71 | 72 | /** 73 | * Tag used to identify the AmbientService seek position 74 | */ 75 | public final static String SEEK_POSITION = TAG + ".SEEK_POSITION"; 76 | 77 | /** 78 | * Tag used to identify the AmbientService playback state 79 | */ 80 | public final static String PLAYBACK_STATE = TAG + ".PLAYBACK_STATE"; 81 | 82 | /** 83 | * Tag used to identify the AmbientService repeat mode 84 | */ 85 | public final static String REPEAT_MODE = TAG + ".REPEAT_MODE"; 86 | 87 | /** 88 | * Tag used to identify a track added to the AmbientService playlist 89 | */ 90 | public final static String ADD_TRACK = TAG + ".ADD_TRACK"; 91 | 92 | /** 93 | * Tag used to identify a track that needs to be removed from the the AmbientService playlist 94 | */ 95 | public final static String REMOVE_TRACK = TAG + ".REMOVE_TRACK"; 96 | 97 | /** 98 | * Tag used to identify the AmbientService shuffle mode 99 | */ 100 | public final static String SHUFFLE_MODE = TAG + ".SHUFFLE_MODE"; 101 | 102 | /** 103 | * Tag used to identify the AmbientService launch activity for notifications 104 | */ 105 | public final static String ACTIVITY_LAUNCHER = TAG + ".ACTIVITY_LAUNCHER"; 106 | 107 | /** 108 | * Tag used to identify the AmbientService progress track 109 | */ 110 | public final static String TRACK_PROGRESS = TAG + ".TRACK_PROGRESS"; 111 | 112 | /** 113 | * Tag used to identify the AmbientService track duration 114 | */ 115 | public final static String TRACK_DURATION = TAG + ".TRACK_DURATION"; 116 | 117 | /** 118 | * Tag used to identify the AmbientService current playing track 119 | */ 120 | public final static String CURRENT_TRACK = TAG + ".CURRENT_PLAYING_TRACK"; 121 | 122 | /** 123 | * Value used to update the handler/user interface 124 | */ 125 | public final static int AUDIO_PROGRESS_UPDATE_TIME = 100; 126 | 127 | /** 128 | * Tag used to identify the AmbientService notification ID 129 | */ 130 | private final int NOTIFICATION_CONTROL_ID = 101; 131 | 132 | 133 | /** 134 | * AmbientService shuffle modes 135 | */ 136 | public static enum ShuffleMode 137 | { 138 | ON, 139 | OFF 140 | } 141 | 142 | /** 143 | * AmbientService repeat modes 144 | */ 145 | public static enum RepeatMode 146 | { 147 | REPEAT_ONE, 148 | REPEAT_ALL, 149 | ShuffleMode, RepeatMode, OFF 150 | } 151 | 152 | /** 153 | * AmbientService playback states 154 | */ 155 | public static enum PlaybackState 156 | { 157 | PLAY, 158 | STOP, 159 | PAUSE, 160 | RESUME, 161 | SKIP, 162 | PREVIOUS, 163 | PREPPING_TRACK, 164 | END_OF_PLAYLIST, 165 | CURRENT_PLAYING_TRACK_INFO, 166 | ERROR, 167 | SERVICE_STARTED, 168 | SERVICE_STOPPED 169 | } 170 | 171 | /** 172 | * Media player used by the service to play AmbientTracks 173 | */ 174 | private MediaPlayer mPlayer; 175 | 176 | /** 177 | * Handler used to communicate updates to the UI thread 178 | */ 179 | private Handler mHandler; 180 | 181 | /** 182 | * Allows service to keep the wifi-radio on when needed 183 | */ 184 | private WifiManager.WifiLock mWifiLock; 185 | 186 | /** 187 | * Provides access to volume and ringer controls 188 | */ 189 | private AudioManager mAudioManager; 190 | 191 | /** 192 | * Compat notification manager 193 | */ 194 | private NotificationManagerCompat mNotificationManager; 195 | 196 | /** 197 | * ArrayList used to hold the original playlist before mPlaylist is shuffled 198 | */ 199 | private ArrayList mOriginalPlaylist = new ArrayList(); 200 | 201 | /** 202 | * ArrayList used to manage AmbientTracks sent to the AmbientService for processing 203 | */ 204 | private ArrayList mPlaylist = new ArrayList(); 205 | 206 | /** 207 | * Holds the current playing AmbientTrack 208 | */ 209 | private AmbientTrack mAmbientTrack; 210 | 211 | /** 212 | * The current position of the current track in the playlist 213 | */ 214 | private int playPosition = 0; 215 | 216 | 217 | /** 218 | * Holds the intent filter action name used to launch an activity when a notification is tapped 219 | * in the navigation drawer. 220 | */ 221 | private String mActivityLauncher; 222 | 223 | /** 224 | * The bundle passed to the IncomingRequestReceiver. This bundle may contain 225 | * data or actions requested by the Ambience Class 226 | */ 227 | private Bundle mBundle; 228 | 229 | /** 230 | * Holds the current repeat mode for the playlist 231 | */ 232 | private RepeatMode mRepeatMode = RepeatMode.OFF; 233 | 234 | /** 235 | * Holds the current shuffle mode for the playlist 236 | */ 237 | private ShuffleMode mShuffleState = ShuffleMode.OFF; 238 | 239 | 240 | /** 241 | * Holds the current volume level of the media player 242 | */ 243 | private float mVolume = 0.5f; 244 | 245 | 246 | 247 | /** 248 | * Method used to bind a service to an Android Component such as an activity 249 | * @param intent intent object 250 | * @return binder object 251 | * @see "http://developer.android.com/guide/components/bound-services.html" 252 | */ 253 | @Override 254 | public IBinder onBind(Intent intent) { 255 | return null; 256 | } 257 | 258 | /** 259 | * Called by the system every time a client explicitly starts the service by calling 260 | * startService(Intent), providing the arguments it supplied and a unique integer token 261 | * representing the start request. The Ambient Service is sticky by default. 262 | * @param intent The Intent supplied to startService(Intent), as given. 263 | * @param flags Additional data about the request 264 | * @param startId A unique integer representing this specific request to start. 265 | * @return The return value indicates what semantics the system should use for the service's 266 | * current started state. 267 | */ 268 | @Override 269 | public int onStartCommand(Intent intent, int flags, int startId) { 270 | return START_STICKY; 271 | } 272 | 273 | /** 274 | * The IncomingRequestBroadcast Receiver responds to all request made to the Ambient Service. 275 | * It is used to traffic all specific request to their intended route. 276 | */ 277 | private BroadcastReceiver IncomingRequestReceiver = new BroadcastReceiver() { 278 | 279 | /** 280 | * Method used to handle the incoming request receiver intent 281 | * @param context Context Object 282 | * @param intent intent object 283 | */ 284 | @Override 285 | public void onReceive(Context context, Intent intent) { 286 | 287 | if (intent == null || intent.getExtras() == null) { 288 | Log.e(TAG, "Passed an empty intent to IncomingRequestBroadcaster"); 289 | return; 290 | } 291 | 292 | mBundle = intent.getExtras(); 293 | 294 | 295 | if(mBundle.containsKey(ACTIVITY_LAUNCHER)) 296 | { 297 | mActivityLauncher = mBundle.getString(ACTIVITY_LAUNCHER); 298 | } 299 | 300 | if(mBundle.containsKey(VOLUME_LEVEL)) 301 | { 302 | setVolumeTo(mBundle.getFloat(VOLUME_LEVEL,0.5f)); 303 | } 304 | 305 | if(mBundle.containsKey(PLAYLIST)) 306 | { 307 | createPlaylist(); 308 | } 309 | 310 | if(mBundle.containsKey(PLAY_POSITION)) 311 | { 312 | setPlayPosition(); 313 | } 314 | 315 | if(mBundle.containsKey(REMOVE_TRACK)) 316 | { 317 | removeTrackFromPlaylist(); 318 | } 319 | 320 | if(mBundle.containsKey(ADD_TRACK)) 321 | { 322 | addTrackToPlaylist(); 323 | } 324 | 325 | if(mBundle.containsKey(REPEAT_MODE)) 326 | { 327 | setRepeatMode(); 328 | } 329 | 330 | if(mBundle.containsKey(SHUFFLE_MODE)) 331 | { 332 | setShuffleMode(); 333 | } 334 | 335 | if(mBundle.containsKey(SEEK_POSITION)) 336 | { 337 | seekTo(mBundle.getInt(SEEK_POSITION,0)); 338 | } 339 | 340 | 341 | if(mBundle.containsKey(PLAYBACK_STATE)) 342 | { 343 | 344 | try { 345 | PlaybackState state = (PlaybackState) mBundle.getSerializable(PLAYBACK_STATE); 346 | 347 | //PLAYBACK CONTROLS 348 | switch (state) { 349 | case PLAY: init(); 350 | break; 351 | case STOP: stop(); 352 | break; 353 | case PAUSE: pause(); 354 | break; 355 | case RESUME: play(); 356 | break; 357 | case SKIP: playNext(); 358 | break; 359 | case PREVIOUS: playPrevious(); 360 | break; 361 | default: 362 | throw new IllegalStateException(TAG + ": Unknown Playback State"); 363 | } 364 | } catch (Exception e) { 365 | e.printStackTrace(); 366 | Log.e(TAG, e.getMessage()); 367 | } 368 | } 369 | } 370 | }; 371 | 372 | /** 373 | * Called by the system when the service is first created. 374 | */ 375 | @Override 376 | public void onCreate() { 377 | super.onCreate(); 378 | 379 | createMediaPlayer(); 380 | mHandler = new Handler(); 381 | 382 | //get handle on audio manager, wifi lock and notification manager 383 | mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 384 | mWifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)) 385 | .createWifiLock(WifiManager.WIFI_MODE_FULL,TAG); 386 | 387 | 388 | mNotificationManager = NotificationManagerCompat.from(AmbientService.this); 389 | 390 | //register the incoming request receiver 391 | IntentFilter filter = new IntentFilter(AMBIENT_SERVICE_BROADCASTER); 392 | registerReceiver(IncomingRequestReceiver,filter); 393 | 394 | sendUpdateBroadcast(PlaybackState.SERVICE_STARTED); 395 | } 396 | 397 | /** 398 | * resets and create a new media player object 399 | */ 400 | private void createMediaPlayer() 401 | { 402 | if(mPlayer != null) 403 | { 404 | try 405 | { 406 | mPlayer.release(); 407 | }catch (Exception e) 408 | { 409 | e.printStackTrace(); 410 | Log.e(TAG, e.getMessage()); 411 | } 412 | 413 | mPlayer = null; 414 | } 415 | 416 | mPlayer = new MediaPlayer(); 417 | 418 | //Set listeners on media player 419 | mPlayer.setOnCompletionListener(this); 420 | mPlayer.setOnPreparedListener(this); 421 | mPlayer.setOnErrorListener(this); 422 | 423 | ///set wake-lock mode for media player 424 | mPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); 425 | } 426 | 427 | /** 428 | * Method used to added the passed AmbientPlaylist to the queue 429 | */ 430 | private void createPlaylist() 431 | { 432 | if( mBundle == null || !mBundle.containsKey(PLAYLIST) 433 | || mBundle.getParcelableArrayList(PLAYLIST) == null) 434 | { 435 | throw new IllegalStateException(TAG + ": needs at least one AmbientTrack item to play"); 436 | } 437 | 438 | if(mOriginalPlaylist != null) 439 | { 440 | mOriginalPlaylist.clear(); 441 | } 442 | else 443 | { 444 | mOriginalPlaylist = new ArrayList(); 445 | } 446 | 447 | 448 | if(mPlaylist == null) 449 | { 450 | mPlaylist = new ArrayList(); 451 | } 452 | 453 | ArrayList newTracks = mBundle.getParcelableArrayList(PLAYLIST); 454 | 455 | for(int j = 0; j < newTracks.size(); j++) 456 | { 457 | mPlaylist.add((AmbientTrack)newTracks.get(j)); 458 | } 459 | 460 | 461 | //Copy position to maintain shuffle & un-shuffle state 462 | for(int x = 0; x < mPlaylist.size(); x++) 463 | { 464 | mOriginalPlaylist.add(mPlaylist.get(x)); 465 | } 466 | 467 | playPosition = 0; 468 | 469 | } 470 | 471 | /** 472 | * Called to remove a track from the current playlist 473 | */ 474 | private void removeTrackFromPlaylist() 475 | { 476 | if(mBundle.getParcelable(REMOVE_TRACK) != null && mOriginalPlaylist != null 477 | && mPlaylist != null) 478 | { 479 | mOriginalPlaylist.remove((AmbientTrack)mBundle.getParcelable(REMOVE_TRACK)); 480 | mPlaylist.remove((AmbientTrack)mBundle.getParcelable(REMOVE_TRACK)); 481 | } 482 | } 483 | 484 | /** 485 | * Called to append a track to the current playlist 486 | */ 487 | private void addTrackToPlaylist() 488 | { 489 | if(mBundle.getParcelable(ADD_TRACK) != null && mOriginalPlaylist != null 490 | && mPlaylist != null) 491 | { 492 | mOriginalPlaylist.add((AmbientTrack)mBundle.getParcelable(ADD_TRACK)); 493 | mPlaylist.add((AmbientTrack)mBundle.getParcelable(ADD_TRACK)); 494 | 495 | if(mShuffleState == ShuffleMode.ON && !mBundle.containsKey(SHUFFLE_MODE)) 496 | { 497 | toggleShuffle(); //If shuffle is on reshuffle track 498 | } 499 | } 500 | } 501 | 502 | /** 503 | * Sets the repeat mode for the Ambient Playlist 504 | */ 505 | private void setRepeatMode() 506 | { 507 | if(mBundle == null || !mBundle.containsKey(REPEAT_MODE) 508 | ||mBundle.getSerializable(REPEAT_MODE) == null ) 509 | { 510 | Log.e(TAG,": No valid repeat mode"); 511 | return; 512 | } 513 | 514 | mRepeatMode = (RepeatMode) mBundle.getSerializable(REPEAT_MODE); 515 | } 516 | 517 | /** 518 | * Sets the shuffle mode for the Ambient Playlist 519 | */ 520 | private void setShuffleMode() 521 | { 522 | if(mBundle == null || !mBundle.containsKey(SHUFFLE_MODE) 523 | ||mBundle.getSerializable(SHUFFLE_MODE) == null ) 524 | { 525 | Log.e(TAG,"No valid shuffle mode"); 526 | return; 527 | } 528 | 529 | mShuffleState = (ShuffleMode) mBundle.getSerializable(SHUFFLE_MODE); 530 | 531 | toggleShuffle(); 532 | } 533 | 534 | /** 535 | * Helper method used to toggle the shuffle state of the Ambient Playlist 536 | */ 537 | private void toggleShuffle() 538 | { 539 | if(mPlaylist != null && mOriginalPlaylist != null) 540 | { 541 | if(mShuffleState == ShuffleMode.ON) 542 | { 543 | Collections.shuffle(mPlaylist); 544 | setCurrentAmbientTrackPosition(); 545 | 546 | }else 547 | { 548 | mPlaylist.clear(); 549 | 550 | for(int x = 0; x < mOriginalPlaylist.size(); x++) 551 | { 552 | mPlaylist.add(mOriginalPlaylist.get(x)); 553 | } 554 | 555 | setCurrentAmbientTrackPosition(); 556 | } 557 | } 558 | } 559 | 560 | /** 561 | * Helper method used to get the position of the current playing AmbientTrack 562 | * before and after shuffle 563 | */ 564 | private void setCurrentAmbientTrackPosition() 565 | { 566 | if(mPlaylist != null) 567 | { 568 | for(int x = 0; x < mPlaylist.size(); x++) 569 | { 570 | if(mPlaylist.get(x) == mAmbientTrack) 571 | { 572 | playPosition = x; 573 | break; 574 | } 575 | } 576 | } 577 | } 578 | 579 | /** 580 | * Sets the play position of an Ambient track from the Ambient Playlist 581 | */ 582 | private void setPlayPosition() 583 | { 584 | if( mBundle == null || !mBundle.containsKey(PLAY_POSITION)) 585 | { 586 | throw new IllegalStateException(TAG + ": invalid play position"); 587 | } 588 | 589 | playPosition = mBundle.getInt(PLAY_POSITION,0); 590 | 591 | if(playPosition < 0 || playPosition >= mPlaylist.size()) 592 | { 593 | playPosition = 0; 594 | } 595 | } 596 | 597 | /** 598 | * Alerts the AmbientService about audio focus gain, 599 | * loss, etc. 600 | * @param focusChange value of focus changed 601 | */ 602 | @Override 603 | public void onAudioFocusChange(int focusChange) { 604 | if(mPlayer == null) 605 | { 606 | return; 607 | } 608 | 609 | switch (focusChange) { 610 | case AudioManager.AUDIOFOCUS_GAIN: 611 | 612 | if (!mPlayer.isPlaying()) 613 | { 614 | play(); 615 | } 616 | 617 | mPlayer.setVolume(mVolume,mVolume); 618 | break; 619 | 620 | case AudioManager.AUDIOFOCUS_LOSS: 621 | // Lost focus for an unbounded amount of time: stop playback and release media player 622 | if (mPlayer.isPlaying()) 623 | { 624 | pause(); 625 | } 626 | 627 | break; 628 | 629 | case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 630 | // Lost focus for a short time, but we have to stop 631 | // playback. We don't release the media player because playback 632 | // is likely to resume 633 | if (mPlayer.isPlaying()) 634 | { 635 | pause(); 636 | } 637 | 638 | break; 639 | 640 | case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 641 | // Lost focus for a short time, but it's ok to keep playing 642 | // at an attenuated level 643 | if (mPlayer.isPlaying()) 644 | { 645 | mPlayer.setVolume(0.1f, 0.1f); 646 | } 647 | 648 | break; 649 | } 650 | } 651 | 652 | /** 653 | * Initialize and prepare the media player with the ambient track 654 | */ 655 | private void init() 656 | { 657 | sendUpdateBroadcast(PlaybackState.PREPPING_TRACK); // send prepping update to callback 658 | 659 | if(mPlayer == null) 660 | { 661 | createMediaPlayer(); 662 | } 663 | 664 | try { 665 | 666 | if(mPlaylist == null || mPlaylist.get(playPosition) == null ) 667 | { 668 | Log.e(TAG, ": The AmbientTrack item was null. Check the quality of your playlist before" + 669 | " passing it to the AmbientService."); 670 | return; 671 | } 672 | 673 | mAmbientTrack = mPlaylist.get(playPosition); 674 | 675 | 676 | if(mPlayer.isPlaying()) 677 | { 678 | mPlayer.stop(); 679 | } 680 | mPlayer.reset(); 681 | 682 | mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); 683 | mPlayer.setDataSource(this, mAmbientTrack.getAudioUri()); // set audio source 684 | 685 | mPlayer.prepareAsync(); 686 | }catch (Exception e) 687 | { 688 | e.printStackTrace(); 689 | Log.e(TAG, e.getMessage()); 690 | } 691 | } 692 | 693 | /** 694 | * Plays the AmbientTrack 695 | */ 696 | private void play() 697 | { 698 | try 699 | { 700 | int result = mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN); 701 | 702 | if (result != AudioManager.AUDIOFOCUS_GAIN) 703 | { 704 | Log.i(TAG, ": could not get audio focus from manager"); 705 | } 706 | 707 | mWifiLock.acquire(); 708 | 709 | if(mPlayer != null) 710 | { 711 | mPlayer.start(); 712 | } 713 | 714 | if(mHandler != null) 715 | { 716 | mHandler.postDelayed(mUpdateProgress, AUDIO_PROGRESS_UPDATE_TIME); 717 | } 718 | 719 | sendUpdateBroadcast(PlaybackState.PLAY); // sends a now playing update to the callback 720 | createNotification(); 721 | }catch (Exception e) 722 | { 723 | e.printStackTrace(); 724 | Log.e(TAG,e.getMessage()); 725 | } 726 | } 727 | 728 | /** 729 | * Stops the current playing AmbientTrack 730 | */ 731 | private void stop() 732 | { 733 | try 734 | { 735 | mAudioManager.abandonAudioFocus(this); 736 | mWifiLock.release(); 737 | 738 | if(mPlayer != null && mPlayer.isPlaying()) 739 | { 740 | mPlayer.stop(); 741 | } 742 | sendUpdateBroadcast(PlaybackState.STOP); // sends a track has stopped update to the callback 743 | }catch (Exception e) 744 | { 745 | e.printStackTrace(); 746 | Log.e(TAG, e.getMessage()); 747 | } 748 | } 749 | 750 | /** 751 | * Pause the current playing AmbientTrack 752 | */ 753 | private void pause() 754 | { 755 | try 756 | { 757 | mAudioManager.abandonAudioFocus(this); 758 | mWifiLock.release(); 759 | 760 | if(mPlayer != null && mPlayer.isPlaying()) 761 | { 762 | mPlayer.pause(); 763 | } 764 | 765 | sendUpdateBroadcast(PlaybackState.PAUSE); // sends a track has paused update to the callback 766 | createNotification(); 767 | }catch (Exception e) 768 | { 769 | e.printStackTrace(); 770 | Log.e(TAG, e.getMessage()); 771 | } 772 | } 773 | 774 | /** 775 | * Method used to play the previous AmbientTrack in the playlist 776 | */ 777 | private void playPrevious() 778 | { 779 | if(mPlayer != null && mPlayer.isPlaying()) 780 | { 781 | stop(); 782 | } 783 | 784 | if(mPlaylist != null) 785 | { 786 | --playPosition; 787 | 788 | if(playPosition < 0 ) 789 | { 790 | playPosition = mPlaylist.size() - 1; 791 | } 792 | 793 | init(); 794 | } 795 | } 796 | 797 | /** 798 | * Method used to play the next AmbientTrack in the playlist 799 | */ 800 | private void playNext() 801 | { 802 | if(mPlayer != null && mPlayer.isPlaying()) 803 | { 804 | stop(); 805 | } 806 | 807 | if(mPlaylist != null) 808 | { 809 | ++playPosition; 810 | 811 | if(playPosition >= mPlaylist.size()) 812 | { 813 | sendUpdateBroadcast(PlaybackState.END_OF_PLAYLIST); // send end of playlist update to callback 814 | playPosition = 0; 815 | } 816 | 817 | init(); 818 | } 819 | } 820 | 821 | /** 822 | * Runnable object used to update the main user interface with playback values 823 | */ 824 | private Runnable mUpdateProgress = new Runnable() { 825 | public void run() { 826 | 827 | if (mHandler != null && mPlayer != null && mPlayer.isPlaying()) { 828 | 829 | int position = mPlayer.getCurrentPosition(); 830 | int totalTime = mPlayer.getDuration(); 831 | 832 | //Send bundle with all track information to callback 833 | Bundle bundle = new Bundle(); 834 | bundle.putSerializable(PLAYBACK_STATE,PlaybackState.CURRENT_PLAYING_TRACK_INFO); 835 | 836 | bundle.putInt(TRACK_PROGRESS,position); 837 | bundle.putParcelable(CURRENT_TRACK,mAmbientTrack); 838 | bundle.putInt(TRACK_DURATION,totalTime); 839 | 840 | sendUpdateBroadcast(bundle); 841 | 842 | 843 | mHandler.postDelayed(this,AUDIO_PROGRESS_UPDATE_TIME); 844 | } 845 | } 846 | }; 847 | 848 | 849 | /** 850 | * Alerts the AmbientService when an AmbientTrack is 851 | * done playing. 852 | * @param mp Media Player object 853 | */ 854 | @Override 855 | public void onCompletion(MediaPlayer mp) { 856 | 857 | 858 | sendUpdateBroadcast(PlaybackState.STOP); //send a stop update to the callback 859 | 860 | 861 | if(mNotificationManager != null) 862 | { 863 | mNotificationManager.cancel(NOTIFICATION_CONTROL_ID); 864 | } 865 | 866 | if(mRepeatMode != null) 867 | { 868 | if(mRepeatMode == RepeatMode.REPEAT_ALL) 869 | { 870 | playNext(); 871 | }else if(mRepeatMode == RepeatMode.REPEAT_ONE) 872 | { 873 | play(); 874 | } 875 | } 876 | 877 | } 878 | 879 | /** 880 | * Method used to alert the AmbientService that an error 881 | * has occurred with the Media Player 882 | * @param mp Media Player object 883 | * @param what What error occurred 884 | * @param extra Extra error information about the error 885 | * @return The success of the error handling 886 | */ 887 | @Override 888 | public boolean onError(MediaPlayer mp, int what, int extra) { 889 | String errorMessage = TAG + ".Error - What: \" + what + \", extra: \" + extra"; 890 | 891 | Log.e(TAG, errorMessage); 892 | 893 | createMediaPlayer(); // reset media player to original state 894 | 895 | sendUpdateBroadcast(PlaybackState.ERROR); // send error update to the callback 896 | 897 | if(mNotificationManager != null) 898 | { 899 | mNotificationManager.cancel(NOTIFICATION_CONTROL_ID); 900 | } 901 | 902 | return false; 903 | } 904 | 905 | /** 906 | * Alters the AmbientService when the media player has prepared an AmbientTrack 907 | * and is ready for play. 908 | * @param mp media player object 909 | */ 910 | @Override 911 | public void onPrepared(MediaPlayer mp) { 912 | play(); 913 | } 914 | 915 | /** 916 | * Sets the volume level of the media player 917 | * @param volumeLevel volume level 918 | */ 919 | private void setVolumeTo(float volumeLevel) { 920 | 921 | if(mPlayer != null) 922 | { 923 | if(volumeLevel < 0.0f || volumeLevel > 1.0f ) 924 | { 925 | volumeLevel = 0.5f; 926 | } 927 | 928 | mVolume = volumeLevel; 929 | 930 | mPlayer.setVolume(mVolume,mVolume); 931 | } 932 | } 933 | 934 | /** 935 | * Set the seek position of the current AmbientTrack 936 | */ 937 | private void seekTo(int position) 938 | { 939 | if(mPlayer != null && position >= 0 && position <= mPlayer.getDuration()) 940 | { 941 | mPlayer.seekTo(position); 942 | } 943 | } 944 | 945 | /** 946 | * Method used to send an intent to the Ambience Broadcast Receiver to update the callback 947 | * component. 948 | * @param bundle A bundle object containing the current AmbientTrack component 949 | */ 950 | private void sendUpdateBroadcast(Bundle bundle) 951 | { 952 | Intent intent = new Intent(Ambience.AMBIENCE_BROADCASTER); 953 | intent.putExtras(bundle); 954 | sendBroadcast(intent); 955 | } 956 | 957 | /** 958 | * Method used to send an intent to the Ambience Broadcast Receiver to update the callback 959 | * component. 960 | * @param value Playback state of the Current AmbientTrack 961 | */ 962 | private void sendUpdateBroadcast(PlaybackState value) 963 | { 964 | Intent intent = new Intent(Ambience.AMBIENCE_BROADCASTER); 965 | intent.putExtra(PLAYBACK_STATE,value); 966 | sendBroadcast(intent); 967 | } 968 | 969 | 970 | /** 971 | * Method used to bundle the Ambient Service current information 972 | * @return A bundle containing the ambientService current playing track, 973 | * playlist, shuffle mode, repeat mode, volume level and action launcher string 974 | * 975 | */ 976 | private Bundle buildAmbientServiceBundle() 977 | { 978 | Bundle bundle = new Bundle(); 979 | 980 | if(mActivityLauncher != null) 981 | { 982 | bundle.putString(ACTIVITY_LAUNCHER,mActivityLauncher); 983 | } 984 | 985 | if(mAmbientTrack != null) 986 | { 987 | bundle.putParcelable(CURRENT_TRACK,mAmbientTrack); 988 | } 989 | 990 | 991 | if(mRepeatMode != null) 992 | { 993 | bundle.putSerializable(REPEAT_MODE,mRepeatMode); 994 | } 995 | 996 | if(mShuffleState != null) 997 | { 998 | bundle.putSerializable(SHUFFLE_MODE,mShuffleState); 999 | } 1000 | 1001 | if(mPlaylist != null) 1002 | { 1003 | bundle.putParcelableArrayList(PLAYLIST,mPlaylist); 1004 | } 1005 | 1006 | 1007 | bundle.putInt(PLAY_POSITION,playPosition); 1008 | 1009 | bundle.putFloat(VOLUME_LEVEL,mVolume); 1010 | 1011 | return bundle; 1012 | } 1013 | 1014 | /** 1015 | * Called before the service terminates. All resources used by the AmbientService are 1016 | * cleaned up in this method. 1017 | */ 1018 | @Override 1019 | public void onDestroy() { 1020 | super.onDestroy(); 1021 | 1022 | /* 1023 | * RELEASE ALL AMBIENT SERVICE RESOURCES 1024 | */ 1025 | if(mPlayer != null) 1026 | { 1027 | try 1028 | { 1029 | if(mPlayer.isPlaying()) 1030 | { 1031 | stop(); 1032 | } 1033 | 1034 | mPlayer.release(); 1035 | mPlayer = null; 1036 | }catch (Exception e) 1037 | { 1038 | e.printStackTrace(); 1039 | Log.e(TAG, e.getMessage()); 1040 | } 1041 | } 1042 | 1043 | mHandler = null; 1044 | mAudioManager = null; 1045 | 1046 | try 1047 | { 1048 | mWifiLock.release(); 1049 | }catch (Exception e) 1050 | { 1051 | e.printStackTrace(); 1052 | Log.e(TAG,e.getMessage()); 1053 | } 1054 | 1055 | mWifiLock = null; 1056 | mBundle = null; 1057 | 1058 | if(mOriginalPlaylist != null) 1059 | { 1060 | mOriginalPlaylist.clear(); 1061 | } 1062 | 1063 | if(mPlaylist != null) 1064 | { 1065 | mPlaylist.clear(); 1066 | } 1067 | 1068 | mPlaylist = null; 1069 | mOriginalPlaylist = null; 1070 | mActivityLauncher = null; 1071 | mAmbientTrack = null; 1072 | mVolume = 0.5f; 1073 | playPosition = 0; 1074 | 1075 | 1076 | if(mNotificationManager != null) 1077 | { 1078 | mNotificationManager.cancel(NOTIFICATION_CONTROL_ID); 1079 | mNotificationManager = null; 1080 | } 1081 | 1082 | unregisterReceiver(IncomingRequestReceiver); 1083 | sendUpdateBroadcast(PlaybackState.SERVICE_STOPPED); 1084 | } 1085 | 1086 | /** 1087 | * Creates a notification for the AmbientTrack in the Notification Drawer. 1088 | * The notification contains the track Image, track name, and the album the 1089 | * track belongs to. Also, the notification contains media playback controls 1090 | * and can launch an action. 1091 | * 1092 | * A bundle object is appended to the notification pendingIntent. This bundle contains 1093 | * the ambientService current playing track, playlist, shuffle mode, repeat mode, 1094 | * volume level and action launcher string. A user app can use this information to restore 1095 | * state. 1096 | * 1097 | * The AmbientService uses the Picasso Library 1098 | * by Square to get the album image from memory, cache or internet. 1099 | * @see "http://square.github.io/picasso/" 1100 | */ 1101 | public void createNotification() 1102 | { 1103 | if(mAmbientTrack == null) 1104 | { 1105 | Log.e(TAG,": AmbientTrack is null. Cannot create a notification"); 1106 | return; 1107 | } 1108 | 1109 | final Drawable placeholderDrawable = getResources().getDrawable(R.drawable.unknown_album); 1110 | 1111 | try { 1112 | 1113 | Picasso.with(this).load(mAmbientTrack.getAlbumImageUri()) 1114 | .placeholder(placeholderDrawable) 1115 | .error(placeholderDrawable) 1116 | .into(new Target() { 1117 | @Override 1118 | public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom loadedFrom) { 1119 | 1120 | if(bitmap == null) 1121 | { 1122 | bitmap = ((BitmapDrawable) placeholderDrawable).getBitmap(); 1123 | } 1124 | 1125 | buildNotification(bitmap); 1126 | } 1127 | 1128 | @Override 1129 | public void onBitmapFailed(Drawable drawable) { 1130 | 1131 | if(drawable == null) 1132 | { 1133 | drawable = placeholderDrawable; 1134 | } 1135 | 1136 | buildNotification(((BitmapDrawable) drawable).getBitmap()); 1137 | } 1138 | 1139 | @Override 1140 | public void onPrepareLoad(Drawable drawable) { 1141 | 1142 | if(drawable == null) 1143 | { 1144 | drawable = placeholderDrawable; 1145 | } 1146 | 1147 | buildNotification(((BitmapDrawable) drawable).getBitmap()); 1148 | } 1149 | }); 1150 | }catch (Exception e) 1151 | { 1152 | e.printStackTrace(); 1153 | Log.e(TAG,e.getMessage()); 1154 | 1155 | buildNotification(((BitmapDrawable) placeholderDrawable).getBitmap()); 1156 | } 1157 | 1158 | } 1159 | 1160 | /** 1161 | * Helper method used to build a notification. Notifications are compatible with Android wearables 1162 | * @param bitmap the AmbientTracks album cover bitmap 1163 | */ 1164 | private void buildNotification(Bitmap bitmap) 1165 | { 1166 | if(mNotificationManager == null) 1167 | { 1168 | mNotificationManager = NotificationManagerCompat.from(this); 1169 | } 1170 | 1171 | NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender(); 1172 | 1173 | 1174 | NotificationCompat.Builder builder = new NotificationCompat.Builder(this) 1175 | .setSmallIcon(R.drawable.app_icon); //replace with app logo by naming your app logo app_icon 1176 | 1177 | 1178 | 1179 | //Set activity to launch from notification drawer 1180 | if(mActivityLauncher != null) 1181 | { 1182 | Intent activityIntent = new Intent(); 1183 | activityIntent.setAction(mActivityLauncher); 1184 | activityIntent.putExtras(buildAmbientServiceBundle()); // pass ambient service state 1185 | 1186 | PendingIntent activityPending = PendingIntent.getActivity(AmbientService.this,800, 1187 | activityIntent,PendingIntent.FLAG_UPDATE_CURRENT); 1188 | 1189 | builder.setContentIntent(activityPending); 1190 | } 1191 | 1192 | if(mAmbientTrack.getName() != null && !mAmbientTrack.getName().equals("")) 1193 | { 1194 | builder.setContentTitle(mAmbientTrack.getName()); 1195 | 1196 | } 1197 | else 1198 | { 1199 | builder.setContentTitle(getString(R.string.unknown_track)); 1200 | } 1201 | 1202 | 1203 | if(mAmbientTrack.getAlbumName() != null && !mAmbientTrack.getAlbumName().equals("")) 1204 | { 1205 | builder.setContentText(mAmbientTrack.getAlbumName()); 1206 | } 1207 | else 1208 | { 1209 | builder.setContentText(getString(R.string.unknown_artist)); 1210 | } 1211 | 1212 | builder.setLargeIcon(bitmap); 1213 | 1214 | 1215 | Intent previousIntent = new Intent(AMBIENT_SERVICE_BROADCASTER); 1216 | previousIntent.putExtra(PLAYBACK_STATE,PlaybackState.PREVIOUS); 1217 | int drawablePrevious = android.R.drawable.ic_media_previous; 1218 | 1219 | PendingIntent PrevIntent = PendingIntent.getBroadcast(AmbientService.this, 1220 | 801,previousIntent,PendingIntent.FLAG_UPDATE_CURRENT); 1221 | 1222 | 1223 | 1224 | Intent playIntent = new Intent(AMBIENT_SERVICE_BROADCASTER); 1225 | 1226 | 1227 | int drawableId = android.R.drawable.ic_media_pause; 1228 | 1229 | 1230 | // SONG IS PLAYING SET INTENT TO PAUSE 1231 | if(mPlayer.isPlaying()) 1232 | { 1233 | playIntent.putExtra(PLAYBACK_STATE,PlaybackState.PAUSE); 1234 | 1235 | } 1236 | else 1237 | { 1238 | playIntent.putExtra(PLAYBACK_STATE,PlaybackState.RESUME); 1239 | drawableId = android.R.drawable.ic_media_play; 1240 | } 1241 | 1242 | 1243 | PendingIntent pIntent = PendingIntent.getBroadcast(AmbientService.this,802, 1244 | playIntent,PendingIntent.FLAG_UPDATE_CURRENT); 1245 | 1246 | 1247 | 1248 | 1249 | Intent forwardIntent = new Intent(AMBIENT_SERVICE_BROADCASTER); 1250 | forwardIntent.putExtra(PLAYBACK_STATE,PlaybackState.SKIP); 1251 | int drawableForward = android.R.drawable.ic_media_next; 1252 | 1253 | PendingIntent NextIntent = PendingIntent.getBroadcast(AmbientService.this,803, 1254 | forwardIntent,PendingIntent.FLAG_UPDATE_CURRENT); 1255 | 1256 | 1257 | 1258 | builder.addAction(drawablePrevious,"",PrevIntent); 1259 | builder.addAction(drawableId,"",pIntent); 1260 | builder.addAction(drawableForward,"",NextIntent); 1261 | builder.extend(wearableExtender); 1262 | 1263 | mNotificationManager.notify(NOTIFICATION_CONTROL_ID,builder.build()); 1264 | 1265 | } 1266 | 1267 | } 1268 | -------------------------------------------------------------------------------- /ambience/src/main/java/com/tonyostudios/ambience/AmbientMediaBrowserService.java: -------------------------------------------------------------------------------- 1 | package com.tonyostudios.ambience; 2 | 3 | 4 | import android.annotation.TargetApi; 5 | import android.app.PendingIntent; 6 | import android.content.BroadcastReceiver; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.content.IntentFilter; 10 | import android.graphics.Bitmap; 11 | import android.graphics.drawable.BitmapDrawable; 12 | import android.graphics.drawable.Drawable; 13 | import android.media.AudioManager; 14 | import android.media.MediaMetadata; 15 | import android.media.MediaPlayer; 16 | import android.media.Rating; 17 | import android.media.browse.MediaBrowser; 18 | import android.media.session.MediaSession; 19 | import android.media.session.PlaybackState; 20 | import android.net.wifi.WifiManager; 21 | import android.os.Build; 22 | import android.os.Bundle; 23 | import android.os.Handler; 24 | import android.os.IBinder; 25 | import android.os.Parcelable; 26 | import android.os.PowerManager; 27 | import android.os.ResultReceiver; 28 | import android.service.media.MediaBrowserService; 29 | import android.util.Log; 30 | import android.view.KeyEvent; 31 | 32 | 33 | import com.squareup.picasso.Picasso; 34 | import com.squareup.picasso.Target; 35 | 36 | import java.util.ArrayList; 37 | import java.util.Collections; 38 | import java.util.List; 39 | 40 | /** 41 | * AmbientTvService is a specific Android Service for androidTV and androidAuto that is used to control media playback 42 | * for audio. AmbientService is advanced and has all basic audio control 43 | * types like play,pause,stop,resume,previous,skip, shuffle and repeat. The 44 | * Service also creates A notification in the Notification Drawer to control playback. 45 | * Use the Ambience Class to communicate with the AmbientService 46 | * @author TonyoStudios.com. Created on 11/18/2014. Updated on 12/01/2014. 47 | * @version 1.3 48 | */ 49 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 50 | public class AmbientMediaBrowserService extends MediaBrowserService implements MediaPlayer.OnErrorListener, 51 | MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener, 52 | AudioManager.OnAudioFocusChangeListener { 53 | 54 | 55 | /** 56 | * Tag used to identify the AmbientService media token 57 | */ 58 | public final static String MEDIA_SESSION_TOKEN_TAG = AmbientService.TAG + ".MEDIA_SESSION_TOKEN_TAG"; 59 | 60 | /** 61 | * Holds the MediaSession object 62 | */ 63 | private MediaSession mSession; 64 | 65 | /** 66 | * Holds the Media Session state value 67 | */ 68 | private long mState = PlaybackState.STATE_NONE; 69 | 70 | /** 71 | * Media player used by the service to play AmbientTracks 72 | */ 73 | private MediaPlayer mPlayer; 74 | 75 | /** 76 | * Handler used to communicate updates to the UI thread 77 | */ 78 | private Handler mHandler; 79 | 80 | /** 81 | * Allows service to keep the wifi-radio on when needed 82 | */ 83 | private WifiManager.WifiLock mWifiLock; 84 | 85 | /** 86 | * Provides access to volume and ringer controls 87 | */ 88 | private AudioManager mAudioManager; 89 | 90 | /** 91 | * ArrayList used to hold the original playlist before mPlaylist is shuffled 92 | */ 93 | private ArrayList mOriginalPlaylist = new ArrayList(); 94 | 95 | /** 96 | * ArrayList used to manage AmbientTracks sent to the AmbientService for processing 97 | */ 98 | private ArrayList mPlaylist = new ArrayList(); 99 | 100 | /** 101 | * Holds the current playing AmbientTrack 102 | */ 103 | private AmbientTrack mAmbientTrack; 104 | 105 | /** 106 | * The current position of the current track in the playlist 107 | */ 108 | private int playPosition = 0; 109 | 110 | /** 111 | * Holds the intent filter action name used to launch an activity when a now playing card 112 | * is clicked on. 113 | */ 114 | private String mActivityLauncher; 115 | 116 | /** 117 | * The bundle passed to the IncomingRequestReceiver. This bundle may contain 118 | * data or actions requested by the Ambience Class 119 | */ 120 | private Bundle mBundle; 121 | 122 | /** 123 | * Holds the current repeat mode for the playlist 124 | */ 125 | private AmbientService.RepeatMode mRepeatMode = AmbientService.RepeatMode.OFF; 126 | 127 | /** 128 | * Holds the current shuffle mode for the playlist 129 | */ 130 | private AmbientService.ShuffleMode mShuffleState = AmbientService.ShuffleMode.OFF; 131 | 132 | 133 | /** 134 | * Holds the current volume level of the media player 135 | */ 136 | private float mVolume = 0.5f; 137 | 138 | 139 | 140 | /** 141 | * Method used to bind a service to an Android Component such as an activity 142 | * @param intent intent object 143 | * @return binder object 144 | * @see "http://developer.android.com/guide/components/bound-services.html" 145 | */ 146 | @Override 147 | public IBinder onBind(Intent intent) { 148 | return null; 149 | } 150 | 151 | /** 152 | * Called by the system every time a client explicitly starts the service by calling 153 | * startService(Intent), providing the arguments it supplied and a unique integer token 154 | * representing the start request. The Ambient Service is sticky by default. 155 | * @param intent The Intent supplied to startService(Intent), as given. 156 | * @param flags Additional data about the request 157 | * @param startId A unique integer representing this specific request to start. 158 | * @return The return value indicates what semantics the system should use for the service's 159 | * current started state. 160 | */ 161 | @Override 162 | public int onStartCommand(Intent intent, int flags, int startId) { 163 | return START_STICKY; 164 | } 165 | 166 | /** 167 | * The IncomingRequestBroadcast Receiver responds to all request made to the Ambient Service. 168 | * It is used to traffic all specific request to their intended route. 169 | */ 170 | private BroadcastReceiver IncomingRequestReceiver = new BroadcastReceiver() { 171 | 172 | /** 173 | * Method used to handle the incoming request receiver intent 174 | * @param context Context Object 175 | * @param intent intent object 176 | */ 177 | @Override 178 | public void onReceive(Context context, Intent intent) { 179 | 180 | if (intent == null || intent.getExtras() == null) { 181 | Log.e(AmbientService.TAG, "Passed an empty intent to IncomingRequestBroadcaster"); 182 | return; 183 | } 184 | 185 | mBundle = intent.getExtras(); 186 | 187 | 188 | if(mBundle.containsKey(AmbientService.ACTIVITY_LAUNCHER)) 189 | { 190 | mActivityLauncher = mBundle.getString(AmbientService.ACTIVITY_LAUNCHER); 191 | updateCardLaunchActivity(); 192 | } 193 | 194 | if(mBundle.containsKey(AmbientService.VOLUME_LEVEL)) 195 | { 196 | setVolumeTo(mBundle.getFloat(AmbientService.VOLUME_LEVEL,0.5f)); 197 | } 198 | 199 | if(mBundle.containsKey(AmbientService.PLAYLIST)) 200 | { 201 | createPlaylist(); 202 | } 203 | 204 | if(mBundle.containsKey(AmbientService.PLAY_POSITION)) 205 | { 206 | setPlayPosition(); 207 | } 208 | 209 | if(mBundle.containsKey(AmbientService.REMOVE_TRACK)) 210 | { 211 | removeTrackFromPlaylist(); 212 | } 213 | 214 | if(mBundle.containsKey(AmbientService.ADD_TRACK)) 215 | { 216 | addTrackToPlaylist(); 217 | } 218 | 219 | if(mBundle.containsKey(AmbientService.REPEAT_MODE)) 220 | { 221 | setRepeatMode(); 222 | } 223 | 224 | if(mBundle.containsKey(AmbientService.SHUFFLE_MODE)) 225 | { 226 | setShuffleMode(); 227 | } 228 | 229 | if(mBundle.containsKey(AmbientService.SEEK_POSITION)) 230 | { 231 | seekTo(mBundle.getInt(AmbientService.SEEK_POSITION,0)); 232 | } 233 | 234 | 235 | if(mBundle.containsKey(AmbientService.PLAYBACK_STATE)) 236 | { 237 | 238 | try { 239 | AmbientService.PlaybackState state = 240 | (AmbientService.PlaybackState) mBundle.getSerializable(AmbientService.PLAYBACK_STATE); 241 | 242 | //PLAYBACK CONTROLS 243 | switch (state) { 244 | case PLAY: init(); 245 | break; 246 | case STOP: stop(); 247 | break; 248 | case PAUSE: pause(); 249 | break; 250 | case RESUME: play(); 251 | break; 252 | case SKIP: playNext(); 253 | break; 254 | case PREVIOUS: playPrevious(); 255 | break; 256 | default: 257 | throw new IllegalStateException(AmbientService.TAG + ": Unknown Playback State"); 258 | } 259 | } catch (Exception e) { 260 | e.printStackTrace(); 261 | Log.e(AmbientService.TAG, e.getMessage()); 262 | } 263 | } 264 | } 265 | }; 266 | 267 | /** 268 | * Called by the system when the service is first created. 269 | */ 270 | @Override 271 | public void onCreate() { 272 | super.onCreate(); 273 | 274 | createMediaPlayer(); 275 | createMediaSession(); 276 | 277 | mHandler = new Handler(); 278 | 279 | //get handle on audio manager, wifi lock and notification manager 280 | mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 281 | mWifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)) 282 | .createWifiLock(WifiManager.WIFI_MODE_FULL,AmbientService.TAG); 283 | 284 | 285 | 286 | //register the incoming request receiver 287 | IntentFilter filter = new IntentFilter(AmbientService.AMBIENT_SERVICE_BROADCASTER); 288 | registerReceiver(IncomingRequestReceiver,filter); 289 | 290 | sendUpdateBroadcast(AmbientService.PlaybackState.SERVICE_STARTED); 291 | 292 | } 293 | 294 | /** 295 | * Called to create a set a media session object for AndroidTV Now Playing Card 296 | */ 297 | private void createMediaSession() 298 | { 299 | 300 | if(mSession != null) 301 | { 302 | mSession.release(); 303 | mSession = null; 304 | } 305 | 306 | mSession = new MediaSession(this, MEDIA_SESSION_TOKEN_TAG); 307 | mSession.setCallback(new MediaSessionCallback()); 308 | mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS); 309 | 310 | setSessionToken(mSession.getSessionToken()); 311 | 312 | updateCardLaunchActivity(); 313 | 314 | } 315 | 316 | 317 | /** 318 | * Method used to bundle the Ambient Service current information 319 | * @return A bundle containing the ambientService current playing track, 320 | * playlist, shuffle mode, repeat mode, volume level and action launcher string 321 | * 322 | */ 323 | private Bundle buildAmbientServiceBundle() 324 | { 325 | Bundle bundle = new Bundle(); 326 | 327 | if(mActivityLauncher != null) 328 | { 329 | bundle.putString(AmbientService.ACTIVITY_LAUNCHER,mActivityLauncher); 330 | } 331 | 332 | if(mAmbientTrack != null) 333 | { 334 | bundle.putParcelable(AmbientService.CURRENT_TRACK,mAmbientTrack); 335 | } 336 | 337 | 338 | if(mRepeatMode != null) 339 | { 340 | bundle.putSerializable(AmbientService.REPEAT_MODE,mRepeatMode); 341 | } 342 | 343 | if(mShuffleState != null) 344 | { 345 | bundle.putSerializable(AmbientService.SHUFFLE_MODE,mShuffleState); 346 | } 347 | 348 | if(mPlaylist != null) 349 | { 350 | bundle.putParcelableArrayList(AmbientService.PLAYLIST,mPlaylist); 351 | } 352 | 353 | 354 | bundle.putInt(AmbientService.PLAY_POSITION,playPosition); 355 | 356 | bundle.putFloat(AmbientService.VOLUME_LEVEL,mVolume); 357 | 358 | return bundle; 359 | } 360 | 361 | /** 362 | * Called to set the launch activity of th Now Playing Card on AndroidTV 363 | */ 364 | private void updateCardLaunchActivity() 365 | { 366 | Intent intent = new Intent(); 367 | 368 | //Set activity to launch from notification drawer 369 | if(mActivityLauncher != null) 370 | { 371 | intent.setAction(mActivityLauncher); 372 | intent.putExtras(buildAmbientServiceBundle()); 373 | } 374 | 375 | PendingIntent pi = PendingIntent.getActivity(this, 99 /*request code*/, 376 | intent, PendingIntent.FLAG_UPDATE_CURRENT); 377 | mSession.setSessionActivity(pi); 378 | } 379 | 380 | /** 381 | * resets and create a new media player object 382 | */ 383 | private void createMediaPlayer() 384 | { 385 | if(mPlayer != null) 386 | { 387 | try 388 | { 389 | mPlayer.release(); 390 | }catch (Exception e) 391 | { 392 | e.printStackTrace(); 393 | Log.e(AmbientService.TAG, e.getMessage()); 394 | } 395 | 396 | mPlayer = null; 397 | } 398 | 399 | mPlayer = new MediaPlayer(); 400 | 401 | //Set listeners on media player 402 | mPlayer.setOnCompletionListener(this); 403 | mPlayer.setOnPreparedListener(this); 404 | mPlayer.setOnErrorListener(this); 405 | 406 | ///set wake-lock mode for media player 407 | mPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); 408 | } 409 | 410 | /** 411 | * Method used to added the passed AmbientPlaylist to the queue 412 | */ 413 | private void createPlaylist() 414 | { 415 | if( mBundle == null || !mBundle.containsKey(AmbientService.PLAYLIST) 416 | || mBundle.getParcelableArrayList(AmbientService.PLAYLIST) == null) 417 | { 418 | throw new IllegalStateException(AmbientService.TAG + ": needs at least one AmbientTrack item to play"); 419 | } 420 | 421 | if(mOriginalPlaylist != null) 422 | { 423 | mOriginalPlaylist.clear(); 424 | } 425 | else 426 | { 427 | mOriginalPlaylist = new ArrayList(); 428 | } 429 | 430 | if(mPlaylist == null) 431 | { 432 | mPlaylist = new ArrayList(); 433 | } 434 | 435 | 436 | ArrayList newTracks = mBundle.getParcelableArrayList(AmbientService.PLAYLIST); 437 | 438 | for(int j = 0; j < newTracks.size(); j++) 439 | { 440 | mPlaylist.add((AmbientTrack)newTracks.get(j)); 441 | } 442 | 443 | //Copy position to maintain shuffle & un-shuffle state 444 | for(int x = 0; x < mPlaylist.size(); x++) 445 | { 446 | mOriginalPlaylist.add(mPlaylist.get(x)); 447 | } 448 | 449 | playPosition = 0; 450 | 451 | } 452 | 453 | /** 454 | * Called to remove a track from the current playlist 455 | */ 456 | private void removeTrackFromPlaylist() 457 | { 458 | if(mBundle.getParcelable(AmbientService.REMOVE_TRACK) != null && mOriginalPlaylist != null 459 | && mPlaylist != null) 460 | { 461 | mOriginalPlaylist.remove((AmbientTrack)mBundle.getParcelable(AmbientService.REMOVE_TRACK)); 462 | mPlaylist.remove((AmbientTrack)mBundle.getParcelable(AmbientService.REMOVE_TRACK)); 463 | } 464 | } 465 | 466 | /** 467 | * Called to append a track to the current playlist 468 | */ 469 | private void addTrackToPlaylist() 470 | { 471 | if(mBundle.getParcelable(AmbientService.ADD_TRACK) != null && mOriginalPlaylist != null 472 | && mPlaylist != null) 473 | { 474 | mOriginalPlaylist.add((AmbientTrack)mBundle.getParcelable(AmbientService.ADD_TRACK)); 475 | mPlaylist.add((AmbientTrack)mBundle.getParcelable(AmbientService.ADD_TRACK)); 476 | 477 | if(mShuffleState == AmbientService.ShuffleMode.ON && !mBundle.containsKey(AmbientService.SHUFFLE_MODE)) 478 | { 479 | toggleShuffle(); //If shuffle is on reshuffle track 480 | } 481 | } 482 | } 483 | 484 | /** 485 | * Sets the repeat mode for the Ambient Playlist 486 | */ 487 | private void setRepeatMode() 488 | { 489 | if(mBundle == null || !mBundle.containsKey(AmbientService.REPEAT_MODE) 490 | ||mBundle.getSerializable(AmbientService.REPEAT_MODE) == null ) 491 | { 492 | Log.e(AmbientService.TAG,": No valid repeat mode"); 493 | return; 494 | } 495 | 496 | mRepeatMode = (AmbientService.RepeatMode) mBundle.getSerializable(AmbientService.REPEAT_MODE); 497 | } 498 | 499 | /** 500 | * Sets the shuffle mode for the Ambient Playlist 501 | */ 502 | private void setShuffleMode() 503 | { 504 | if(mBundle == null || !mBundle.containsKey(AmbientService.SHUFFLE_MODE) 505 | ||mBundle.getSerializable(AmbientService.SHUFFLE_MODE) == null ) 506 | { 507 | Log.e(AmbientService.TAG,"No valid shuffle mode"); 508 | return; 509 | } 510 | 511 | mShuffleState = (AmbientService.ShuffleMode) mBundle.getSerializable(AmbientService.SHUFFLE_MODE); 512 | 513 | toggleShuffle(); 514 | } 515 | 516 | /** 517 | * Helper method used to toggle the shuffle state of the Ambient Playlist 518 | */ 519 | private void toggleShuffle() 520 | { 521 | if(mPlaylist != null && mOriginalPlaylist != null) 522 | { 523 | if(mShuffleState == AmbientService.ShuffleMode.ON) 524 | { 525 | Collections.shuffle(mPlaylist); 526 | setCurrentAmbientTrackPosition(); 527 | 528 | }else 529 | { 530 | mPlaylist.clear(); 531 | 532 | for(int x = 0; x < mOriginalPlaylist.size(); x++) 533 | { 534 | mPlaylist.add(mOriginalPlaylist.get(x)); 535 | } 536 | 537 | setCurrentAmbientTrackPosition(); 538 | } 539 | } 540 | } 541 | 542 | /** 543 | * Helper method used to get the position of the current playing AmbientTrack 544 | * before and after shuffle 545 | */ 546 | private void setCurrentAmbientTrackPosition() 547 | { 548 | if(mPlaylist != null) 549 | { 550 | for(int x = 0; x < mPlaylist.size(); x++) 551 | { 552 | if(mPlaylist.get(x) == mAmbientTrack) 553 | { 554 | playPosition = x; 555 | break; 556 | } 557 | } 558 | } 559 | } 560 | 561 | /** 562 | * Sets the play position of an Ambient track from the Ambient Playlist 563 | */ 564 | private void setPlayPosition() 565 | { 566 | if( mBundle == null || !mBundle.containsKey(AmbientService.PLAY_POSITION)) 567 | { 568 | throw new IllegalStateException(AmbientService.TAG + ": invalid play position"); 569 | } 570 | 571 | playPosition = mBundle.getInt(AmbientService.PLAY_POSITION,0); 572 | 573 | if(playPosition < 0 || playPosition >= mPlaylist.size()) 574 | { 575 | playPosition = 0; 576 | } 577 | } 578 | 579 | /** 580 | * Alerts the AmbientService about audio focus gain, 581 | * loss, etc. 582 | * @param focusChange value of focus changed 583 | */ 584 | @Override 585 | public void onAudioFocusChange(int focusChange) { 586 | if(mPlayer == null) 587 | { 588 | return; 589 | } 590 | 591 | switch (focusChange) { 592 | case AudioManager.AUDIOFOCUS_GAIN: 593 | 594 | if (!mPlayer.isPlaying()) 595 | { 596 | play(); 597 | } 598 | 599 | mPlayer.setVolume(mVolume,mVolume); 600 | break; 601 | 602 | case AudioManager.AUDIOFOCUS_LOSS: 603 | // Lost focus for an unbounded amount of time: stop playback and release media player 604 | if (mPlayer.isPlaying()) 605 | { 606 | if(mSession != null && mSession.isActive()) 607 | { 608 | mSession.setActive(false); 609 | } 610 | 611 | pause(); 612 | } 613 | 614 | break; 615 | 616 | case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 617 | // Lost focus for a short time, but we have to stop 618 | // playback. We don't release the media player because playback 619 | // is likely to resume 620 | if (mPlayer.isPlaying()) 621 | { 622 | pause(); 623 | } 624 | 625 | break; 626 | 627 | case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 628 | // Lost focus for a short time, but it's ok to keep playing 629 | // at an attenuated level 630 | if (mPlayer.isPlaying()) 631 | { 632 | mPlayer.setVolume(0.1f, 0.1f); 633 | } 634 | 635 | break; 636 | } 637 | } 638 | 639 | /** 640 | * Initialize and prepare the media player with the ambient track 641 | */ 642 | private void init() 643 | { 644 | sendUpdateBroadcast(AmbientService.PlaybackState.PREPPING_TRACK); // send prepping update to callback 645 | 646 | if(mPlayer == null) 647 | { 648 | createMediaPlayer(); 649 | } 650 | 651 | try { 652 | 653 | if(mPlaylist == null || mPlaylist.get(playPosition) == null ) 654 | { 655 | Log.e(AmbientService.TAG, ": The AmbientTrack item was null. Check the quality of your playlist before" + 656 | " passing it to the AmbientService."); 657 | return; 658 | } 659 | 660 | mAmbientTrack = mPlaylist.get(playPosition); 661 | 662 | 663 | if(mPlayer.isPlaying()) 664 | { 665 | mPlayer.stop(); 666 | } 667 | mPlayer.reset(); 668 | 669 | mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); 670 | mPlayer.setDataSource(this, mAmbientTrack.getAudioUri()); // set audio source 671 | 672 | mPlayer.prepareAsync(); 673 | }catch (Exception e) 674 | { 675 | e.printStackTrace(); 676 | Log.e(AmbientService.TAG, e.getMessage()); 677 | } 678 | } 679 | 680 | /** 681 | * Plays the AmbientTrack 682 | */ 683 | private void play() 684 | { 685 | try 686 | { 687 | int result = mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN); 688 | 689 | if (result != AudioManager.AUDIOFOCUS_GAIN) 690 | { 691 | Log.i(AmbientService.TAG, ": could not get audio focus from manager"); 692 | } 693 | 694 | mWifiLock.acquire(); 695 | 696 | if(mPlayer != null) 697 | { 698 | mPlayer.start(); 699 | mState = PlaybackState.ACTION_PLAY; 700 | } 701 | 702 | if(mSession != null) 703 | { 704 | mSession.setPlaybackState(getPlaybackState()); 705 | 706 | if(!mSession.isActive()) 707 | { 708 | mSession.setActive(true); 709 | } 710 | } 711 | 712 | metadataBitmapHelper(); 713 | 714 | if(mHandler != null) 715 | { 716 | mHandler.postDelayed(mUpdateProgress, AmbientService.AUDIO_PROGRESS_UPDATE_TIME); 717 | } 718 | 719 | sendUpdateBroadcast(AmbientService.PlaybackState.PLAY); // sends a now playing update to the callback 720 | }catch (Exception e) 721 | { 722 | e.printStackTrace(); 723 | Log.e(AmbientService.TAG,e.getMessage()); 724 | } 725 | } 726 | 727 | /** 728 | * Stops the current playing AmbientTrack 729 | */ 730 | private void stop() 731 | { 732 | try 733 | { 734 | mAudioManager.abandonAudioFocus(this); 735 | mWifiLock.release(); 736 | 737 | if(mPlayer != null && mPlayer.isPlaying()) 738 | { 739 | mPlayer.stop(); 740 | mState = PlaybackState.ACTION_STOP; 741 | } 742 | 743 | if(mSession != null) 744 | { 745 | mSession.setPlaybackState(getPlaybackState()); 746 | } 747 | 748 | sendUpdateBroadcast(AmbientService.PlaybackState.STOP); // sends a track has stopped update to the callback 749 | }catch (Exception e) 750 | { 751 | e.printStackTrace(); 752 | Log.e(AmbientService.TAG, e.getMessage()); 753 | } 754 | } 755 | 756 | /** 757 | * Pause the current playing AmbientTrack 758 | */ 759 | private void pause() 760 | { 761 | try 762 | { 763 | mAudioManager.abandonAudioFocus(this); 764 | mWifiLock.release(); 765 | 766 | 767 | if(mPlayer != null && mPlayer.isPlaying()) 768 | { 769 | mPlayer.pause(); 770 | mState = PlaybackState.ACTION_PAUSE; 771 | } 772 | 773 | if(mSession != null) 774 | { 775 | mSession.setPlaybackState(getPlaybackState()); 776 | } 777 | 778 | sendUpdateBroadcast(AmbientService.PlaybackState.PAUSE); // sends a track has paused update to the callback 779 | }catch (Exception e) 780 | { 781 | e.printStackTrace(); 782 | Log.e(AmbientService.TAG, e.getMessage()); 783 | } 784 | } 785 | 786 | /** 787 | * Method used to play the previous AmbientTrack in the playlist 788 | */ 789 | private void playPrevious() 790 | { 791 | mState = PlaybackState.ACTION_SKIP_TO_PREVIOUS; 792 | 793 | if(mPlayer != null && mPlayer.isPlaying()) 794 | { 795 | stop(); 796 | } 797 | 798 | if(mPlaylist != null) 799 | { 800 | --playPosition; 801 | 802 | if(playPosition < 0 ) 803 | { 804 | playPosition = mPlaylist.size() - 1; 805 | } 806 | 807 | init(); 808 | } 809 | } 810 | 811 | /** 812 | * Method used to play the next AmbientTrack in the playlist 813 | */ 814 | private void playNext() 815 | { 816 | mState = PlaybackState.ACTION_SKIP_TO_NEXT; 817 | 818 | if(mPlayer != null && mPlayer.isPlaying()) 819 | { 820 | stop(); 821 | } 822 | 823 | if(mPlaylist != null) 824 | { 825 | ++playPosition; 826 | 827 | if(playPosition >= mPlaylist.size()) 828 | { 829 | sendUpdateBroadcast(AmbientService.PlaybackState.END_OF_PLAYLIST); // send end of playlist update to callback 830 | playPosition = 0; 831 | } 832 | 833 | init(); 834 | } 835 | } 836 | 837 | /** 838 | * Runnable object used to update the main user interface with playback values 839 | */ 840 | private Runnable mUpdateProgress = new Runnable() { 841 | public void run() { 842 | 843 | if (mHandler != null && mPlayer != null && mPlayer.isPlaying()) { 844 | 845 | int position = mPlayer.getCurrentPosition(); 846 | int totalTime = mPlayer.getDuration(); 847 | 848 | 849 | //Send bundle with all track information to callback 850 | Bundle bundle = new Bundle(); 851 | bundle.putSerializable(AmbientService.PLAYBACK_STATE, AmbientService.PlaybackState.CURRENT_PLAYING_TRACK_INFO); 852 | 853 | bundle.putInt(AmbientService.TRACK_PROGRESS,position); 854 | bundle.putParcelable(AmbientService.CURRENT_TRACK,mAmbientTrack); 855 | bundle.putInt(AmbientService.TRACK_DURATION,totalTime); 856 | 857 | sendUpdateBroadcast(bundle); 858 | 859 | 860 | mHandler.postDelayed(this,AmbientService.AUDIO_PROGRESS_UPDATE_TIME); 861 | } 862 | } 863 | }; 864 | 865 | 866 | /** 867 | * Alerts the AmbientService when an AmbientTrack is 868 | * done playing. 869 | * @param mp Media Player object 870 | */ 871 | @Override 872 | public void onCompletion(MediaPlayer mp) { 873 | 874 | 875 | sendUpdateBroadcast(AmbientService.PlaybackState.STOP); //send a stop update to the callback 876 | 877 | if (!mSession.isActive()) { 878 | mSession.setActive(false); 879 | } 880 | 881 | if(mSession != null) 882 | { 883 | mSession.release(); 884 | mSession = null; 885 | createMediaSession(); 886 | } 887 | 888 | 889 | if(mRepeatMode != null) 890 | { 891 | if(mRepeatMode == AmbientService.RepeatMode.REPEAT_ALL) 892 | { 893 | playNext(); 894 | }else if(mRepeatMode == AmbientService.RepeatMode.REPEAT_ONE) 895 | { 896 | play(); 897 | } 898 | } 899 | 900 | } 901 | 902 | /** 903 | * Method used to alert the AmbientService that an error 904 | * has occured with the Media Player 905 | * @param mp Media Player object 906 | * @param what What error occurred 907 | * @param extra Extra error information about the error 908 | * @return The success of the error handling 909 | */ 910 | @Override 911 | public boolean onError(MediaPlayer mp, int what, int extra) { 912 | String errorMessage = AmbientService.TAG + ".Error - What: \" + what + \", extra: \" + extra"; 913 | 914 | Log.e(AmbientService.TAG, errorMessage); 915 | 916 | createMediaPlayer(); // reset media player to original state 917 | 918 | sendUpdateBroadcast(AmbientService.PlaybackState.ERROR); // send error update to the callback 919 | 920 | return false; 921 | } 922 | 923 | /** 924 | * Alters the AmbientService when the media player has prepared an AmbientTrack 925 | * and is ready for play. 926 | * @param mp media player object 927 | */ 928 | @Override 929 | public void onPrepared(MediaPlayer mp) { 930 | play(); 931 | } 932 | 933 | /** 934 | * Sets the volume level of the media player 935 | * @param volumeLevel volume level 936 | */ 937 | private void setVolumeTo(float volumeLevel) { 938 | 939 | if(mPlayer != null) 940 | { 941 | if(volumeLevel < 0.0f || volumeLevel > 1.0f ) 942 | { 943 | volumeLevel = 0.5f; 944 | } 945 | 946 | mVolume = volumeLevel; 947 | 948 | mPlayer.setVolume(mVolume,mVolume); 949 | } 950 | } 951 | 952 | /** 953 | * Set the seek position of the current AmbientTrack 954 | */ 955 | private void seekTo(int position) 956 | { 957 | if(mPlayer != null && position >= 0 && position <= mPlayer.getDuration()) 958 | { 959 | mPlayer.seekTo(position); 960 | } 961 | } 962 | 963 | /** 964 | * Method used to send an intent to the Ambience Broadcast Receiver to update the callback 965 | * component. 966 | * @param bundle A bundle object containing the current AmbientTrack component 967 | */ 968 | private void sendUpdateBroadcast(Bundle bundle) 969 | { 970 | Intent intent = new Intent(Ambience.AMBIENCE_BROADCASTER); 971 | intent.putExtras(bundle); 972 | sendBroadcast(intent); 973 | } 974 | 975 | /** 976 | * Method used to send an intent to the Ambience Broadcast Receiver to update the callback 977 | * component. 978 | * @param value Playback state of the Current AmbientTrack 979 | */ 980 | private void sendUpdateBroadcast(AmbientService.PlaybackState value) 981 | { 982 | Intent intent = new Intent(Ambience.AMBIENCE_BROADCASTER); 983 | intent.putExtra(AmbientService.PLAYBACK_STATE, value); 984 | sendBroadcast(intent); 985 | } 986 | 987 | /** 988 | * Called before the service terminates. All resources used by the AmbientService are 989 | * cleaned up in this method. 990 | */ 991 | @Override 992 | public void onDestroy() { 993 | super.onDestroy(); 994 | 995 | /* 996 | * RELEASE ALL AMBIENT SERVICE RESOURCES 997 | */ 998 | if(mPlayer != null) 999 | { 1000 | try 1001 | { 1002 | if(mPlayer.isPlaying()) 1003 | { 1004 | stop(); 1005 | } 1006 | 1007 | mPlayer.release(); 1008 | mPlayer = null; 1009 | }catch (Exception e) 1010 | { 1011 | e.printStackTrace(); 1012 | Log.e(AmbientService.TAG, e.getMessage()); 1013 | } 1014 | } 1015 | 1016 | if(mSession != null) 1017 | { 1018 | if(mSession.isActive()) 1019 | { 1020 | mSession.setActive(false); 1021 | } 1022 | 1023 | mSession.release(); 1024 | } 1025 | 1026 | mSession = null; 1027 | mState = 0; 1028 | 1029 | mHandler = null; 1030 | mAudioManager = null; 1031 | 1032 | try 1033 | { 1034 | mWifiLock.release(); 1035 | }catch (Exception e) 1036 | { 1037 | e.printStackTrace(); 1038 | Log.e(AmbientService.TAG,e.getMessage()); 1039 | } 1040 | 1041 | mWifiLock = null; 1042 | mBundle = null; 1043 | 1044 | if(mOriginalPlaylist != null) 1045 | { 1046 | mOriginalPlaylist.clear(); 1047 | } 1048 | 1049 | if(mPlaylist != null) 1050 | { 1051 | mPlaylist.clear(); 1052 | } 1053 | 1054 | mPlaylist = null; 1055 | mOriginalPlaylist = null; 1056 | mActivityLauncher = null; 1057 | mAmbientTrack = null; 1058 | mVolume = 0.5f; 1059 | playPosition = 0; 1060 | 1061 | unregisterReceiver(IncomingRequestReceiver); 1062 | sendUpdateBroadcast(AmbientService.PlaybackState.SERVICE_STOPPED); 1063 | } 1064 | 1065 | /** 1066 | * Called to get the root information for browsing by a particular client. 1067 | *The implementation should verify that the client package has permission to access browse media 1068 | *information before returning the root id; it should return null if the client is not allowed 1069 | * to access this information. 1070 | * @param clientPackageName The package name of the application which is requesting access to browse media. 1071 | * @param clientUid The uid of the application which is requesting access to browse media. 1072 | * @param rootHints An optional bundle of service-specific arguments to send to the media browse 1073 | * service when connecting and retrieving the root id for browsing, or null if none. 1074 | * The contents of this bundle may affect the information returned when browsing. 1075 | * @return A BrowserRoot object 1076 | */ 1077 | @Override 1078 | public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { 1079 | //not implemented 1080 | return null; 1081 | } 1082 | 1083 | /** 1084 | * Called to get information about the children of a media item. 1085 | * @param parentId The id of the parent media item whose children are to be queried. 1086 | * @param result The list of children, or null if the id is invalid. 1087 | */ 1088 | @Override 1089 | public void onLoadChildren(String parentId, Result> result) { 1090 | //not implemented 1091 | } 1092 | 1093 | /** 1094 | * Called to get the playback state of the media player. This method updates the media session 1095 | */ 1096 | private PlaybackState getPlaybackState() 1097 | { 1098 | long position = android.media.session.PlaybackState.PLAYBACK_POSITION_UNKNOWN; 1099 | 1100 | if (mPlayer != null && mPlayer.isPlaying()) { 1101 | position = mPlayer.getCurrentPosition(); 1102 | } 1103 | android.media.session.PlaybackState.Builder stateBuilder = new android.media.session.PlaybackState.Builder() 1104 | .setActions(getAvailableActions()); 1105 | stateBuilder.setState((int)mState, position, 1.0f); 1106 | 1107 | return stateBuilder.build(); 1108 | } 1109 | 1110 | /** 1111 | * Called to get the available actions for the now playing card 1112 | * @return actions value 1113 | */ 1114 | private long getAvailableActions() { 1115 | 1116 | long actions = android.media.session.PlaybackState.ACTION_PLAY | 1117 | android.media.session.PlaybackState.ACTION_PLAY_FROM_MEDIA_ID | 1118 | android.media.session.PlaybackState.ACTION_PLAY_FROM_SEARCH; 1119 | 1120 | if (mPlaylist == null || mPlaylist.size() <= 0) { 1121 | return actions; 1122 | } 1123 | if (mState == android.media.session.PlaybackState.STATE_PLAYING) { 1124 | actions |= android.media.session.PlaybackState.ACTION_PAUSE; 1125 | } 1126 | if (playPosition > 0) { 1127 | actions |= android.media.session.PlaybackState.ACTION_SKIP_TO_PREVIOUS; 1128 | } 1129 | if ( playPosition < mPlaylist.size() - 1) { 1130 | actions |= android.media.session.PlaybackState.ACTION_SKIP_TO_NEXT; 1131 | } 1132 | return actions; 1133 | } 1134 | 1135 | /** 1136 | * Called to get the AmbientTrack bitmap and update the Now Playing Card's meta data 1137 | * on AndroidTV. The AmbientService uses the Picasso Library 1138 | * by Square to get the album image from memory, cache or internet. 1139 | * @see "http://square.github.io/picasso/" 1140 | */ 1141 | private void metadataBitmapHelper () { 1142 | 1143 | if(mAmbientTrack == null) 1144 | { 1145 | Log.e(AmbientService.TAG,": AmbientTrack is null. Cannot create now playing card"); 1146 | return; 1147 | } 1148 | 1149 | 1150 | final Drawable placeholderDrawable = getResources().getDrawable(R.drawable.unknown_album); 1151 | 1152 | try 1153 | { 1154 | Picasso.with(this) 1155 | .load(mAmbientTrack.getAlbumImageUri()) 1156 | .placeholder(placeholderDrawable) 1157 | .error(placeholderDrawable) 1158 | .into(new Target() { 1159 | @Override 1160 | public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom loadedFrom) { 1161 | 1162 | if (bitmap == null) { 1163 | bitmap = ((BitmapDrawable) placeholderDrawable).getBitmap(); 1164 | } 1165 | 1166 | updateMetadata(bitmap); 1167 | 1168 | } 1169 | 1170 | @Override 1171 | public void onBitmapFailed(Drawable drawable) { 1172 | 1173 | if(drawable == null) 1174 | { 1175 | drawable = placeholderDrawable; 1176 | } 1177 | 1178 | updateMetadata(((BitmapDrawable) drawable).getBitmap()); 1179 | } 1180 | 1181 | @Override 1182 | public void onPrepareLoad(Drawable drawable) { 1183 | if(drawable == null) 1184 | { 1185 | drawable = placeholderDrawable; 1186 | } 1187 | 1188 | updateMetadata(((BitmapDrawable) drawable).getBitmap()); 1189 | } 1190 | }); 1191 | 1192 | }catch (Exception e) 1193 | { 1194 | e.printStackTrace(); 1195 | Log.e(AmbientService.TAG,e.getMessage()); 1196 | 1197 | updateMetadata(((BitmapDrawable) placeholderDrawable).getBitmap()); 1198 | } 1199 | 1200 | } 1201 | 1202 | /** 1203 | * Method used to update the meta data of the Now Playing Card with data from the current 1204 | * playing Ambient Track 1205 | * @param bitmap bitmap downloaded from memory, cache or internet async 1206 | */ 1207 | private void updateMetadata (Bitmap bitmap) 1208 | { 1209 | MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder(); 1210 | 1211 | // To provide most control over how an item is displayed set the 1212 | // display fields in the metadata 1213 | metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE,mAmbientTrack.getName()); 1214 | metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE,mAmbientTrack.getAlbumName()); 1215 | 1216 | metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,mAmbientTrack.getAlbumImageUri().toString()); 1217 | 1218 | // And at minimum the title and artist for legacy support 1219 | metadataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE,mAmbientTrack.getName()); 1220 | metadataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST,mAmbientTrack.getArtistName()); 1221 | 1222 | // A small bitmap for the artwork is also recommended 1223 | metadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_ART,bitmap); 1224 | 1225 | if(mSession != null) 1226 | { 1227 | // Add any other fields you have for your data as well 1228 | mSession.setMetadata(metadataBuilder.build()); 1229 | 1230 | } 1231 | 1232 | if (mSession != null && !mSession.isActive()) { 1233 | mSession.setActive(true); 1234 | 1235 | } 1236 | 1237 | } 1238 | 1239 | /** 1240 | * Receives media buttons, transport controls, and commands from controllers and the system. 1241 | * A callback may be set using setCallback(MediaSession.Callback). 1242 | */ 1243 | private class MediaSessionCallback extends MediaSession.Callback 1244 | { 1245 | 1246 | /** 1247 | * constructor 1248 | */ 1249 | public MediaSessionCallback() { 1250 | super(); 1251 | } 1252 | 1253 | /** 1254 | * Called when a controller has sent a command to this session. The owner of the session may handle custom commands but is not required to. 1255 | * @param command The command name. 1256 | * @param args Optional parameters for the command, may be null. 1257 | * @param cb A result receiver to which a result may be sent by the command, may be null. 1258 | */ 1259 | @Override 1260 | public void onCommand(String command, Bundle args, ResultReceiver cb) { 1261 | super.onCommand(command, args, cb); 1262 | 1263 | } 1264 | 1265 | 1266 | 1267 | /** 1268 | * Called when a media button is pressed and this session has the highest priority or a 1269 | * controller sends a media button event to the session. The default behavior will call the 1270 | * relevant method if the action for it was set. 1271 | * @param mediaButtonIntent an intent containing the KeyEvent as an extra 1272 | * @return True if the event was handled, false otherwise. 1273 | */ 1274 | @Override 1275 | public boolean onMediaButtonEvent(Intent mediaButtonIntent) { 1276 | 1277 | if (mSession != null 1278 | && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) { 1279 | KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); 1280 | if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) { 1281 | PlaybackState state = getPlaybackState(); 1282 | long validActions = state == null ? 0 : state.getActions(); 1283 | switch (ke.getKeyCode()) { 1284 | case KeyEvent.KEYCODE_MEDIA_PLAY: 1285 | if ((validActions & PlaybackState.ACTION_PLAY) != 0) { 1286 | onPlay(); 1287 | return true; 1288 | } 1289 | break; 1290 | case KeyEvent.KEYCODE_MEDIA_PAUSE: 1291 | if ((validActions & PlaybackState.ACTION_PAUSE) != 0) { 1292 | onPause(); 1293 | return true; 1294 | } 1295 | break; 1296 | case KeyEvent.KEYCODE_MEDIA_NEXT: 1297 | if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) { 1298 | onSkipToNext(); 1299 | return true; 1300 | } 1301 | break; 1302 | case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 1303 | if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) { 1304 | onSkipToPrevious(); 1305 | return true; 1306 | } 1307 | break; 1308 | case KeyEvent.KEYCODE_MEDIA_STOP: 1309 | if ((validActions & PlaybackState.ACTION_STOP) != 0) { 1310 | onStop(); 1311 | return true; 1312 | } 1313 | break; 1314 | case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 1315 | if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) { 1316 | onFastForward(); 1317 | return true; 1318 | } 1319 | break; 1320 | case KeyEvent.KEYCODE_MEDIA_REWIND: 1321 | if ((validActions & PlaybackState.ACTION_REWIND) != 0) { 1322 | onRewind(); 1323 | return true; 1324 | } 1325 | break; 1326 | case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 1327 | case KeyEvent.KEYCODE_HEADSETHOOK: 1328 | boolean isPlaying = state == null ? false 1329 | : state.getState() == PlaybackState.STATE_PLAYING; 1330 | boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE 1331 | | PlaybackState.ACTION_PLAY)) != 0; 1332 | boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE 1333 | | PlaybackState.ACTION_PAUSE)) != 0; 1334 | if (isPlaying && canPause) { 1335 | onPause(); 1336 | return true; 1337 | } else if (!isPlaying && canPlay) { 1338 | onPlay(); 1339 | return true; 1340 | } 1341 | break; 1342 | } 1343 | } 1344 | } 1345 | return false; 1346 | } 1347 | 1348 | /** 1349 | * Override to handle requests to begin playback. 1350 | */ 1351 | @Override 1352 | public void onPlay() { 1353 | super.onPlay(); 1354 | play(); 1355 | } 1356 | 1357 | /** 1358 | * Override to handle requests to play a specific mediaId that was provided by 1359 | * your app's MediaBrowserService. 1360 | * @param mediaId media id value 1361 | * @param extras extra information 1362 | */ 1363 | @Override 1364 | public void onPlayFromMediaId(String mediaId, Bundle extras) { 1365 | super.onPlayFromMediaId(mediaId, extras); 1366 | //not implemented 1367 | } 1368 | 1369 | /** 1370 | * Override to handle requests to begin playback from a search query. An empty query 1371 | * indicates that the app may play any music. The implementation should attempt to make a 1372 | * smart choice about what to play. 1373 | * @param query query string 1374 | * @param extras extra information 1375 | */ 1376 | @Override 1377 | public void onPlayFromSearch(String query, Bundle extras) { 1378 | super.onPlayFromSearch(query, extras); 1379 | //not implemented 1380 | } 1381 | 1382 | /** 1383 | * Override to handle requests to play an item with a given id from the play queue. 1384 | * @param id item id 1385 | */ 1386 | @Override 1387 | public void onSkipToQueueItem(long id) { 1388 | super.onSkipToQueueItem(id); 1389 | //not implemented 1390 | } 1391 | 1392 | /** 1393 | * Override to handle requests to pause playback. 1394 | */ 1395 | @Override 1396 | public void onPause() { 1397 | super.onPause(); 1398 | pause(); 1399 | } 1400 | 1401 | /** 1402 | * Override to handle requests to skip to the next media item. 1403 | */ 1404 | @Override 1405 | public void onSkipToNext() { 1406 | super.onSkipToNext(); 1407 | playNext(); 1408 | } 1409 | 1410 | /** 1411 | * Override to handle requests to skip to the previous media item. 1412 | */ 1413 | @Override 1414 | public void onSkipToPrevious() { 1415 | super.onSkipToPrevious(); 1416 | playPrevious(); 1417 | } 1418 | 1419 | /** 1420 | * Override to handle requests to fast forward. 1421 | */ 1422 | @Override 1423 | public void onFastForward() { 1424 | super.onFastForward(); 1425 | //not implemented 1426 | } 1427 | 1428 | /** 1429 | * Override to handle requests to rewind. 1430 | */ 1431 | @Override 1432 | public void onRewind() { 1433 | super.onRewind(); 1434 | //not implemented 1435 | } 1436 | 1437 | /** 1438 | * Override to handle requests to stop playback. 1439 | */ 1440 | @Override 1441 | public void onStop() { 1442 | super.onStop(); 1443 | stop(); 1444 | } 1445 | 1446 | /** 1447 | * Override to handle requests to seek to a specific position in ms. 1448 | * @param pos New position to move to, in milliseconds.\ 1449 | */ 1450 | @Override 1451 | public void onSeekTo(long pos) { 1452 | super.onSeekTo(pos); 1453 | seekTo((int)pos); 1454 | } 1455 | 1456 | /** 1457 | * Override to handle the item being rated. 1458 | * @param rating rating value 1459 | */ 1460 | @Override 1461 | public void onSetRating(Rating rating) { 1462 | super.onSetRating(rating); 1463 | //not implemented 1464 | } 1465 | 1466 | /** 1467 | * Called when a MediaController wants a PlaybackState.CustomAction to be performed. 1468 | * @param action The action that was originally sent in the PlaybackState.CustomAction. 1469 | * @param extras Optional extras specified by the MediaController. 1470 | */ 1471 | @Override 1472 | public void onCustomAction(String action, Bundle extras) { 1473 | super.onCustomAction(action, extras); 1474 | //not implemented 1475 | } 1476 | } 1477 | 1478 | } 1479 | --------------------------------------------------------------------------------