I have implemented a Service to run the audio in background which runs perfectly but I am unable to get the instance of the SimpleExoPlayer from the service to the activity to update the UI and also the Audio plays twice in the background if I exit and reopen the activity.
AudioPlayerService
public class AudioPlayerService extends Service {
private final IBinder mBinder = new LocalBinder();
private SimpleExoPlayer player;
private Item item;
private PlayerNotificationManager playerNotificationManager;
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
playerNotificationManager.setPlayer(null);
player.release();
player = null;
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
enter code here
public SimpleExoPlayer getplayerInstance() {
return player;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Bundle b = intent.getBundleExtra(AppConstants.BUNDLE_KEY);
if (b != null) {
item = b.getParcelable(AppConstants.ITEM_KEY);
}
startPlayer();
return START_STICKY;
}
private void startPlayer() {
final Context context = this;
Uri uri = Uri.parse(item.getUrl());
player = ExoPlayerFactory.newSimpleInstance(context, new DefaultTrackSelector());
DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(context,
Util.getUserAgent(context, getString(R.string.app_name)));
MediaSource mediaSource = new ExtractorMediaSource.Factory(dataSourceFactory)
.createMediaSource(uri);
player.prepare(mediaSource);
player.setPlayWhenReady(true);
playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(context, AppConstants.PLAYBACK_CHANNEL_ID,
R.string.playback_channel_name,
AppConstants.PLAYBACK_NOTIFICATION_ID,
new PlayerNotificationManager.MediaDescriptionAdapter() {
@Override
public String getCurrentContentTitle(Player player) {
return item.getTitle();
}
@Nullable
@Override
public PendingIntent createCurrentContentIntent(Player player) {
Intent intent = new Intent(context, PlayerActivity.class);
Bundle serviceBundle = new Bundle();
serviceBundle.putParcelable(AppConstants.ITEM_KEY, item);
intent.putExtra(AppConstants.BUNDLE_KEY, serviceBundle);
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
@Nullable
@Override
public String getCurrentContentText(Player player) {
return item.getSummary();
}
@Nullable
@Override
public Bitmap getCurrentLargeIcon(Player player, PlayerNotificationManager.BitmapCallback callback) {
return item.getBitmap();
}
}
);
playerNotificationManager.setNotificationListener(new PlayerNotificationManager.NotificationListener() {
@Override
public void onNotificationStarted(int notificationId, Notification notification) {
startForeground(notificationId, notification);
}
@Override
public void onNotificationCancelled(int notificationId) {
stopSelf();
}
});
playerNotificationManager.setPlayer(player);
}
public class LocalBinder extends Binder {
public AudioPlayerService getService() {
return AudioPlayerService.this;
}
}
}
This is my activity where I am starting the service and binding to it. I have to pass the Item object in-order the service to run, if I don't pass the data using the intent the service will crash so I can't do startService() in the service itself I have to start in the activity I guess.
PlayerActivity
public class PlayerActivity extends BaseActivity {
@BindView(R.id.video_view)
PlayerView mPlayerView;
@BindView(R.id.tvTitle)
TextView mTvTitle;
@BindView(R.id.tvSummary)
TextView mTvSummary;
@BindView(R.id.ivThumbnail)
ImageView mIvThumb;
private SimpleExoPlayer player;
private String mURL, mTitle, mSummary, mImage;
private AudioPlayerService mService;
private boolean mBound = false;
private Intent intent;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
AudioPlayerService.LocalBinder binder = (AudioPlayerService.LocalBinder) iBinder;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
mBound = false;
}
};
@SuppressLint("MissingSuperCall")
@Override
protected void onCreate(Bundle savedInstanceState) {
onCreate(savedInstanceState, R.layout.activity_player);
Bundle b = getIntent().getBundleExtra(AppConstants.BUNDLE_KEY);
if (b != null) {
Item item = b.getParcelable(AppConstants.ITEM_KEY);
mURL = item.getUrl();
mImage = item.getImage();
mTitle = item.getTitle();
mSummary = item.getSummary();
intent = new Intent(this, AudioPlayerService.class);
Bundle serviceBundle = new Bundle();
serviceBundle.putParcelable(AppConstants.ITEM_KEY, item);
intent.putExtra(AppConstants.BUNDLE_KEY, serviceBundle);
Util.startForegroundService(this, intent);
}
}
private void initializePlayer() {
if (player == null && !mURL.isEmpty() && mBound) {
player = mService.getplayerInstance();
mPlayerView.setPlayer(player);
mPlayerView.setControllerHideOnTouch(false);
mPlayerView.setControllerShowTimeoutMs(10800000);
}
}
@Override
public void onStart() {
super.onStart();
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
initializePlayer();
setUI();
}
private void setUI() {
mTvTitle.setText(mTitle);
mTvSummary.setText(mSummary);
GlideApp.with(this)
.load(mImage)
.placeholder(R.color.colorPrimary)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(mIvThumb);
}
@Override
protected void onStop() {
unbindService(mConnection);
mBound = false;
releasePlayer();
super.onStop();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.player_menu, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.share_podcast:
//Logic for Share
return true;
case R.id.download_podcast:
//Logic for download
return true;
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void releasePlayer() {
if (player != null) {
player.release();
player = null;
}
}
@Override
public void onToolBarSetUp(Toolbar toolbar, ActionBar actionBar) {
TextView tvHeader = toolbar.findViewById(R.id.tvClassName);
tvHeader.setText(R.string.app_name);
actionBar.setHomeAsUpIndicator(R.drawable.ic_arrow_back_black_24dp);
}
}
I have tried everything I know but I'm unable to move forward because of this.
So after a lot of research I was able to resolve this using the bound service and getting the SimpleExoPlayer instance from the service and set the Player view to always show up using the following method.
mPlayerView.showController()
After all the modification and setup it takes only two classes to achieve background audio playback with notification control, one is the activity and the other is the service using the latest exoplayer release.
PlayerActivity
public class PlayerActivity extends BaseActivity {
@BindView(R.id.video_view)
PlayerView mPlayerView;
@BindView(R.id.tvTitle)
TextView mTvTitle;
@BindView(R.id.tvSummary)
TextView mTvSummary;
@BindView(R.id.ivThumbnail)
ImageView mIvThumb;
private String mUrl, mTitle, mSummary, mImage;
private AudioPlayerService mService;
private Intent intent;
private String shareableLink;
private boolean mBound = false;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
AudioPlayerService.LocalBinder binder = (AudioPlayerService.LocalBinder) iBinder;
mService = binder.getService();
mBound = true;
initializePlayer();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
mBound = false;
}
};
@SuppressLint("MissingSuperCall")
@Override
protected void onCreate(Bundle savedInstanceState) {
onCreate(savedInstanceState, R.layout.activity_player);
Bundle b = getIntent().getBundleExtra(AppConstants.BUNDLE_KEY);
if (b != null) {
Item item = b.getParcelable(AppConstants.ITEM_KEY);
shareableLink = b.getString(AppConstants.SHARE_KEY);
mImage = item.getImage();
mUrl = item.getUrl();
mTitle = item.getTitle();
mSummary = item.getSummary();
intent = new Intent(this, AudioPlayerService.class);
Bundle serviceBundle = new Bundle();
serviceBundle.putParcelable(AppConstants.ITEM_KEY, item);
intent.putExtra(AppConstants.BUNDLE_KEY, serviceBundle);
Util.startForegroundService(this, intent);
mPlayerView.setUseController(true);
mPlayerView.showController();
mPlayerView.setControllerAutoShow(true);
mPlayerView.setControllerHideOnTouch(false);
}
}
private void initializePlayer() {
if (mBound) {
SimpleExoPlayer player = mService.getplayerInstance();
mPlayerView.setPlayer(player);
}
}
@Override
public void onStart() {
super.onStart();
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
initializePlayer();
setUI();
}
private void setUI() {
mTvTitle.setText(mTitle);
mTvSummary.setText(mSummary);
GlideApp.with(this)
.load(mImage)
.placeholder(R.color.colorPrimary)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(mIvThumb);
}
@Override
protected void onStop() {
unbindService(mConnection);
mBound = false;
super.onStop();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.player_menu, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.share_podcast:
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_SUBJECT, mTitle);
shareIntent.putExtra(Intent.EXTRA_TEXT, mTitle + "\n\n" + shareableLink);
shareIntent.setType("text/plain");
startActivity(Intent.createChooser(shareIntent, getString(R.string.share_text)));
return true;
case R.id.download_podcast:
Uri uri = Uri.parse(mUrl);
ProgressiveDownloadAction progressiveDownloadAction
= new ProgressiveDownloadAction(uri, false, null, null);
AudioDownloadService.startWithAction(PlayerActivity.this, AudioDownloadService.class,
progressiveDownloadAction, false);
return true;
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onToolBarSetUp(Toolbar toolbar, ActionBar actionBar) {
TextView tvHeader = toolbar.findViewById(R.id.tvClassName);
tvHeader.setText(R.string.app_name);
actionBar.setHomeAsUpIndicator(R.drawable.ic_arrow_back_black_24dp);
}
}
AudioPlayerService
public class AudioPlayerService extends Service {
private final IBinder mBinder = new LocalBinder();
private SimpleExoPlayer player;
private Item item;
private PlayerNotificationManager playerNotificationManager;
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
releasePlayer();
super.onDestroy();
}
private void releasePlayer() {
if (player != null) {
playerNotificationManager.setPlayer(null);
player.release();
player = null;
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public SimpleExoPlayer getplayerInstance() {
if (player == null) {
startPlayer();
}
return player;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//releasePlayer();
Bundle b = intent.getBundleExtra(AppConstants.BUNDLE_KEY);
if (b != null) {
item = b.getParcelable(AppConstants.ITEM_KEY);
}
if (player == null) {
startPlayer();
}
return START_STICKY;
}
private void startPlayer() {
final Context context = this;
Uri uri = Uri.parse(item.getUrl());
player = ExoPlayerFactory.newSimpleInstance(context, new DefaultTrackSelector());
DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(context,
Util.getUserAgent(context, getString(R.string.app_name)));
CacheDataSourceFactory cacheDataSourceFactory = new CacheDataSourceFactory(
DownloadUtil.getCache(context),
dataSourceFactory,
CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);
MediaSource mediaSource = new ExtractorMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(uri);
player.prepare(mediaSource);
player.setPlayWhenReady(true);
playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(context, AppConstants.PLAYBACK_CHANNEL_ID,
R.string.playback_channel_name,
AppConstants.PLAYBACK_NOTIFICATION_ID,
new PlayerNotificationManager.MediaDescriptionAdapter() {
@Override
public String getCurrentContentTitle(Player player) {
return item.getTitle();
}
@Nullable
@Override
public PendingIntent createCurrentContentIntent(Player player) {
Intent intent = new Intent(context, PlayerActivity.class);
Bundle serviceBundle = new Bundle();
serviceBundle.putParcelable(AppConstants.ITEM_KEY, item);
intent.putExtra(AppConstants.BUNDLE_KEY, serviceBundle);
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
@Nullable
@Override
public String getCurrentContentText(Player player) {
return item.getSummary();
}
@Nullable
@Override
public Bitmap getCurrentLargeIcon(Player player, PlayerNotificationManager.BitmapCallback callback) {
return null;
}
}
);
playerNotificationManager.setNotificationListener(new PlayerNotificationManager.NotificationListener() {
@Override
public void onNotificationStarted(int notificationId, Notification notification) {
startForeground(notificationId, notification);
}
@Override
public void onNotificationCancelled(int notificationId) {
stopSelf();
}
});
playerNotificationManager.setPlayer(player);
}
public class LocalBinder extends Binder {
public AudioPlayerService getService() {
return AudioPlayerService.this;
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With