/*
* This is a simple foregroundservice example using Android 2.0 API. For more information, backward compatibility, etc. visit:
* http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.html
*/
package org.freemp.droid.player;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.media.AudioManager;
import android.media.MediaMetadataRetriever;
import android.media.RemoteControlClient;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
import com.flurry.android.FlurryAgent;
import com.un4seen.bass.BASS;
import org.freemp.droid.ClsTrack;
import org.freemp.droid.FileUtils;
import org.freemp.droid.MediaUtils;
import org.freemp.droid.NotificationUtils;
import org.freemp.droid.playlist.MakePlaylistFS;
import java.io.File;
import java.util.ArrayList;
import java.util.Random;
public class ServicePlayer extends Service implements AudioManager.OnAudioFocusChangeListener {
private static final int NOTIFICATION_ID = 101;
// Bass Service Binder
private final IBinder mBinder = new BassServiceBinder();
public long startVolumeUpFlag;
ClsTrack currentTrack = null;
// our RemoteControlClient object, which will use remote control APIs available in
// SDK level >= 14, if they're available.
RemoteControlClient remoteControlClient = null;
Handler timerHandler = new Handler();
// Notification
private Notification notification;
// Pending Intent to be called if a user click on the notification
private PendingIntent pendIntent;
private boolean repeat;
private int errorCount = 0;
//media button counter
private long mediabtnLastEventTime = 0;
private int mediabtnPressCounter = 0;
// Channel Handle
private int chan;
//TrackList
private ArrayList<ClsTrack> tracks = new ArrayList<ClsTrack>();
//currentPosition
private int position = 0;
// Activity with implemented BassInterface
private InterfacePlayer activity;
private int screenHeight, screenWidth;
// Properties: BassInterface
private String plugins;
private double duration = 0.0;
private double progress = 0.0;
private boolean shuffle = false;
private boolean firstVolumeUpFlag;
private boolean activityStarted;
Runnable timerRunnable = new Runnable() {
@Override
public void run() {
if (BASS.BASS_ChannelIsActive(chan) == BASS.BASS_ACTIVE_PLAYING) {
if (activity != null && activityStarted) {
progress = BASS.BASS_ChannelBytes2Seconds(chan, BASS.BASS_ChannelGetPosition(chan, BASS.BASS_POS_BYTE));
activity.onProgressChanged(progress);
}
}
timerHandler.postDelayed(this, 200);//looks like laggy timer on more then 200 values
}
};
final BASS.SYNCPROC EndSync = new BASS.SYNCPROC() {
public void SYNCPROC(int handle, int channel, int data, Object user) {
if (!isRepeat())
playNext();
else
play(position);
}
};
private boolean isUnpluggedFlag;
private TelephonyManager tm;
private MyBroadcastReceiver myBroadcastReceiver;
private AudioManager mAudioManager;
private PhoneStateListener telephone = new PhoneStateListener() {
boolean onhook = false;
RING_STATE callstaet;
public void onCallStateChanged(int state, String number) {
switch (state) {
case TelephonyManager.CALL_STATE_RINGING: {
callstaet = RING_STATE.STATE_RINGING;
if (isPlaying()) {
pause();
onhook = true;
//setResumeStop(CALL_RESUME);
}
}
break;
case TelephonyManager.CALL_STATE_OFFHOOK: {
if (callstaet == RING_STATE.STATE_RINGING) {
callstaet = RING_STATE.STATE_OFFHOOK;
} else {
callstaet = RING_STATE.STATE_NORMAL;
if (isPlaying()) {
pause();
onhook = true;
//setResumeStop(CALL_RESUME);
}
}
}
break;
case TelephonyManager.CALL_STATE_IDLE: {
if (onhook) {
onhook = false;
if (isPaused())
playFromPause();
//setResumeStart(5, CALL_RESUME);
}
callstaet = RING_STATE.STATE_NORMAL;
}
break;
default: {
}
}
}
};
public boolean isShuffle() {
return shuffle;
}
public void setShuffle(boolean shuffle) {
this.shuffle = shuffle;
}
public boolean isRepeat() {
return repeat;
}
public void setRepeat(boolean repeat) {
this.repeat = repeat;
}
public void setActivityStarted(boolean activityStarted) {
this.activityStarted = activityStarted;
}
@Override
public void onAudioFocusChange(int focusChange) {
}
public int getPlayingPosition() {
return position;
}
// Set Activity
public void setActivity(InterfacePlayer activity) {
this.activity = activity;
if (activity != null) {
activity.onPluginsLoaded(plugins);
activity.onFileLoaded(null, duration, "", "", 0, 0);
activity.onProgressChanged(progress);
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void onCreate() {
super.onCreate();
// initialize default output device
if (!BASS.BASS_Init(-1, 44100, 0)) {
return;
}
// look for plugins
plugins = "";
String path = getApplicationInfo().nativeLibraryDir;
String[] list = new File(path).list();
for (String s : list) {
int plug = BASS.BASS_PluginLoad(path + "/" + s, 0);
if (plug != 0) { // plugin loaded...
plugins += s + "\n"; // add it to the list
}
}
if (plugins.equals("")) plugins = "no plugins - visit the BASS webpage to get some\n";
if (activity != null) {
activity.onPluginsLoaded(plugins);
}
BASS.BASS_SetConfig(BASS.BASS_CONFIG_BUFFER, 1000);
Log.w("BASS.BASS_CONFIG_BUFFER", "" + BASS.BASS_GetConfig(BASS.BASS_CONFIG_BUFFER));
//screen
screenHeight = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getInt("screenHeight", 1000);
screenWidth = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getInt("screenWidth", 800);
// Pending Intend
Intent intent = new Intent(this, ActPlayer.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
pendIntent = PendingIntent.getActivity(this, 0, intent, 0);
//tracklist
updateTrackList();
tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
tm.listen(telephone, PhoneStateListener.LISTEN_CALL_STATE);
myBroadcastReceiver = new MyBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
intentFilter.addAction("android.media.VOLUME_CHANGED_ACTION");
intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
registerReceiver(myBroadcastReceiver, intentFilter);
mAudioManager = (AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
ComponentName rcvMedia = new ComponentName(getPackageName(), RcvMediaControl.class.getName());
mAudioManager.registerMediaButtonEventReceiver(rcvMedia);
// Use the remote control APIs (if available) to set the playback state
if (android.os.Build.VERSION.SDK_INT >= 14 && remoteControlClient == null) {
registerRemoteControl(rcvMedia);
}
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
void registerRemoteControl(ComponentName rcvMedia) {
mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(rcvMedia);
PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
0, mediaButtonIntent, 0);
remoteControlClient = new RemoteControlClient(mediaPendingIntent);
remoteControlClient.setTransportControlFlags(
RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE |
RemoteControlClient.FLAG_KEY_MEDIA_NEXT |
RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
);
mAudioManager.registerRemoteControlClient(remoteControlClient);
}
public void updateTrackList() {
String fileName = "tracks";
tracks = (ArrayList<ClsTrack>) FileUtils.readObject("tracks", getApplicationContext());
}
public void updateTrackList(ArrayList<ClsTrack> data) {
if (data != null) {
tracks = data;
if (tracks != null && position >= 0) {
int newPlayingTrackIndex = data.indexOf(currentTrack);
if (newPlayingTrackIndex >= 0) {
position = newPlayingTrackIndex;
}
}
} else {
tracks = new ArrayList<ClsTrack>();
}
}
@Override
public void onDestroy() {
if (tm != null) {
tm.listen(telephone, PhoneStateListener.LISTEN_NONE);
tm = null;
}
if (myBroadcastReceiver != null) {
unregisterReceiver(myBroadcastReceiver);
}
if (mAudioManager != null) {
mAudioManager.unregisterMediaButtonEventReceiver(new ComponentName(getPackageName(), RcvMediaControl.class.getName()));
}
if (android.os.Build.VERSION.SDK_INT >= 14 && remoteControlClient != null) {
unregisterRemoteControl();
}
// "free" the output device and all plugins
BASS.BASS_Free();
BASS.BASS_PluginFree(0);
// Stop foreground
stopForeground(true);
stopUpdateProgress();
super.onDestroy();
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
void unregisterRemoteControl() {
mAudioManager.unregisterRemoteControlClient(remoteControlClient);
}
// Play file
public void play(int pos) {
if (tracks == null) return;
startUpdateProgress();
this.position = pos;
// Play File
String path = "";
if (tracks != null && tracks.size() > position) {
currentTrack = tracks.get(position);
path = currentTrack.getPath();
}
if (path.equals("")) {
onPlayError("empty");
return;
}
BASS.BASS_StreamFree(chan);
if ((chan = BASS.BASS_StreamCreateFile(path, 0, 0, 0)) == 0) {
onPlayError(path);
// Stop Foreground
stopForeground(true);
return;
}
// Play File
int result = BASS.BASS_ChannelSetSync(chan, BASS.BASS_SYNC_END, 0, EndSync, 0);
//ByteBuffer byteBuffer = (ByteBuffer)BASS.BASS_ChannelGetTags(chan, BASS.BASS_TAG_ID3V2);
BASS.BASS_ChannelPlay(chan, false);
// Update Properties
this.duration = BASS.BASS_ChannelBytes2Seconds(chan, BASS.BASS_ChannelGetLength(chan, BASS.BASS_POS_BYTE));
this.progress = 0.0;
// Notify Activity
if (activity != null) {
activity.onFileLoaded(currentTrack, this.duration,
currentTrack.getArtist(),
currentTrack.getTitle(), position,
currentTrack.getAlbumId());
activity.onProgressChanged(progress);
activity.onUpdatePlayPause();
}
// Start foreground
fireNotification();
//Remote control
if (android.os.Build.VERSION.SDK_INT >= 14 && remoteControlClient != null) {
updateRemoteControl();
}
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
void updateRemoteControlState(int state) {
remoteControlClient.setPlaybackState(state);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
void updateRemoteControl() {
updateRemoteControlState(RemoteControlClient.PLAYSTATE_PLAYING);
remoteControlClient.setTransportControlFlags(
RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS |
RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE |
RemoteControlClient.FLAG_KEY_MEDIA_NEXT
);
// Update the remote controls
Bitmap bitmap = MediaUtils.getArtworkQuick(this, currentTrack, screenWidth / 2, screenWidth / 2);
int redTop = 0, greenTop = 0, blueTop = 0, pixelsTop = 0;
int redBtm = 0, greenBtm = 0, blueBtm = 0, pixelsBtm = 0;
int colorTop = 0, colorBtm = 0;
if (bitmap != null) {
int w = bitmap.getWidth();
int h = bitmap.getHeight();
for (int i = 0; i < w; i++) {
try {
colorTop = bitmap.getPixel(i, 0);
redTop += Color.red(colorTop);
greenTop += Color.green(colorTop);
blueTop += Color.blue(colorTop);
pixelsTop += 1;
colorBtm = bitmap.getPixel(i, h - 1);
redBtm += Color.red(colorBtm);
greenBtm += Color.green(colorBtm);
blueBtm += Color.blue(colorBtm);
pixelsBtm += 1;
} catch (Exception e) {
}
}
if (pixelsTop > 0 && pixelsBtm > 0) {
colorTop = Color.rgb(redTop / pixelsTop, greenTop / pixelsTop, blueTop / pixelsTop); //EDE7E9
colorBtm = Color.rgb(redBtm / pixelsBtm, greenBtm / pixelsBtm, blueBtm / pixelsBtm);
Shader shader = new LinearGradient(w / 2, 0, w / 2, h, colorTop, colorBtm, Shader.TileMode.CLAMP);
Bitmap bitmapBgr = Bitmap.createBitmap(w, screenHeight / 2, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmapBgr);
Paint paint = new Paint();
paint.setShader(shader);
canvas.drawRect(0, 0, w, screenHeight / 2, paint);
canvas.drawBitmap(bitmap, 0, (screenHeight / 2 - screenWidth / 2) / 2, null);
bitmap.recycle();
bitmap = bitmapBgr;
}
} else {
//create random color
bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
Random rnd = new Random();
bitmap.eraseColor(Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256)));
}
remoteControlClient.editMetadata(true)
.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, currentTrack.getArtist())
.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, currentTrack.getAlbum())
.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, currentTrack.getTitle())
.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, currentTrack.getDuration())
.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, bitmap)
.apply();
}
private void fireNotification() {
notification = NotificationUtils.getNotification(this, pendIntent, (tracks != null && tracks.size() > position) ? tracks.get(position) : null, isPlaying());
if (notification != null) {
startForeground(NOTIFICATION_ID, notification);
} else {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(NOTIFICATION_ID);
stopForeground(true);
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String action = intent.getAction();
if ("play".equals(action)) {
if (mediabtnLastEventTime == 0) {
mediabtnLastEventTime = System.currentTimeMillis();
mediabtnPressCounter++;
Handler mediaHandler = new Handler();
Runnable mediaRunnable = new Runnable() {
@Override
public void run() {
if (mediabtnPressCounter <= 1) {
if (isPaused())
playFromPause();
else
pause();
} else {
playNext();
}
fireNotification();
mediabtnPressCounter = 0;
mediabtnLastEventTime = 0;
}
};
mediaHandler.postDelayed(mediaRunnable, 500);
} else {
if ((System.currentTimeMillis() - mediabtnLastEventTime) < 500) {
//в течение секунды жмаки идут
mediabtnLastEventTime = System.currentTimeMillis();
mediabtnPressCounter++;
} else {
//обнуляем
mediabtnLastEventTime = 0;
mediabtnPressCounter = 0;
}
}
} else if ("next".equals(action)) {
playNext();
} else if ("prev".equals(action)) {
playPrev();
} else if ("voup".equals(action)) {
volumeUp();
} else if ("vodn".equals(action)) {
volumeDown();
}
fireNotification();
return START_NOT_STICKY;
}
public void playNext() {
if (tracks == null) return;
if (tracks.size() > (position + 1)) {
play(position + 1);
} else if (tracks.size() > 0) {
//Play(0);
stop();
}
}
public void playPrev() {
if (tracks == null) return;
if ((position - 1) >= 0) {
position = position - 1;
} else {
return;
}
play(position);
}
public void onPlayError(String e) {
// Update Properties
this.duration = 0.0;
this.progress = 0.0;
// Notify activity
if (activity != null) {
activity.onFileLoaded(tracks.get(position), this.duration, "", "", 0, 0);
activity.onProgressChanged(progress);
activity.onUpdatePlayPause();
}
stopUpdateProgress();
//skip 1st n errors on play
if (errorCount < 3) {
errorCount++;
playNext();
} else {
FlurryAgent.onError("onPlayError", e, "");
stop();
}
}
// Seek to position
public void seekTo(int progress) {
BASS.BASS_ChannelSetPosition(chan, BASS.BASS_ChannelSeconds2Bytes(chan, progress), BASS.BASS_POS_BYTE);
}
public void pause() {
BASS.BASS_ChannelPause(chan);
stopForeground(true);
stopUpdateProgress();
// Notify activity
if (activity != null) {
activity.onUpdatePlayPause();
}
// Tell any remote controls that our playback state is 'paused'.
if (remoteControlClient != null) {
updateRemoteControlState(RemoteControlClient.PLAYSTATE_PAUSED);
}
}
public void stop() {
BASS.BASS_ChannelStop(chan);
stopUpdateProgress();
if (activity != null) {
activity.onUpdatePlayPause();
}
if (remoteControlClient != null) {
updateRemoteControlState(RemoteControlClient.PLAYSTATE_STOPPED);
}
}
public void stopUpdateProgress() {
timerHandler.removeCallbacks(timerRunnable);
}
public void startUpdateProgress() {
//start update progress
timerHandler.postDelayed(timerRunnable, 0);
}
public boolean isPlaying() {
if (!(BASS.BASS_ACTIVE_PLAYING == BASS.BASS_ChannelIsActive(chan))) {
stopForeground(true);
stopUpdateProgress();
}
if (BASS.BASS_ACTIVE_PLAYING == BASS.BASS_ChannelIsActive(chan)) {
startUpdateProgress();
}
return BASS.BASS_ACTIVE_PLAYING == BASS.BASS_ChannelIsActive(chan);
}
public boolean isPaused() {
return BASS.BASS_ACTIVE_PAUSED == BASS.BASS_ChannelIsActive(chan);
}
public void playFromPause() {
BASS.BASS_ChannelPlay(chan, false);
startUpdateProgress();
// Notify activity
if (activity != null) {
activity.onUpdatePlayPause();
}
if (remoteControlClient != null) {
updateRemoteControlState(RemoteControlClient.PLAYSTATE_PLAYING);
}
fireNotification();
}
public void volumeUp() {
AudioManager am =
(AudioManager) getSystemService(Context.AUDIO_SERVICE);
if (am == null) return;
int currVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
int maxVolume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
currVolume = currVolume + (maxVolume / 10);
if (currVolume > maxVolume)
currVolume = maxVolume;
am.setStreamVolume(AudioManager.STREAM_MUSIC, currVolume, AudioManager.FLAG_SHOW_UI);
}
public void volumeDown() {
AudioManager am =
(AudioManager) getSystemService(Context.AUDIO_SERVICE);
if (am == null) return;
int currVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
int maxVolume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
currVolume = currVolume - (maxVolume / 10);
if (currVolume < 0)
currVolume = 0;
am.setStreamVolume(AudioManager.STREAM_MUSIC, currVolume, AudioManager.FLAG_SHOW_UI);
}
enum RING_STATE {
STATE_RINGING, STATE_OFFHOOK, STATE_NORMAL
}
// Bass Service Binder Class
public class BassServiceBinder extends Binder {
public ServicePlayer getService() {
return ServicePlayer.this;
}
}
public class UpdateAllFiles extends AsyncTask {
@Override
protected Object doInBackground(Object[] params) {
try {
new MakePlaylistFS(getApplicationContext(), true).getArrTracks();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
private class MediaButtonReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
}
}
private class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) {
return;
}
if (intent.getAction().equals(Intent.ACTION_POWER_DISCONNECTED)) {
//on power disconnect scan for new files
new UpdateAllFiles().execute(new ArrayList<String>());
}
if (intent.getAction().equals("android.media.VOLUME_CHANGED_ACTION")) {
AudioManager am =
(AudioManager) getSystemService(Context.AUDIO_SERVICE);
if (am == null) return;
int currVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
int maxVolume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
if (currVolume == maxVolume && (System.currentTimeMillis() - startVolumeUpFlag) > 2000) {
if (firstVolumeUpFlag) {
firstVolumeUpFlag = false;
if (isPlaying()) {
startVolumeUpFlag = System.currentTimeMillis();
playNext();
}
} else {
firstVolumeUpFlag = true;
}
} else {
startVolumeUpFlag = System.currentTimeMillis();
}
}
if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) {
int state = intent.getIntExtra("state", -1);
switch (state) {
case 0:
if (isPlaying()) {
isUnpluggedFlag = true;
pause();
}
break;
case 1:
if (isUnpluggedFlag && isPaused()) {
isUnpluggedFlag = false;
playFromPause();
}
break;
default:
break;
}
}
}
}
}