package com.none.rnar.testplayer.service; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.BitmapFactory; import android.media.AudioAttributes; import android.media.AudioFocusRequest; import android.media.AudioManager; import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.IBinder; import android.support.annotation.Nullable; import android.support.v4.app.NotificationManagerCompat; import android.support.v4.content.ContextCompat; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaButtonReceiver; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; import android.support.v4.app.NotificationCompat; import android.support.v4.media.app.NotificationCompat.MediaStyle; import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSourceFactory; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.cache.Cache; import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory; import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor; import com.google.android.exoplayer2.upstream.cache.SimpleCache; import com.google.android.exoplayer2.util.Util; import okhttp3.OkHttpClient; import com.none.rnar.testplayer.R; import com.none.rnar.testplayer.ui.MainActivity; import java.io.File; final public class PlayerService extends Service { private final int NOTIFICATION_ID = 404; private final String NOTIFICATION_DEFAULT_CHANNEL_ID = "default_channel"; private final MediaMetadataCompat.Builder metadataBuilder = new MediaMetadataCompat.Builder(); private final PlaybackStateCompat.Builder stateBuilder = new PlaybackStateCompat.Builder().setActions( PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS ); private MediaSessionCompat mediaSession; private AudioManager audioManager; private AudioFocusRequest audioFocusRequest; private boolean audioFocusRequested = false; private SimpleExoPlayer exoPlayer; private ExtractorsFactory extractorsFactory; private DataSource.Factory dataSourceFactory; private final MusicRepository musicRepository = new MusicRepository(); @Override public void onCreate() { super.onCreate(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_DEFAULT_CHANNEL_ID, getString(R.string.notification_channel_name), NotificationManagerCompat.IMPORTANCE_DEFAULT); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.createNotificationChannel(notificationChannel); AudioAttributes audioAttributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build(); audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) .setOnAudioFocusChangeListener(audioFocusChangeListener) .setAcceptsDelayedFocusGain(false) .setWillPauseWhenDucked(true) .setAudioAttributes(audioAttributes) .build(); } audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); mediaSession = new MediaSessionCompat(this, "PlayerService"); mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); mediaSession.setCallback(mediaSessionCallback); Context appContext = getApplicationContext(); Intent activityIntent = new Intent(appContext, MainActivity.class); mediaSession.setSessionActivity(PendingIntent.getActivity(appContext, 0, activityIntent, 0)); Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null, appContext, MediaButtonReceiver.class); mediaSession.setMediaButtonReceiver(PendingIntent.getBroadcast(appContext, 0, mediaButtonIntent, 0)); exoPlayer = ExoPlayerFactory.newSimpleInstance(new DefaultRenderersFactory(this), new DefaultTrackSelector(), new DefaultLoadControl()); exoPlayer.addListener(exoPlayerListener); DataSource.Factory httpDataSourceFactory = new OkHttpDataSourceFactory(new OkHttpClient(), Util.getUserAgent(this, getString(R.string.app_name)), null); Cache cache = new SimpleCache(new File(this.getCacheDir().getAbsolutePath() + "/exoplayer"), new LeastRecentlyUsedCacheEvictor(1024 * 1024 * 100)); // 100 Mb max this.dataSourceFactory = new CacheDataSourceFactory(cache, httpDataSourceFactory, CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR); this.extractorsFactory = new DefaultExtractorsFactory(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { MediaButtonReceiver.handleIntent(mediaSession, intent); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); mediaSession.release(); exoPlayer.release(); } private MediaSessionCompat.Callback mediaSessionCallback = new MediaSessionCompat.Callback() { private Uri currentUri; int currentState = PlaybackStateCompat.STATE_STOPPED; @Override public void onPlay() { if (!exoPlayer.getPlayWhenReady()) { startService(new Intent(getApplicationContext(), PlayerService.class)); MusicRepository.Track track = musicRepository.getCurrent(); updateMetadataFromTrack(track); prepareToPlay(track.getUri()); if (!audioFocusRequested) { audioFocusRequested = true; int audioFocusResult; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { audioFocusResult = audioManager.requestAudioFocus(audioFocusRequest); } else { audioFocusResult = audioManager.requestAudioFocus(audioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); } if (audioFocusResult != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) return; } mediaSession.setActive(true); // Сразу после получения фокуса registerReceiver(becomingNoisyReceiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); exoPlayer.setPlayWhenReady(true); } mediaSession.setPlaybackState(stateBuilder.setState(PlaybackStateCompat.STATE_PLAYING, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1).build()); currentState = PlaybackStateCompat.STATE_PLAYING; refreshNotificationAndForegroundStatus(currentState); } @Override public void onPause() { if (exoPlayer.getPlayWhenReady()) { exoPlayer.setPlayWhenReady(false); unregisterReceiver(becomingNoisyReceiver); } mediaSession.setPlaybackState(stateBuilder.setState(PlaybackStateCompat.STATE_PAUSED, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1).build()); currentState = PlaybackStateCompat.STATE_PAUSED; refreshNotificationAndForegroundStatus(currentState); } @Override public void onStop() { if (exoPlayer.getPlayWhenReady()) { exoPlayer.setPlayWhenReady(false); unregisterReceiver(becomingNoisyReceiver); } if (audioFocusRequested) { audioFocusRequested = false; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { audioManager.abandonAudioFocusRequest(audioFocusRequest); } else { audioManager.abandonAudioFocus(audioFocusChangeListener); } } mediaSession.setActive(false); mediaSession.setPlaybackState(stateBuilder.setState(PlaybackStateCompat.STATE_STOPPED, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1).build()); currentState = PlaybackStateCompat.STATE_STOPPED; refreshNotificationAndForegroundStatus(currentState); stopSelf(); } @Override public void onSkipToNext() { MusicRepository.Track track = musicRepository.getNext(); updateMetadataFromTrack(track); refreshNotificationAndForegroundStatus(currentState); prepareToPlay(track.getUri()); } @Override public void onSkipToPrevious() { MusicRepository.Track track = musicRepository.getPrevious(); updateMetadataFromTrack(track); refreshNotificationAndForegroundStatus(currentState); prepareToPlay(track.getUri()); } private void prepareToPlay(Uri uri) { if (!uri.equals(currentUri)) { currentUri = uri; ExtractorMediaSource mediaSource = new ExtractorMediaSource(uri, dataSourceFactory, extractorsFactory, null, null); exoPlayer.prepare(mediaSource); } } private void updateMetadataFromTrack(MusicRepository.Track track) { metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, BitmapFactory.decodeResource(getResources(), track.getBitmapResId())); metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, track.getTitle()); metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, track.getArtist()); metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, track.getArtist()); metadataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, track.getDuration()); mediaSession.setMetadata(metadataBuilder.build()); } }; private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() { @Override public void onAudioFocusChange(int focusChange) { switch (focusChange) { case AudioManager.AUDIOFOCUS_GAIN: mediaSessionCallback.onPlay(); // Не очень красиво break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: mediaSessionCallback.onPause(); break; default: mediaSessionCallback.onPause(); break; } } }; private final BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // Disconnecting headphones - stop playback if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) { mediaSessionCallback.onPause(); } } }; private ExoPlayer.EventListener exoPlayerListener = new ExoPlayer.EventListener() { @Override public void onTimelineChanged(Timeline timeline, Object manifest) {} @Override public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {} @Override public void onLoadingChanged(boolean isLoading) {} @Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { if (playWhenReady && playbackState == ExoPlayer.STATE_ENDED) { mediaSessionCallback.onSkipToNext(); } } @Override public void onPlayerError(ExoPlaybackException error) {} @Override public void onPositionDiscontinuity() {} @Override public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {} }; @Nullable @Override public IBinder onBind(Intent intent) { return new PlayerServiceBinder(); } public class PlayerServiceBinder extends Binder { public MediaSessionCompat.Token getMediaSessionToken() { return mediaSession.getSessionToken(); } } private void refreshNotificationAndForegroundStatus(int playbackState) { switch (playbackState) { case PlaybackStateCompat.STATE_PLAYING: { startForeground(NOTIFICATION_ID, getNotification(playbackState)); break; } case PlaybackStateCompat.STATE_PAUSED: { NotificationManagerCompat.from(PlayerService.this).notify(NOTIFICATION_ID, getNotification(playbackState)); stopForeground(false); break; } default: { stopForeground(true); break; } } } private Notification getNotification(int playbackState) { NotificationCompat.Builder builder = MediaStyleHelper.from(this, mediaSession); builder.addAction(new NotificationCompat.Action(android.R.drawable.ic_media_previous, getString(R.string.previous), MediaButtonReceiver.buildMediaButtonPendingIntent(this, PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS))); if (playbackState == PlaybackStateCompat.STATE_PLAYING) builder.addAction(new NotificationCompat.Action(android.R.drawable.ic_media_pause, getString(R.string.pause), MediaButtonReceiver.buildMediaButtonPendingIntent(this, PlaybackStateCompat.ACTION_PLAY_PAUSE))); else builder.addAction(new NotificationCompat.Action(android.R.drawable.ic_media_play, getString(R.string.play), MediaButtonReceiver.buildMediaButtonPendingIntent(this, PlaybackStateCompat.ACTION_PLAY_PAUSE))); builder.addAction(new NotificationCompat.Action(android.R.drawable.ic_media_next, getString(R.string.next), MediaButtonReceiver.buildMediaButtonPendingIntent(this, PlaybackStateCompat.ACTION_SKIP_TO_NEXT))); builder.setStyle(new MediaStyle() .setShowActionsInCompactView(1) .setShowCancelButton(true) .setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(this, PlaybackStateCompat.ACTION_STOP)) .setMediaSession(mediaSession.getSessionToken())); // setMediaSession требуется для Android Wear builder.setSmallIcon(R.mipmap.ic_launcher); builder.setColor(ContextCompat.getColor(this, R.color.colorPrimaryDark)); // The whole background (in MediaStyle), not just icon background builder.setShowWhen(false); builder.setPriority(NotificationCompat.PRIORITY_HIGH); builder.setOnlyAlertOnce(true); builder.setChannelId(NOTIFICATION_DEFAULT_CHANNEL_ID); return builder.build(); } } package com.none.rnar.testplayer.service; import android.net.Uri; import com.none.rnar.testplayer.R; final class MusicRepository { private final Track[] data = { new Track("Triangle", "Jason Shaw", R.drawable.ic_launcher_foreground, Uri.parse("android.resource://com.none.rnar.testplayer/" + R.raw.us_big), (3 * 60 + 41) * 1000), new Track("Rubix Cube", "Jason Shaw", R.drawable.ic_launcher_background, Uri.parse("android.resource://com.none.rnar.testplayer/" + R.raw.us_black), (3 * 60 + 44) * 1000), new Track("MC Ballad S Early Eighties", "Frank Nora", R.drawable.ic_launcher_background, Uri.parse("android.resource://com.none.rnar.testplayer/" + R.raw.us_blue), (2 * 60 + 50) * 1000), new Track("Folk Song", "Brian Boyko", R.drawable.ic_launcher_background, Uri.parse("android.resource://com.none.rnar.testplayer/" + R.raw.us_child), (3 * 60 + 5) * 1000), new Track("Morning Snowflake", "Kevin MacLeod", R.drawable.ic_launcher_foreground, Uri.parse("android.resource://com.none.rnar.testplayer/" + R.raw.saga), (2 * 60 + 0) * 1000), }; private final int maxIndex = data.length - 1; private int currentItemIndex = 0; Track getNext() { if (currentItemIndex == maxIndex) currentItemIndex = 0; else currentItemIndex++; return getCurrent(); } Track getPrevious() { if (currentItemIndex == 0) currentItemIndex = maxIndex; else currentItemIndex--; return getCurrent(); } Track getCurrent() { return data[currentItemIndex]; } static class Track { private String title; private String artist; private int bitmapResId; private Uri uri; private long duration; // in ms Track(String title, String artist, int bitmapResId, Uri uri, long duration) { this.title = title; this.artist = artist; this.bitmapResId = bitmapResId; this.uri = uri; this.duration = duration; } String getTitle() { return title; } String getArtist() { return artist; } int getBitmapResId() { return bitmapResId; } Uri getUri() { return uri; } long getDuration() { return duration; } } } Hey. Tell me pliz how to add here the ability to play files from the file system of the device, and from the raw folder. (at least indicate the direction where to dig)
I tried to simply replace the link to audio with this type ... Uri.parse ("android.resource: //com.none.rnar.testplayer/" + R.raw.us.ig). But this naturally did not help, which is not strange. As far as I understand, probably somewhere okhttp3 loads an audio recording over the network and saves it into temporary memory, then exoplayer somehow plays it. Unfortunately, I still can not determine exactly the part of the code where this is happening, and how. It would be great if pointed to these places. So I can not understand this: new Track("Triangle", "Jason Shaw", R.drawable.ic_launcher_foreground, Uri.parse("android.resource://com.none.rnar.testplayer/" + R.raw.us_big), (3 * 60 + 41) * 1000)) last parameter (duration), by name you can guess what is the duration. But why is it indicated in the manual, and yes even the way of multiplying and adding numbers. Why is this option here? In this form ... ps
And if it is possible, a reference to the examples of the implementation of various buns from Google, with these libs (which are relevant to java), because I lost it (I’ve been working with android for the first time). Thank! And yes, sorry for the stupid question, I have been suffering for almost a day with this example, which is very, very sad =) That's where I took this example >> tyk <<