Note: For personal reference using Media3 ExoPlayer & MediaSessionService in java
project: com.dedetok.tutorialmediaplayerbackgroundservice
dependencies {
...
// For exposing and controlling media sessions
implementation("androidx.media3:media3-session:1.2.1")
// For media playback using ExoPlayer
implementation("androidx.media3:media3-exoplayer:1.2.1")
// For HLS playback support with ExoPlayer
implementation("androidx.media3:media3-exoplayer-hls:1.2.1")
...
}
package com.dedetok.tutorialmediaplayerbackgroundservice.myservice.DedetokMediaSessionService.java
package com.dedetok.tutorialmediaplayerbackgroundservice.myservice;
import android.content.Intent;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.Player;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.session.MediaSession;
import androidx.media3.session.MediaSessionService;
/*
* MediaSession ok
* ExoPlayer inside MediaSession
*/
public class DedetokMediaSessionService extends MediaSessionService {
/*
* MediaSession creates a default implementation of MediaSession.Callback that
* automatically handles all commands a MediaController sends to your player
*/
MediaSession myMediaSession = null;
/*
* Create your Player and MediaSession in the onCreate lifecycle event
*
*/
@Override
public void onCreate() {
super.onCreate();
ExoPlayer player = new ExoPlayer.Builder(this).build();
// Let system handle focus audio for incoming or outgoing call
player.setAudioAttributes(AudioAttributes.DEFAULT, true);
myMediaSession = new MediaSession.Builder(this, player).build();
Log.e("dedetok","DedetokMediaSessionService onCreate"); // debug
}
/*
* The user dismissed the app from the recent tasks
*/
@Override
public void onTaskRemoved(@Nullable Intent rootIntent) {
Player player = myMediaSession.getPlayer();
if (!player.getPlayWhenReady() || player.getMediaItemCount() == 0 ) {
/*
* stop player if not ready || no media playing
* else play on background
*/
player.stop();
stopSelf();
Log.e("dedetok", "onTaskRemoved stop and release resources"); // debug
}
}
/*
* Remember to release the player and media session in onDestroy
*/
@Override
public void onDestroy() {
super.onDestroy();
myMediaSession.getPlayer().release();
myMediaSession.release();
myMediaSession = null;
}
/*
* extends MediaSessionService
*/
@Nullable
@Override
public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
return myMediaSession;
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- internet -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- foreground service -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- target API 34 and above -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
...
<!-- omited -->
...
<!-- android:foregroundServiceType="mediaPlayback" target API >=29 -->
<!-- action android:name="android.media.browse.MediaBrowserService" backward compatible with client apps -->
<!-- action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" Declare legacy support for voice actions -->
<service
android:name=".myservice.DedetokMediaSessionService"
android:exported="true"
android:foregroundServiceType="mediaPlayback"
android:icon="@mipmap/ic_launcher"
>
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService" />
<action android:name="android.media.browse.MediaBrowserService" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
</intent-filter>
</service>
</application>
</manifest>
package com.dedetok.tutorialmediaplayerbackgroundservice.RadioListItem.java
package com.dedetok.tutorialmediaplayerbackgroundservice;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
public class RadioListItem implements Parcelable {
public String mCountry; //0
public String mCity; //1
public String mRadio; //2
public String mIcon; //3
public String mStreamURL; //4
public RadioListItem() {
}
protected RadioListItem(Parcel in) {
mCountry = in.readString();
mCity = in.readString();
mRadio = in.readString();
mIcon = in.readString();
mStreamURL = in.readString();
}
public static final Creator<RadioListItem> CREATOR = new Creator<RadioListItem>() {
@Override
public RadioListItem createFromParcel(Parcel in) {
return new RadioListItem(in);
}
@Override
public RadioListItem[] newArray(int size) {
return new RadioListItem[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel parcel, int i) {
parcel.writeString(mCountry);
parcel.writeString(mCity);
parcel.writeString(mRadio);
parcel.writeString(mIcon);
parcel.writeString(mStreamURL);
}
}
package com.dedetok.tutorialmediaplayerbackgroundservice.MainActivity.java
package com.dedetok.tutorialmediaplayerbackgroundservice;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatButton;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
import androidx.media3.session.MediaController;
import androidx.media3.session.SessionToken;
import android.content.ComponentName;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.dedetok.tutorialmediaplayerbackgroundservice.myservice.DedetokMediaSessionService;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.ExecutionException;
/*
* MediaController
* UI
*/
public class MainActivity extends AppCompatActivity {
MediaController myMediaController = null;
AppCompatButton bPlay;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bPlay = findViewById(R.id.b_play);
bPlay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// https://stackoverflow.com/questions/74035158/android-media3-session-controller-playback-not-starting
// https://stackoverflow.com/questions/77039284/how-to-modify-the-auto-generated-media3-notification-java
if (myMediaController!=null) {
Log.e("dedetok", "MediaController set MediaItem"); // debug
RadioListItem myRadio = new RadioListItem();
myRadio.mStreamURL = "https://stream-node0.rri.co.id/streaming/25/9125/voi.mp3";
myRadio.mRadio = "RRI VOI";
myRadio.mIcon = "https://www.rri.co.id/assets/v2/img/pro6.png";
String mRadioTitle = "Playing "+myRadio.mRadio;
Log.e("dedetok", "MediaController prepare and play"); // debug
Uri myUri = Uri.parse(myRadio.mIcon);
MediaMetadata myMediaMetadata = new MediaMetadata.Builder().
setTitle(myRadio.mRadio).
setStation(myRadio.mRadio).
setDisplayTitle(mRadioTitle).
//setArtworkUri(myUri). // to set background icon may any size
build();
//MediaItem myMediaItem = MediaItem.fromUri(myRadio.mStreamURL); // original
MediaItem myMediaItem = new MediaItem.Builder().
setMediaMetadata(myMediaMetadata).
setUri(Uri.parse(myRadio.mStreamURL)).
build();
myMediaController.setMediaItem(myMediaItem);
myMediaController.prepare();
myMediaController.setPlayWhenReady(true);
Log.e("dedetok", "MediaController prepare and play"); // debug
}
}
});
}
/*
* ## activity life cycle 2 ##
* Android N (7.0) create Player
*/
@Override
protected void onStart() {
super.onStart();
// start services and get MediaController
createMediaController();
}
/*
* ## activity life cycle 5 ##
* Android N (7.0) release Player
*/
@Override
protected void onStop() {
super.onStop();
// THIS WILL STOP BACKGROUND PLAYER
//myMediaController.stop();
//Log.e("dedetok", "onStaop stoping player"); // debug
}
/*
* ContextCompat.getMainExecutor() instead of MoreExecutors.directExecutor()
* in case you run your service in another process than the UI.
*/
private void createMediaController() {
// https://developer.android.com/media/media3/session/connect-to-media-app#create-controller
// step 1 create SessionToken
SessionToken mySessionToken =
new SessionToken(this,
new ComponentName(this, DedetokMediaSessionService.class));
// step 2 build MediaController using SessionToken
ListenableFuture<MediaController> myListenableFutureMediaController =
new MediaController.Builder(this, mySessionToken).buildAsync();
myListenableFutureMediaController.addListener(() -> {
// MediaController is available here with myListenableFutureMediaController.get()
try {
myMediaController = myListenableFutureMediaController.get();
Log.e("dedetok", "get MediaController"); // debug
if (myMediaController==null) {
Log.e("dedetok", "MediaController null"); // debug
}
} catch(ExecutionException e) {
Log.e("dedetok", e.getMessage()); // debug
e.printStackTrace(); // debug
} catch(InterruptedException e) {
Log.e("dedetok", e.getMessage()); // debug
e.printStackTrace(); // debug
}
}, MoreExecutors.directExecutor());
}
}