Не отображается полоса перемотки в Notification Player
Пишу музыкальный плеер и осталось разобраться совсем малость, это как вывести корректно плеер в уведомление.
Пробовал разные методы, разные комбинации но все тщетно.
Все что я пробовал не смогу написать здесь, но обычно у меня получается что-то вроде этого:

Описание самой проблемы: Плеер работает, переключает треки, останавливается, а так же воспроизводится по нажатию, но он не отображает длительность трека и перемотку (seekBar). А так же спустя некоторое время (Всегда по разному), примерно около 5 минут уведомление с плеером исчезает и достать его вновь не получается пока через настройки не закрою приложение. Что могло пойти не так??? Простите за код который режет вам глаза, но он тестовый и делался исключительно для тестов. Благодарю за понимание. P.s. Треки берутся прямыми ссылками на файл *.mp3.
Мой тестовый код :
public class MainActivity extends AppCompatActivity {
private Button b1, b2;
private TextView textViewStatePlayer;
private PlayerNotificationManager playerNotificationManager;
private SimpleExoPlayer exoPlayer;
private String urlAlbumLogo = "";
private Context context;
private PlayerNotificationManager pnm;
private int notificationId = 77432;
private MediaSession mediaSession;
@SuppressLint("ServiceCast")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
b1 = this.findViewById(R.id.b1);
b2 = this.findViewById(R.id.b2);
textViewStatePlayer = this.findViewById(R.id.textViewStatePlayer);
mediaSession = new MediaSession(context, "TAG_MUZMO");
exoPlayer = new SimpleExoPlayer.Builder(this)
.setUseLazyPreparation(false)
.setSkipSilenceEnabled(true)
.setDetachSurfaceTimeoutMs(0)
.build();
exoPlayer.addMediaItems(getMediaItemsTrack());
exoPlayer.setPlayWhenReady(false);
exoPlayer.prepare();
b1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Play
pnm = new PlayerNotificationManager.Builder(context, notificationId, "ch97")
.setChannelNameResourceId(R.string.not_id)
.setChannelDescriptionResourceId(R.string.not_desc)
.setMediaDescriptionAdapter(mediaDescriptionAdapter).build();
pnm.setPlayer(exoPlayer);
pnm.setMediaSessionToken(MediaSessionCompat.Token.fromToken(mediaSession.getSessionToken()));
pnm.setPriority(NotificationCompat.PRIORITY_MAX);
pnm.setVisibility(VISIBILITY_PUBLIC);
pnm.setUseNextAction(true);
pnm.setDefaults(1);
pnm.setUseChronometer(true);
textViewStatePlayer.setText("Воспроизводится");
exoPlayer.play();
}
});
b2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Pause
textViewStatePlayer.setText("Пауза");
exoPlayer.pause();
}
});
}
public final PlayerNotificationManager.MediaDescriptionAdapter mediaDescriptionAdapter = new PlayerNotificationManager.MediaDescriptionAdapter() {
@NonNull
@Override
public CharSequence getCurrentContentTitle(@NonNull Player player) {
return String.valueOf(player.getMediaMetadata().title);
}
@Override
public CharSequence getCurrentContentText(@NonNull Player player) {
return String.valueOf(player.getMediaMetadata().artist);
}
// Загрузка изображения из трека
private Bitmap bmap;
public PlayerNotificationManager.BitmapCallback callbackBM;
@SuppressLint("StaticFieldLeak")
class LoadBitMap extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... vd) {
try {
// Здесь берем ссылку на картинку текущего трека
bmap = Picasso.get().load(urlAlbumLogo).get();
} catch (IOException e) {
throw new RuntimeException(e);
}
return null;
}
@Override
protected void onPostExecute(Void v) {
super.onPostExecute(v);
if (bmap != null && callbackBM != null) {
callbackBM.onBitmap(bmap);
}
}
}
@Nullable
@Override
public PendingIntent createCurrentContentIntent(@NonNull Player player) {
// Определяет Действия по нажатию на уведомление
PendingIntent pendingIntent = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
pendingIntent = PendingIntent.getActivity(context,
777, new Intent(context, MainActivity.class).addFlags(
Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
} else {
pendingIntent = PendingIntent.getActivity(context,
777, new Intent(context, MainActivity.class).addFlags(
Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_UPDATE_CURRENT);
}
return pendingIntent;
}
@Nullable
@Override
public Bitmap getCurrentLargeIcon(Player player, PlayerNotificationManager.BitmapCallback callback) {
callbackBM = callback;
urlAlbumLogo = String.valueOf(player.getMediaMetadata().albumTitle);
LoadBitMap l = new LoadBitMap();
l.execute();
return null;
}
};
public static ArrayList<MediaItem> getMediaItemsTrack(){
Uri uri = Uri.parse("https://cs9-17v4.vkuseraudio.net/s/v1/acmp/r737HWHPprZNTdn7RfjQe0BqeV6i1ge0eTF7-8AMgH2cCCeWMclPemZsDq580zBQ2-Hx6Ex9ORhRF0WTGQkUZ7PUDwKsOOF2JHcg_Jx4Zx1GlVMK0P5SKRlQHCzfWugGccKFEErHLXQQiLVfRsE8Vy9-a44UAOQqGqu6uFaBEj9HaStP-Q.mp3?siren=1");
ArrayList<MediaItem> mediaItemArrayList = new ArrayList<>();
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
MediaMetadata.Builder metadata = new MediaMetadata.Builder();
metadata.setArtist("Strepsils Scot")
.setTitle("TITLE_MIDLE")
.setAlbumTitle("https://excel-home.ru/wp-content/uploads/img/800x0/b460795895283861941d4c40178c95a8.jpg")
.setMediaType(com.google.android.exoplayer2.MediaMetadata.MEDIA_TYPE_MUSIC);
MediaItem mitm = mediaItemBuilder.setMediaMetadata(metadata.build())
.setUri(uri).build();
mediaItemArrayList.add(mitm);
return mediaItemArrayList;
}
}
Ответы (1 шт):
Хотелось бы максимально подробно ответить на свой же вопрос. Ответ: Удалил все старые реализации ExoPlayer и добавил новые от Media3
implementation "androidx.media3:media3-exoplayer:1.2.0"
implementation "androidx.media3:media3-ui:1.2.0"
implementation "androidx.media3:media3-common:1.2.0"
implementation "androidx.media3:media3-session:1.2.0"
Далее я Создал класс медиа службы и переопределил onCreate() и объявил в классе сервиса плеер, контекст, PlayernotificationManager.Builder и т.д.
private static ExoPlayer player;
private MediaSession mediaSession;
private PlayerNotificationManager.Builder pnm;
private Context context;
private String urlAlbumLogo;
private PlayerView playerView;
public class PService extends MediaSessionService {
@Override
public void onCreate() {
super.onCreate();
}
@Nullable
@Override
public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
return null;
}
}
Далее сделал метод создания MediaItem листа внутри сервиса... (Для теста и примера)
public static ArrayList<MediaItem> getMediaItemsTrack(){
Uri[] uris = {
Uri.parse("");
};
ArrayList<MediaItem> mediaItemArrayList = new ArrayList<>();
for (Uri uri : uris) {
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
MediaMetadata.Builder metadata = new MediaMetadata.Builder();
metadata.setArtist("Подзаголовок уведомления")
.setTitle("Заголовок уведомления")
.setAlbumTitle("URL Картинки на уведомление")
.setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC);
MediaItem mitm = mediaItemBuilder.setMediaMetadata(metadata.build())
.setUri(uri).build();
mediaItemArrayList.add(mitm);
}
return mediaItemArrayList;
}
В onCreate() я инициализировал плеер, менеджеры и сессии вот так:
context = this;
startService(new Intent(context, AudioPlayerForegroundService.class));
Log.i("0x000001", "Запуск фоновой службы плеера");
// Init
player = new ExoPlayer.Builder(getApplicationContext()).build();
mediaSession = new MediaSession.Builder(context, player).build();
playerView = new PlayerView(context);
player.setMediaItems(getMediaItemsTrack());
player.setPlayWhenReady(false);
player.prepare();
//Create session
SessionToken sessionToken =
new SessionToken(context, new ComponentName(context, AudioPlayerForegroundService.class));
ListenableFuture<MediaController> controllerFuture =
new MediaController.Builder(context, sessionToken).buildAsync();
controllerFuture.addListener(() -> {
// Call controllerFuture.get() to retrieve the MediaController.
// MediaController implements the Player interface, so it can be
// attached to the PlayerView UI component.
try {
playerView.setPlayer(controllerFuture.get());
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
}, MoreExecutors.directExecutor());
// create Notification
pnm = new PlayerNotificationManager.Builder(context, 1997, "1997", mediaDescriptionAdapter);
в onGetSession вместо значения null я возвращаю mediaSession сделанную в onCreate()
И финал, в главной активности MainActivity.class Откуда стартует приложение, я запустил службу через намерение.
// Start Service
Intent intentStartFService = new Intent(this, AudioPlayerForegroundService.class);
startForegroundService(intentStartFService);
в onDestroy я удаляю сессию и прочую ненужную фигню.
@Override
public void onDestroy() {
super.onDestroy();
mediaSession.getPlayer().release();
mediaSession.release();
mediaSession = null;
}
И чуть не забыл... В манифесте не забудьте указать имя сервиса
<service
android:name=".AudioPlayerForegroundService"
android:exported="true"
android:foregroundServiceType="mediaPlayback"
android:permission="TODO"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService" />
</intent-filter>
</service>
Вкратце мой код выглядел таким образом:
public class AudioPlayerForegroundService extends MediaSessionService {
public AudioPlayerForegroundService() {
}
public static ExoPlayer getPlayer() {
return player;
}
private static ExoPlayer player;
private MediaSession mediaSession;
private PlayerNotificationManager.Builder pnm;
private Context context;
private String urlAlbumLogo;
private PlayerView playerView;
@OptIn(markerClass = UnstableApi.class) @Override
public void onCreate() {
super.onCreate();
context = this;
startService(new Intent(context, AudioPlayerForegroundService.class));
Log.i("0x000001", "Запуск фоновой службы плеера");
// Init
player = new ExoPlayer.Builder(getApplicationContext()).build();
mediaSession = new MediaSession.Builder(context, player).build();
playerView = new PlayerView(context);
player.setMediaItems(getMediaItemsTrack());
player.setPlayWhenReady(false);
player.prepare();
//Create session
SessionToken sessionToken =
new SessionToken(context, new ComponentName(context, AudioPlayerForegroundService.class));
ListenableFuture<MediaController> controllerFuture =
new MediaController.Builder(context, sessionToken).buildAsync();
controllerFuture.addListener(() -> {
// Call controllerFuture.get() to retrieve the MediaController.
// MediaController implements the Player interface, so it can be
// attached to the PlayerView UI component.
try {
playerView.setPlayer(controllerFuture.get());
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
}, MoreExecutors.directExecutor());
// create Notification
pnm = new PlayerNotificationManager.Builder(context, 1997, "1997", mediaDescriptionAdapter);
}
@Nullable
@Override
public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
return mediaSession;
}
public static ArrayList<MediaItem> getMediaItemsTrack(){
Uri[] uris = {
Uri.parse("ТУТ ССЫЛКИ НА ТРЕКИ"),
Uri.parse("ТУТ ССЫЛКИ НА ТРЕКИ"),
Uri.parse("ТУТ ССЫЛКИ НА ТРЕКИ"),
Uri.parse("ТУТ ССЫЛКИ НА ТРЕКИ"),
Uri.parse("ТУТ ССЫЛКИ НА ТРЕКИ"),
Uri.parse("ТУТ ССЫЛКИ НА ТРЕКИ"),
Uri.parse("ТУТ ССЫЛКИ НА ТРЕКИ"),
Uri.parse("ТУТ ССЫЛКИ НА ТРЕКИ"),
Uri.parse("ТУТ ССЫЛКИ НА ТРЕКИ"),
Uri.parse("ТУТ ССЫЛКИ НА ТРЕКИ"),
Uri.parse("ТУТ ССЫЛКИ НА ТРЕКИ"),
};
ArrayList<MediaItem> mediaItemArrayList = new ArrayList<>();
for (Uri uri : uris) {
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder();
MediaMetadata.Builder metadata = new MediaMetadata.Builder();
metadata.setArtist("Подзаголовок уведомления")
.setTitle("ЗАГОЛОВОК УВЕДОМЛЕНИЯ")
.setAlbumTitle("https://www.fonstola.ru/images/201312/fonstola.ru_139316.jpg")
.setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC);
MediaItem mitm = mediaItemBuilder.setMediaMetadata(metadata.build())
.setUri(uri).build();
mediaItemArrayList.add(mitm);
}
return mediaItemArrayList;
}
На этом всё. Было сложно даже написать это... Рад буду помочь если у кого возникнет похожая ситуация. Отдельное спасибо @MadocaMagica за предоставленную документацию
