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;
}
}
}
Привет. Подскажите плиз, как добавить сюда возможность проигровать файлы с файловой системы устройства, и с папки raw. (хотя-бы укажите направление куда копать)
Попробовал просто ссылку на аудио заменить на такого вида... Uri.parse("android.resource://com.none.rnar.testplayer/" + R.raw.us_big) .
Но это, естественно не помогло, что и не странно. Т.к насколько я понимаю, наверное где-то okhttp3 загружает аудиозапись по сети и сохраняет его во временную память, затем exoplayer как-то это играет. К сожалению я пока не могу определить точно ту часть кода где это происходит, и как. Было бы здорово если бы указали на эти места.
Так-же не могу понять вот что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))
последний параметр (duration), по названию можно догадаться что это продолжительность. Но почему она указывается в ручную, да ещё и способом умножения и сложения чисел. Зачем этот параметр здесь? В таком виде...
p.s
И если можно, ссылочку на примеры реализации различных плюшек от гугла, с этими либами (которые на java и актуальны) ибо я чука потерялся (первый раз работю с android). Спасибо! И да, простите за глупый вопрос, просто уже почти день мучаюсь с этим примером, что весьма и весьма печально =) Вот где я взял этот пример >>тык<<
Вообщем нужно было всеголишь
DataSource.Factory httpDataSourceFactory = new OkHttpDataSourceFactory(new OkHttpClient(), Util.getUserAgent(this, getString(R.string.app_name)), null);
заменить на
DataSource.Factory dataSourceFactory = new FileDataSourceFactory();
ну и путь заменил на такого рода Uri.parse("file:///sdcard/MyData/sagаobegile") Если вдруг кому пригодится...
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Ребята, не могу понять, почему падает приложение, помогите разобраться В задумке: оно должно выполнять два разных сценария в зависимости...
Элемент Listview не реагирует на длительное нажатие и не вызывается контекстное меню
Для алгоритма требуется найти производнуюНе подскажете, какой библиотекой или каким-то иным способом можно это сделать? В скриншоте первый...
Возможно ли как-то это осуществить? Например, программа сработала, консоль отобразила результат и пользователь введя букву "s" переносит...