Make a template for a very common music player.
In addition, we will take a method that can support Wear and Auto without implementing dedicated processing.
There are no Japanese articles about Android music players. I made an article because I managed to implement it with various efforts. It will be longer, but please keep in touch if you like.
If you want to see only the implementation, skip to the implementation.
When making a music player that runs on a PC, use various methods of instances such as MediaPlayer. I think the main way to make it was to call it by clicking the button pasted on the Form.
However, mobile platforms such as Android and iOS in recent years are not limited to smartphones.
It has come to be incorporated into various devices such as watches, car audios, and televisions.
Then, it became necessary to link with a smartphone, operate the player from a smart watch or car audio, and display song information.
If a developer writes code for various devices (for Android Wear, Auto, TV, etc.), it will be a heavy burden.
Therefore, there was a movement to standardize such processing in the Android series, and it was implemented in Android 5.0.
That is MediaSession
.
The figure above shows the mechanism around Android music. These are similar to server-client relationships.
The Media Session
acts as a server.
It provides the function to operate songs, player status, song information, etc. to connected clients.
The client requests an operation from the Media Session using Media Controller
, Media Button Intent
, etc.
Displays the player status and song information to be notified.
reference: Understanding MediaSession (Part 1/4) Understanding MediaSession (Part 2/4)
Both are used to request the Media Session
to operate the song (play, stop, skip, etc.).
The difference is that Media Button Intent
can only request operations, while Media Controller
can also get player status and song information.
By the way, Media Session is assigned a unique ID called Session Token
. If you use the server analogy above, it's like an IP address. In other words, if you know the Token, you can connect from the outside.
When connecting, pass Token to the constructor of Media Controller
.
I think it's hard to imagine how to use Media Session from the above explanation, so I made a diagram.
Looking at the figure, it looks like you're doing something quite annoying. However, it makes a lot of sense to take the above form when creating a music player on the Android platform.
I think that the music player you usually use will continue to play even if you remove Activity from recent tasks. Or even if you don't remove it from the task, moving to another app may destroy the Activity when it runs out of memory. In other words, if you leave some of the processing related to playback to Activity, you will not be able to continue playback when the Activity is destroyed.
Therefore, the important idea in creating a music player that runs on Android is to implement all the functions to play songs on the Service side, and on the Activity side, perform operations such as play and pause according to the user's operation Media Session
. It is ** to clarify the division of roles **, such as simply displaying the song information being played from the Media Session
on the UI.
You don't have to be afraid that the Activity will be destroyed by simply displaying the song information to the user and providing control to control the player.
When the Activity is regenerated, you just need to reconnect to the Media Session
.
Reference: Media Apps Overview
In the previous example, Media Session was implemented in a normal Service.
However, in practice it is recommended to implement it using Media Browser Service
, which is an extension of Service.
The Media Session
mentioned earlier acts like a server that provides song operations and information, and the Media Browser Service
acts as a music library. If you implement it according to the specifications of Media Browser, you will be able to use functions such as automatically selecting songs from Wear and Auto.
It also encapsulates operations such as binding and getting Session Token.
It seems that there is not much difference from the precedent, as the names of steps ① and ② are easy to understand. In fact, a function to provide a list of songs will be added here.
reference: Building a Media Browser Service Building a Media Browser Client
Next is about browsing the library. In short, it is a function that displays a list of songs that can be played to the user.
I told you the song you want to play in ④ of the previous example, but if you do not have a list of songs in the first place, you can not make a request. As mentioned above, Media Browser has a mechanism to provide a music library.
The figure above shows the process of connecting to the Media Browser Service
and getting a list of songs.
Call subscribe (MediaId)
to request a Media Item from the Media Browser to the Media Browser Service
.
Then the list of Media Items is returned. Steps ③ and ④ are executed each time necessary.
In Media Browser Service
, all elements of songs, albums and genres are represented by the character string MediaId.
And there is MediaBrowser.MediaItem in the class that holds the MediaId and the accompanying data.
The content held by MediaItem has a flag that identifies whether it has a child element, an item that has the role of a so-called folder, or a playable music item that has no children. See the figure below.
Orange is the Item that has the folder flag, and blue is the Item that has the playable flag. The written string indicates MediaId.
Don't get me wrong here, the folder-flagged MediaItem itself doesn't actually have child elements as objects. In the above figure, it is written like a tree structure, but there is no parent-child relationship in terms of objects.
I think it's hard to understand, so let's give a concrete example.
Calling Media Browser subscribe (mediaId)
will call ʻonLoadChildren ()of the connected
Media Browser Serviceand will return the content sent from it. Initially call
subscribe ("root "). Then
{"Genre "," Album "}will be returned. Suppose a user wants to be by genre. Then call
subscribe ("Genre "). Then
{"Genre: Rock, "Genre: Jazz"}will be returned. Then go to
subscribe ("Genre: Rock ")and you'll get
{" music1 "," music2 "}`.
When the user selects music1, the Media Controller will request that the song with MediaId "music1" be played.
I wrote it as if music1 and music2 were automatically returned when I called subscribe ("Genre: Rock ")
, but I had to write the mechanism myself. For example, if "Genre:" is added to the beginning of MediaId, it is regarded as an ID that lists all songs of a specific genre, and the song is returned.
In other words, you need to know what you are looking for with just the Media ID and return the song or subcategory.
However, in this sample, I simply make it feel like two songs are hanging from root.
The classes prepared in advance are as follows.
name | Description |
---|---|
MediaDescription | A class that holds a minimum of metadata. |
MediaMetadata | ThesonginformationdeliveredfromMediaSessionisinthisformat.Inadditiontotheminimummetadata,youcansetcustomdatalikeanassociativearray.getDescription()You can also generate a Media Description with. |
MediaBrowser.MediaItem | The format for retrieving information from the MediaBrowserService. In addition to songs, items with child elements such as genres and albums can also be expressed. It has a Media Description inside. |
MediaSession.QueueItem | It has a Media Description inside and is used for information on songs in the queue. Therefore, index information is attached. |
You can see that we use Media Browser
and Media Controller
to connect to Media Browser Service
and Media Session
.
Actually, Wear and Auto also have these two. In the examples so far, it was connected from Activity, but Wear and Auto are just connected in the same way, so there is no need to write a dedicated process.
Information on songs delivered from Media Session is displayed. Also, if you set a cue item in Media Session, a list of songs will be displayed below.
The Wear side also has MediaBrowser.subscribe, which gets a list of songs and displays them. (This time only songs, but depending on the implementation of onLoadChildren, menus by album or genre can also be displayed)
Audio Focus
It's not a big deal given the presence of Media Sessions and Browsers, but it's also an important feature.
ʻAudio Focus is the audio version of Focus in the UI. Make sure only one app has ʻAudio Focus
. And if only apps with ʻAudio Focus` play songs, you can prevent multiple apps from playing music at the same time.
However, the Android system only notifies you when Focus hits or misses, so you write your own processing such as pausing when the focus is lost (such as when another app starts playing). is needed.
This time, we will use the music data stored in the Assets folder. I felt that it would be complicated when the story of storage was involved, so I compromised by practicing using Media Session. The song metadata is also hard-coded. Please note.
** All code is here **
Since the notification channel is added in Android 8.0 and it will be necessary to branch depending on the version, this time the target is set to 25 and the minimum is set to 21. (We do not accept Tsukkomi that it is not imadoki ^^;)
MediaPlayer is fine because it only plays MP3s, but I use ExoPlayer that I usually use. ExoPlayer is a media player developed by Google and is designed to replace the default MediaPlayer, which may have different implementations depending on the version and model. As it is updated, the formats that can be played will increase, and it will also have functions such as live streaming. How to use ExoPlayer How to use ExoPlayer I think that will be helpful.
It also uses the Support Library. I also recommend the Google official because it absorbs the difference between versions.
Therefore, use the one with Compat at the end, such as MediaSessionCompat
.
Dependent libraries are as follows
build.gradle
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support:design:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
implementation 'com.google.android.exoplayer:exoplayer:2.6.1'
}
・ Maina c Chity ty Activity displayed ・ Music Library I borrowed it from Google's official sample. The song information is hard-coded. ・ MusicService Implementation of Meida Browser Service.
Below is a diagram of the relationship between callbacks and methods.
AndroidManifest.xml
AndroidManifest.xml
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".MusicService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<receiver android:name="android.support.v4.media.session.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON"/>
</intent-filter>
</receiver>
</application>
This is an excerpt from the Application part.
Register MusicService
and also set the receiver to receive MediaButtonIntent
.
MainActivity UI ** Click here for UI xml file (https://github.com/SIY1121/MediaSessionSample/blob/master/app/src/main/res/layout/activity_main.xml) **
MainActivity.java
MediaBrowserCompat mBrowser;
MediaControllerCompat mController;
//UI related is omitted
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//UI setup (omitted)
//Keep the service running
//This is unnecessary if Service can be stopped at the same time as Activity is destroyed.
startService(new Intent(this, MusicService.class));
//Initialize MediaBrowser
mBrowser = new MediaBrowserCompat(this, new ComponentName(this, MusicService.class), connectionCallback, null);
//Connect(Bind service)
mBrowser.connect();
}
//Callback called when connecting
private MediaBrowserCompat.ConnectionCallback connectionCallback = new MediaBrowserCompat.ConnectionCallback() {
@Override
public void onConnected() {
try {
//Since Session Token can be obtained when the connection is completed
//Create a Media Controller using it
mController = new MediaControllerCompat(MainActivity.this, mBrowser.getSessionToken());
//Set a callback when the player status or song information sent from the service is changed
mController.registerCallback(controllerCallback);
//If it is already playing, call the callback itself and update the UI
if (mController.getPlaybackState() != null && mController.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING) {
controllerCallback.onMetadataChanged(mController.getMetadata());
controllerCallback.onPlaybackStateChanged(mController.getPlaybackState());
}
} catch (RemoteException ex) {
ex.printStackTrace();
Toast.makeText(MainActivity.this, ex.getMessage(), Toast.LENGTH_LONG).show();
}
//Get a list of playable songs from the service
mBrowser.subscribe(mBrowser.getRoot(), subscriptionCallback);
}
};
//Callback that is called when you subscribe
private MediaBrowserCompat.SubscriptionCallback subscriptionCallback = new MediaBrowserCompat.SubscriptionCallback() {
@Override
public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaBrowserCompat.MediaItem> children) {
//Request to play the first song if it is not already playing
if (mController.getPlaybackState() == null)
Play(children.get(0).getMediaId());
}
};
First, start Music Service.
After that, MediaBrowser is initialized and connected.
When the connection is completed, ʻonConnected ()is called, so get the Token with
mBrowser.getSessionToken ()and pass it to the constructor of
MediaController` to connect with Media Session.
Then call mBrowser.subscribe (mBrowser.getRoot (), subscriptionCallback)
to request a list of songs.
When the Service side sends a list, the set callback ʻonChildrenLoaded ()` is called, so this time we will play the first item.
MainActivity.java
private void Play(String id) {
//Get TransportControl to request operation from MediaController to service
//When playFromMediaId is called, onPlayFromMediaId in the callback of MediaSession on the service side is called.
mController.getTransportControls().playFromMediaId(id, null);
}
You can get the TransportControl
used to request operations from the MediaSession withmController.getTransportControls ()
.
MainActivity.java
//MediaController callback
private MediaControllerCompat.Callback controllerCallback = new MediaControllerCompat.Callback() {
//Called when the information of the song being played is changed
@Override
public void onMetadataChanged(MediaMetadataCompat metadata) {
textView_title.setText(metadata.getDescription().getTitle());
imageView.setImageBitmap(metadata.getDescription().getIconBitmap());
textView_duration.setText(Long2TimeString(metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)));
seekBar.setMax((int) metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION));
}
//Called when the player's state changes
@Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
//Change button behavior and icon depending on player status
if (state.getState() == PlaybackStateCompat.STATE_PLAYING) {
button_play.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mController.getTransportControls().pause();
}
});
button_play.setImageResource(R.drawable.exo_controls_pause);
} else {
button_play.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mController.getTransportControls().play();
}
});
button_play.setImageResource(R.drawable.exo_controls_play);
}
textView_position.setText(Long2TimeString(state.getPosition()));
seekBar.setProgress((int) state.getPosition());
}
};
When the state of the player or the song to be played changes, information is sent from MediaSession and the callback of MediaController is called. Reflect the change in the UI.
MusicService Create a Service that inherits from MediaBrowserService.
MusicService.java
//Called when connecting to a client
//Decide whether to connect from the package name etc.
//Connection permission when an arbitrary character string is returned
//Deny connection with null
//Allow all connections this time
@Override
public BrowserRoot onGetRoot(@NonNull String clientPackageName,
int clientUid,
Bundle rootHints) {
Log.d(TAG, "Connected from pkg:" + clientPackageName + " uid:" + clientUid);
return new BrowserRoot(ROOT_ID, null);
}
//Called when the client calls subscribe
//Returns the contents of the music library
//Also used for the list of songs displayed in Wear and Auto
//By default, the string returned by onGetRoot is passed to parentMediaId
//The Id is also passed when you select a MediaItem that has child elements on the browser screen.
@Override
public void onLoadChildren(
@NonNull final String parentMediaId,
@NonNull final Result<List<MediaBrowserCompat.MediaItem>> result) {
if (parentMediaId.equals(ROOT_ID))
//Send a list of songs to the client
result.sendResult(MusicLibrary.getMediaItems());
else//This time ROOT_Invalid except ID
result.sendResult(new ArrayList<MediaBrowserCompat.MediaItem>());
}
ʻOnGetRoot () is called when connecting and ʻonLoadChildren ()
is called when subscribed. Since ʻonLoadChildren ()is supposed to take time, it takes the form of sending the result to the result object instead of returning it with return. If you want to return it immediately, put
result.sendResult, and if you want to return it asynchronously later, put
result.detatch () `.
MusicService.java
final String TAG = MusicService.class.getSimpleName();//Log tag
final String ROOT_ID = "root";//ID onGetRoot to return to client/Used in onLoadChildren
Handler handler;//Handler for turning processing on a regular basis
MediaSessionCompat mSession;//Media Session of the leading role
AudioManager am;//Manager for handling AudioFoucs
int index = 0;//Index being played
ExoPlayer exoPlayer;//The substance of the music player
List<MediaSessionCompat.QueueItem> queueItems = new ArrayList<>();//List to use for queue
@Override
public void onCreate() {
super.onCreate();
//Get AudioManager
am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
//Initialize MediaSession
mSession = new MediaSessionCompat(getApplicationContext(), TAG);
//Set the functions provided by this Media Session
mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | //Handle buttons such as headphones
MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS | //Supports the use of queue commands
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); //Provides controls for play, stop, skip, etc.
//Set callback according to the operation from the client
mSession.setCallback(callback);
//Set Session Token in MediaBrowserService
setSessionToken(mSession.getSessionToken());
//When the Media Session metadata and player status are updated
//Create notification/Update
mSession.getController().registerCallback(new MediaControllerCompat.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
CreateNotification();
}
@Override
public void onMetadataChanged(MediaMetadataCompat metadata) {
CreateNotification();
}
});
//Add item to queue
int i = 0;
for (MediaBrowserCompat.MediaItem media : MusicLibrary.getMediaItems()) {
queueItems.add(new MediaSessionCompat.QueueItem(media.getDescription(), i));
i++;
}
mSession.setQueue(queueItems);//Queues are displayed in Wear and Auto
//Initialization of exoPlayer
exoPlayer = ExoPlayerFactory.newSimpleInstance(getApplicationContext(), new DefaultTrackSelector());
//Set player event listener
exoPlayer.addListener(eventListener);
handler = new Handler();
//Update playback information every 500ms
handler.postDelayed(new Runnable() {
@Override
public void run() {
//Update during playback
if (exoPlayer.getPlaybackState() == Player.STATE_READY && exoPlayer.getPlayWhenReady())
UpdatePlaybackState();
//Run again
handler.postDelayed(this, 500);
}
}, 500);
}
Since the amount is large, I will explain it briefly.
First of all, about mSession.setFlags ()
which is done after initializing MediaSession.
You can enable the Media Session feature by setting a flag.
Please note that no function can be used unless it is set in reverse.
Then setSessionToken (mSession.getSessionToken ())
This sets the value returned by getSessionToken ()
of the client (MediaBrowser).
Also, since ExoPlayer will be used this time, we will initialize ExoPlayer.
Finally, the handler is used to call ʻUpdatePlaybackState ()` at regular intervals. This process will be explained later.
This is the part that is paired with Transport Control that was used to request operations to MediaSession in MainActivity.
MusicService.java
//Callback for Media Session
private MediaSessionCompat.Callback callback = new MediaSessionCompat.Callback() {
//Play from song ID
//This is also called when a song is selected from the Wear or Auto browsing screen.
@Override
public void onPlayFromMediaId(String mediaId, Bundle extras) {
//This time play the audio file contained in the Assets folder
//Play from Uri
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getApplicationContext(), Util.getUserAgent(getApplicationContext(), "AppName"));
MediaSource mediaSource = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse("file:///android_asset/" + MusicLibrary.getMusicFilename(mediaId)));
//This time, the index is easily calculated from the mediaId.
for (MediaSessionCompat.QueueItem item : queueItems)
if (item.getDescription().getMediaId().equals(mediaId))
index = (int) item.getQueueId();
exoPlayer.prepare(mediaSource);
mSession.setActive(true);
onPlay();
//Set information about the song being played, delivered by MediaSession
mSession.setMetadata(MusicLibrary.getMetadata(getApplicationContext(), mediaId));
}
//When requested to play
@Override
public void onPlay() {
//Request audio focus
if (am.requestAudioFocus(afChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
//If you can get it, start playing
mSession.setActive(true);
exoPlayer.setPlayWhenReady(true);
}
}
//When a pause is requested
@Override
public void onPause() {
exoPlayer.setPlayWhenReady(false);
//Release audio focus
am.abandonAudioFocus(afChangeListener);
}
//When requested to stop
@Override
public void onStop() {
onPause();
mSession.setActive(false);
//Release audio focus
am.abandonAudioFocus(afChangeListener);
}
//When a seek is requested
@Override
public void onSeekTo(long pos) {
exoPlayer.seekTo(pos);
}
//When the next song is requested
@Override
public void onSkipToNext() {
index++;
if (index >= MusicLibrary.getMediaItems().size())//After playing to the end of the library
index = 0;//Return to the beginning
onPlayFromMediaId(queueItems.get(index).getDescription().getMediaId(), null);
}
//When the previous song is requested
@Override
public void onSkipToPrevious() {
index--;
if (index < 0)//When the index goes below 0
index = queueItems.size() - 1;//Move to the last song
onPlayFromMediaId(queueItems.get(index).getDescription().getMediaId(), null);
}
//Also called when an item in the queue is selected with Wear or Auto
@Override
public void onSkipToQueueItem(long i) {
onPlayFromMediaId(queueItems.get((int)i).getDescription().getMediaId(), null);
}
//Called when the Media Button Intent flies
//No override required (just spit out logs this time)
//The operations that can be performed change according to the Action flag of playbackState of MediaSession.
@Override
public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
KeyEvent key = mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
Log.d(TAG, String.valueOf(key.getKeyCode()));
return super.onMediaButtonEvent(mediaButtonEvent);
}
};
There are two points to note here.
First of all, mSession.setMetadata ()
which is the final process of ʻonPlayFromMediaId () If you set Metadata in Media Session, song information will be delivered to connected clients. Taking MainActivity as an example, the MediaController callback ʻonMetadataChanged ()
is called.
Then ʻonMediaButtonEvent ()` This is the place that will be called when the Media Button Intent flies. It also includes physical buttons on wired headphones and controls from Bluetooth-connected devices.
Unlike other methods, there is already an implementation here, so there is basically no need to override it. For example, processing such as mapping a single physical button press on headphones to onPlay () and onPause () and a double press to onSkipNext () has already been implemented.
However, if you want to add your own operation by pressing the physical button, you will handle it here.
MusicService.java
//Player callback
private Player.EventListener eventListener = new Player.DefaultEventListener() {
//Called when the player's status changes
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
UpdatePlaybackState();
}
};
//Set the current player status delivered by MediaSession
//This includes information on the playback position, so update it regularly.
private void UpdatePlaybackState() {
int state = PlaybackStateCompat.STATE_NONE;
//Set the appropriate Media Session status from the player's status
switch (exoPlayer.getPlaybackState()) {
case Player.STATE_IDLE:
state = PlaybackStateCompat.STATE_NONE;
break;
case Player.STATE_BUFFERING:
state = PlaybackStateCompat.STATE_BUFFERING;
break;
case Player.STATE_READY:
if (exoPlayer.getPlayWhenReady())
state = PlaybackStateCompat.STATE_PLAYING;
else
state = PlaybackStateCompat.STATE_PAUSED;
break;
case Player.STATE_ENDED:
state = PlaybackStateCompat.STATE_STOPPED;
break;
}
//Set player information, current playback position, etc.
//Also, set the operations that can be performed with MeidaButtonIntent.
mSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_STOP)
.setState(state, exoPlayer.getCurrentPosition(), exoPlayer.getPlaybackParameters().speed)
.build());
}
The first callback is from ExoPlayer. As the name implies, it is called when the status of Player changes.
ʻUpdatePlaybackState ()` sets the state of the player delivered by MediaSession. The contents to be delivered are the status such as Playing and Paused, the playback position, the playback speed, and the currently accepted operations. The status PlaybackStateCompat.STATE_XXXX, which indicates the state of the player, and the status of ExoPlayer are different and must be converted to the corresponding ones. By the way, the fact that the playback position is included means that the playback position must always be delivered. Therefore, I am trying to call here every 0.5 seconds in the initialization (onCreate) part.
MusicService.java
//Create a notification and make the service Foreground
private void CreateNotification() {
MediaControllerCompat controller = mSession.getController();
MediaMetadataCompat mediaMetadata = controller.getMetadata();
if (mediaMetadata == null && !mSession.isActive()) return;
MediaDescriptionCompat description = mediaMetadata.getDescription();
NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext());
builder
//Set information for the current song
.setContentTitle(description.getTitle())
.setContentText(description.getSubtitle())
.setSubText(description.getDescription())
.setLargeIcon(description.getIconBitmap())
//Set intent when clicking notifications
.setContentIntent(createContentIntent())
//Set intent when notifications are swiped off
.setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(this,
PlaybackStateCompat.ACTION_STOP))
//Make the notification range public so that it will be displayed on the lock screen
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setSmallIcon(R.drawable.exo_controls_play)
//Set the color used for the notification area
//Styles change depending on Android version, and colors are often not applied
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
//Use Media Style
.setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mSession.getSessionToken())
//Set the index of the control that is displayed when the notification is folded small
.setShowActionsInCompactView(1));
// Android4.Before 4, you can't swipe to dismiss notifications
//Deal with by displaying the cancel button
//This time the min SDK is 21, so it's not necessary
//.setShowCancelButton(true)
//.setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(this,
// PlaybackStateCompat.ACTION_STOP)));
//Notification control settings
builder.addAction(new NotificationCompat.Action(
R.drawable.exo_controls_previous, "prev",
MediaButtonReceiver.buildMediaButtonPendingIntent(this,
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)));
//Set play and pause buttons in the player state
if (controller.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING) {
builder.addAction(new NotificationCompat.Action(
R.drawable.exo_controls_pause, "pause",
MediaButtonReceiver.buildMediaButtonPendingIntent(this,
PlaybackStateCompat.ACTION_PAUSE)));
} else {
builder.addAction(new NotificationCompat.Action(
R.drawable.exo_controls_play, "play",
MediaButtonReceiver.buildMediaButtonPendingIntent(this,
PlaybackStateCompat.ACTION_PLAY)));
}
builder.addAction(new NotificationCompat.Action(
R.drawable.exo_controls_next, "next",
MediaButtonReceiver.buildMediaButtonPendingIntent(this,
PlaybackStateCompat.ACTION_SKIP_TO_NEXT)));
startForeground(1, builder.build());
//Allow swipes to turn off notifications when not playing
if (controller.getPlaybackState().getState() != PlaybackStateCompat.STATE_PLAYING)
stopForeground(false);
}
To create a notification with playback control, you need to specify MediaStyle with setStyle and set Media Button Intent with addAction to raise it.
MusicService.java
//Audio focus callback
AudioManager.OnAudioFocusChangeListener afChangeListener =
new AudioManager.OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
//If you lose focus completely
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
//stop
mSession.getController().getTransportControls().pause();
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {//Temporary focus lost
//stop
mSession.getController().getTransportControls().pause();
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {//Focus lost due to notification sound (should be turned down and continue playing)
//Normally you should turn down the volume temporarily, but do nothing
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {//When the focus is regained
//Regeneration
mSession.getController().getTransportControls().play();
}
}
};
We have implemented a mechanism to stop playback if another app starts playing sound and the Audio Focus of this app is lost.
I felt that it was very well done because there was only an API implemented from Android 5.0.
Since many Android music players have already been released, I think that there are few opportunities to make a particularly pure music player by yourself, but it may be useful if you have the opportunity to play music in the background such as internet radio. Hmm.
I tried to explain Android Media API in one article. It's been a long time and I'm surprised myself. In such a case, is it easier to read by dividing it?
Anyway, thank you for watching until the end.
-Android MediaBrowserService Sample This was simplified and this sample was created. ・ Media Controller Test A sample that implements Media Browser and Media Controller and can control other music apps. ・ Universal Android Music Player Sample A fairly neat music player implementation. You are using the browsing function of Media Browser Service properly.
Recommended Posts