Java tutorial
/* * Author: Scott Ware <scoot.software@gmail.com> * Copyright (c) 2015 Scott Ware * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.scooter1556.sms.lib.android.service; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.media.AudioManager; import android.media.AudioManager.OnAudioFocusChangeListener; import android.net.Uri; import android.net.wifi.WifiManager; import android.os.Binder; import android.os.IBinder; import android.os.PowerManager; import android.preference.PreferenceManager; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; import android.util.Log; import android.view.KeyEvent; import android.widget.Toast; import com.bumptech.glide.Glide; import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.target.SimpleTarget; import com.google.android.exoplayer.ExoPlayer; import com.google.gson.Gson; import com.loopj.android.http.JsonHttpResponseHandler; import com.scooter1556.sms.lib.android.R; import com.scooter1556.sms.lib.android.domain.MediaElement; import com.scooter1556.sms.lib.android.domain.TranscodeProfile; import com.scooter1556.sms.lib.android.player.SMSAudioPlayer; import com.scooter1556.sms.lib.android.utils.MediaUtils; import org.json.JSONObject; import java.util.ArrayList; public class AudioPlayerService extends Service implements SMSAudioPlayer.Listener { private static final String TAG = "AudioPlayerService"; static final String SUPPORTED_FILES = "aac,m4a,mp3,oga,ogg"; static final String SUPPORTED_CODECS = "aac,mp3,vorbis"; static final String MCH_CODECS = "ac3"; static final int MAX_SAMPLE_RATE = 48000; public static final float VOLUME_DUCK = 0.2f; public static final float VOLUME_NORMAL = 1.0f; public static final float VOLUME_OFF = 0.0f; // REST Client private RESTService restService = null; // Media Player private SMSAudioPlayer player; // Media Session private MediaSessionCompat mediaSession; private MediaSessionCallback mediaSessionCallback; private int mediaState; // Audio Manager private AudioManager audioManager; // Receivers BroadcastReceiver audioManagerReceiver; // Device Locks private PowerManager.WakeLock wakeLock; private WifiManager.WifiLock wifiLock; // Listeners private final ArrayList<AudioPlayerListener> listeners = new ArrayList<>(); // Binder private final IBinder audioPlayerBind = new AudioPlayerBinder(); // Audio List private ArrayList<MediaElement> mediaElementList; // Current Position private int currentListPosition; // Flags private boolean isPaused = false; public AudioPlayerService() { } @Override public void onCreate() { // Create the service super.onCreate(); restService = RESTService.getInstance(); currentListPosition = 0; mediaElementList = new ArrayList<>(); // Setup media session mediaSessionCallback = new MediaSessionCallback(); mediaSession = new MediaSessionCompat(this, "SMSPlayer"); mediaSession.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); mediaSession.setCallback(mediaSessionCallback); mediaState = PlaybackStateCompat.STATE_NONE; updatePlaybackState(); audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "sms"); wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)).createWifiLock(WifiManager.WIFI_MODE_FULL, "sms"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_NOT_STICKY; } @Override public void onDestroy() { super.onDestroy(); cleanupPlayer(); mediaSession.release(); } @Override public IBinder onBind(Intent intent) { return audioPlayerBind; } @Override public boolean onUnbind(Intent intent) { return true; } public class AudioPlayerBinder extends Binder { public AudioPlayerService getService() { return AudioPlayerService.this; } } // Listener Management public void registerListener(AudioPlayerListener listener) { listeners.add(listener); } public void unregisterListener(AudioPlayerListener listener) { listeners.remove(listener); } private OnAudioFocusChangeListener audioFocusListener = new OnAudioFocusChangeListener() { public void onAudioFocusChange(int focusChange) { if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) { // Pause playback pause(); } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { // Lower the volume if (player != null) { player.setVolume(VOLUME_DUCK); } } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { // Resume playback if (player != null) { player.setVolume(VOLUME_NORMAL); if (isPaused()) { start(); } } } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { audioManager.abandonAudioFocus(audioFocusListener); // Stop playback stop(); } } }; public void play() { if (mediaElementList.size() <= currentListPosition) { return; } cleanupPlayer(); initialiseTrack(currentListPosition); // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaylistPositionChanged(); } } public void playAll(ArrayList<MediaElement> mediaElements) { if (mediaElements == null) { return; } if (mediaElements.size() == 0) { return; } // Set playlist and start playing first track mediaElementList.clear(); mediaElementList.addAll(mediaElements); currentListPosition = 0; cleanupPlayer(); initialiseTrack(currentListPosition); // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaylistChanged(); listener.PlaylistPositionChanged(); } } public void addAndPlay(MediaElement mediaElement) { mediaElementList.clear(); mediaElementList.add(mediaElement); currentListPosition = 0; cleanupPlayer(); initialiseTrack(currentListPosition); // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaylistChanged(); listener.PlaylistPositionChanged(); } } public void addAndPlayNext(MediaElement mediaElement) { if (mediaElementList.isEmpty()) { mediaElementList.add(mediaElement); currentListPosition = 0; cleanupPlayer(); initialiseTrack(currentListPosition); // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaylistChanged(); listener.PlaylistPositionChanged(); } } else { mediaElementList.add(currentListPosition + 1, mediaElement); // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaylistChanged(); } } } public void addAllAndPlayNext(ArrayList<MediaElement> mediaElements) { if (mediaElementList.isEmpty()) { this.mediaElementList.addAll(mediaElements); currentListPosition = 0; cleanupPlayer(); initialiseTrack(currentListPosition); // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaylistChanged(); listener.PlaylistPositionChanged(); } } else { mediaElementList.addAll(currentListPosition + 1, mediaElements); // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaylistChanged(); } } } public void addAllToQueue(ArrayList<MediaElement> mediaElements) { if (mediaElements == null) { return; } boolean queueTrack = currentListPosition == (mediaElementList.size() - 1); this.mediaElementList.addAll(mediaElements); // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaylistChanged(); } } public void addToQueue(MediaElement mediaElement) { boolean queueTrack = currentListPosition == (mediaElementList.size() - 1); mediaElementList.add(mediaElement); // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaylistChanged(); } } public void removeMediaElementFromList(long id) { Integer position = null; // Find the element id in the playlist for (int i = 0; i < mediaElementList.size(); i++) { if (mediaElementList.get(i).getID().equals(id)) { position = i; mediaElementList.remove(i); } } // Don't continue if the element isn't found if (position == null) { return; } // If the track being removed is before the current playing track we need to re-sync the list position. if (player != null && position < currentListPosition) { currentListPosition--; position--; } if (player != null && position == currentListPosition) { stop(); } // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaylistChanged(); listener.PlaylistPositionChanged(); } } public void clearMediaList() { mediaElementList.clear(); currentListPosition = 0; isPaused = false; cleanupPlayer(); // Update state mediaState = PlaybackStateCompat.STATE_NONE; updatePlaybackState(); // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaylistChanged(); listener.PlaybackStateChanged(); listener.PlaylistPositionChanged(); } } public void setMediaListPosition(int position) { currentListPosition = position; } public void setMediaListPosition(MediaElement element) { if (mediaElementList.contains(element)) { currentListPosition = mediaElementList.indexOf(element); } } public int getMediaListPosition() { return currentListPosition; } public ArrayList<MediaElement> getMediaList() { return mediaElementList; } public MediaElement getMediaElement() { if (mediaElementList.isEmpty()) { return null; } else { return mediaElementList.get(currentListPosition); } } public void initialiseTrack(int position) { // Get settings final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); // Get media element to play final MediaElement mediaElement = mediaElementList.get(position); if (mediaElement == null) { Toast error = Toast.makeText(getApplicationContext(), getString(R.string.error_media_playback), Toast.LENGTH_SHORT); error.show(); return; } // Initialise Stream restService.initialiseStream(getApplicationContext(), mediaElement.getID(), SUPPORTED_FILES, SUPPORTED_CODECS, null, null, Integer.parseInt(settings.getString("pref_audio_quality", "0")), MAX_SAMPLE_RATE, null, null, settings.getBoolean("pref_direct_play", false), new JsonHttpResponseHandler() { @Override public void onSuccess(int statusCode, cz.msebera.android.httpclient.Header[] headers, JSONObject response) { // Initialise player player = new SMSAudioPlayer(); // Update state mediaState = PlaybackStateCompat.STATE_CONNECTING; updatePlaybackState(); // Parse profile Gson parser = new Gson(); TranscodeProfile profile = parser.fromJson(response.toString(), TranscodeProfile.class); // Setup Player player.setID(mediaElement.getID()); player.setDuration(mediaElement.getDuration()); player.setTranscodeProfile(profile); player.setQuality(Integer.parseInt(settings.getString("pref_audio_quality", "0"))); // Streaming status if (profile.getType() == TranscodeProfile.StreamType.FILE) { player.setStreaming(false); } else { player.setStreaming(true); } try { preparePlayer(); isPaused = false; // Update metadata mediaSession.setMetadata(MediaUtils.getMediaMetadata(mediaElement, null)); // Attempt to get album art Glide.with(getApplicationContext()) .load(RESTService.getInstance().getConnection().getUrl() + "/image/" + mediaElement.getID() + "/cover/300") .asBitmap().into(new SimpleTarget<Bitmap>() { @Override public void onResourceReady(Bitmap image, GlideAnimation<? super Bitmap> glideAnimation) { // Update metadata with artwork mediaSession .setMetadata(MediaUtils.getMediaMetadata(mediaElement, image)); } }); // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaybackStateChanged(); listener.PlaylistPositionChanged(); } } catch (Exception e) { cleanupPlayer(); } } @Override public void onFailure(int statusCode, cz.msebera.android.httpclient.Header[] headers, Throwable throwable, JSONObject response) { error(); } private void error() { Toast error = Toast.makeText(getApplicationContext(), getString(R.string.error_media_playback), Toast.LENGTH_SHORT); error.show(); currentListPosition = 0; // Update state mediaState = PlaybackStateCompat.STATE_ERROR; updatePlaybackState(); // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaybackStateChanged(); listener.PlaylistPositionChanged(); } } }); } private void preparePlayer() { // Request audio focus for playback int result = audioManager.requestAudioFocus(audioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { // Start playback String streamUrl = restService.getConnection().getUrl() + "/stream/" + player.getTranscodeProfile().getID() + "?offset=" + (int) (player.getOffset() * 0.001); Uri contentUri = Uri.parse(streamUrl); player.addListener(this); player.initialise(this, contentUri, true); wakeLock.acquire(); wifiLock.acquire(); if (!mediaSession.isActive()) { mediaSession.setActive(true); } // Register receiver for audio manager events registerAudioManagerReceiver(); } } private void cleanupPlayer() { if (player != null) { if (player.getTranscodeProfile() != null) { restService.endJob(player.getTranscodeProfile().getID()); } // Abandon audio focus audioManager.abandonAudioFocus(audioFocusListener); player.release(); player = null; // Unregister audio manager receiver unregisterReceiver(audioManagerReceiver); if (wakeLock.isHeld()) { wakeLock.release(); } if (wifiLock.isHeld()) { wifiLock.release(); } } } // // Helper Functions // private void updatePlaybackState() { // Check media session if (mediaSession == null) { return; } // Update state //noinspection WrongConstant mediaSession.setPlaybackState(new PlaybackStateCompat.Builder() .setState(mediaState, getCurrentPosition() * 1000, 1.0f).setActions(getAvailableActions()).build()); } private long getAvailableActions() { long actions = PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID; if (mediaElementList == null || mediaElementList.isEmpty()) { return actions; } if (mediaState == PlaybackStateCompat.STATE_PLAYING) { actions |= PlaybackStateCompat.ACTION_PAUSE; } else { actions |= PlaybackStateCompat.ACTION_PLAY; } if (currentListPosition > 0) { actions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; } if (currentListPosition < mediaElementList.size() - 1) { actions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT; } return actions; } public MediaSessionCompat.Token getSessionToken() { if (mediaSession == null) { return null; } return mediaSession.getSessionToken(); } // // Player Control // public void playPrev() { if (mediaElementList == null) { return; } if (mediaElementList.size() > (currentListPosition - 1) && (currentListPosition - 1) >= 0) { // Update state mediaState = PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS; updatePlaybackState(); currentListPosition--; cleanupPlayer(); initialiseTrack(currentListPosition); } // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaybackStateChanged(); listener.PlaylistPositionChanged(); } } public void playNext() { if (mediaElementList == null) { return; } if (mediaElementList.size() > (currentListPosition + 1)) { // Update state mediaState = PlaybackStateCompat.STATE_SKIPPING_TO_NEXT; updatePlaybackState(); currentListPosition++; cleanupPlayer(); initialiseTrack(currentListPosition); } // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaybackStateChanged(); listener.PlaylistPositionChanged(); } } public long getCurrentPosition() { if (player == null) { return 0; } return player.getCurrentPosition(); } public long getDuration() { if (player == null) { return -1; } return player.getDuration(); } public boolean isPlaying() { if (player == null) { return false; } return player.isPlaying(); } public boolean isPaused() { return isPaused; } public void pause() { if (player == null) { return; } player.pause(); isPaused = true; if (wakeLock.isHeld()) { wakeLock.release(); } if (wifiLock.isHeld()) { wifiLock.release(); } // Update state mediaState = PlaybackStateCompat.STATE_PAUSED; updatePlaybackState(); // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaybackStateChanged(); } } public void stop() { if (player == null) { return; } cleanupPlayer(); currentListPosition = 0; isPaused = false; // Update state mediaState = PlaybackStateCompat.STATE_STOPPED; updatePlaybackState(); // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaybackStateChanged(); listener.PlaylistPositionChanged(); } } public void seek(int position) { if (player == null) { return; } if (player.isStreaming()) { // Generate new stream uri String streamUrl = restService.getConnection().getUrl() + "/stream/" + player.getTranscodeProfile().getID() + "?offset=" + position; Uri contentUri = Uri.parse(streamUrl); // Initialise player in new position player.release(); player.setOffset(position); player.initialise(this, contentUri, !isPaused()); } else { player.seek(position); } } public void start() { if (mediaState == PlaybackStateCompat.STATE_STOPPED) { play(); } else if (mediaState == PlaybackStateCompat.STATE_PAUSED) { player.start(); // Update state mediaState = PlaybackStateCompat.STATE_PLAYING; updatePlaybackState(); wakeLock.acquire(); wifiLock.acquire(); } isPaused = false; // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaybackStateChanged(); } } // // Player Callbacks // @Override public void onStateChanged(SMSAudioPlayer player, boolean playWhenReady, int playbackState) { switch (playbackState) { case ExoPlayer.STATE_BUFFERING: // Update state mediaState = PlaybackStateCompat.STATE_BUFFERING; updatePlaybackState(); break; case ExoPlayer.STATE_ENDED: // Clean-up resources cleanupPlayer(); if (mediaElementList.size() > (currentListPosition + 1)) { currentListPosition++; initialiseTrack(currentListPosition); // Update state mediaState = PlaybackStateCompat.STATE_SKIPPING_TO_NEXT; updatePlaybackState(); } else { // Playlist has ended cleanupPlayer(); currentListPosition = 0; // Update state mediaState = PlaybackStateCompat.STATE_STOPPED; updatePlaybackState(); } // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaybackStateChanged(); listener.PlaylistPositionChanged(); } break; case ExoPlayer.STATE_IDLE: break; case ExoPlayer.STATE_PREPARING: // Update state mediaState = PlaybackStateCompat.STATE_CONNECTING; updatePlaybackState(); break; case ExoPlayer.STATE_READY: if (playWhenReady) { // Update state mediaState = PlaybackStateCompat.STATE_PLAYING; updatePlaybackState(); } break; default: break; } } @Override public void onError(Exception e) { cleanupPlayer(); currentListPosition = 0; // Update state mediaState = PlaybackStateCompat.STATE_ERROR; updatePlaybackState(); // Update listeners for (AudioPlayerListener listener : listeners) { listener.PlaybackStateChanged(); listener.PlaylistPositionChanged(); } } private void registerAudioManagerReceiver() { IntentFilter filter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); audioManagerReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) { // Pause playback pause(); } } }; registerReceiver(audioManagerReceiver, filter); } private class MediaSessionCallback extends MediaSessionCompat.Callback { @Override public void onPlay() { start(); } @Override public void onSeekTo(long position) { seek((int) position); } @Override public void onPause() { pause(); } @Override public void onStop() { stop(); } @Override public void onSkipToNext() { playNext(); } @Override public void onSkipToPrevious() { playPrev(); } @Override public boolean onMediaButtonEvent(Intent intent) { if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) { KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); if (event.getAction() == KeyEvent.ACTION_DOWN) { if (KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE == event.getKeyCode()) { if (isPlaying()) { pause(); } else { start(); } } else if (KeyEvent.KEYCODE_MEDIA_PLAY == event.getKeyCode()) { start(); } else if (KeyEvent.KEYCODE_MEDIA_PAUSE == event.getKeyCode()) { pause(); } else if (KeyEvent.KEYCODE_MEDIA_NEXT == event.getKeyCode()) { playNext(); } else if (KeyEvent.KEYCODE_MEDIA_PREVIOUS == event.getKeyCode()) { playPrev(); } } } return true; } } public interface AudioPlayerListener { void PlaybackStateChanged(); void PlaylistPositionChanged(); void PlaylistChanged(); } }