Back to project page AnAudioPlayer.
The source code is released under:
MIT License
If you think the Android project AnAudioPlayer listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
package jp.gr.java_conf.neko_daisuki.anaudioplayer; /*from ww w. j av a2 s .c o m*/ import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Locale; import android.app.Notification; import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnPreparedListener; import android.media.MediaPlayer.OnSeekCompleteListener; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.util.Log; import android.util.SparseArray; import jp.gr.java_conf.neko_daisuki.android.util.ContextUtil; public class AudioService extends Service { public static class Utils { private static SparseArray<String> mMessages; public static String getMessageString(Message msg) { Object o = msg.obj; String args = o != null ? o.toString() : "(null)"; return String.format(LOCALE, "%s: %s", mMessages.get(msg.what), args); } static { mMessages = new SparseArray<String>(); mMessages.put(MSG_WHAT_LIST, "MSG_WHAT_LIST"); mMessages.put(MSG_LIST, "MSG_LIST"); mMessages.put(MSG_WHAT_STATUS, "MSG_WHAT_STATUS"); mMessages.put(MSG_PLAYING, "MSG_PLAYING"); mMessages.put(MSG_PAUSED, "MSG_PAUSED"); mMessages.put(MSG_INIT, "MSG_INIT"); mMessages.put(MSG_PLAY, "MSG_PLAY"); mMessages.put(MSG_PAUSE, "MSG_PAUSE"); mMessages.put(MSG_WHAT_AUTO_REPEAT, "MSG_WHAT_AUTO_REPEAT"); mMessages.put(MSG_TOGGLE_AUTO_REPEAT, "MSG_TOGGLE_AUTO_REPEAT"); mMessages.put(MSG_AUTO_REPEAT, "MSG_AUTO_REPEAT"); } } public static class Argument { protected String join(String[] sa) { return sa != null ? joinNonNullStrings(sa) : "(null)"; } protected StringBuffer quote(String s) { StringBuffer buffer = new StringBuffer("\""); buffer.append(s); buffer.append("\""); return buffer; } private String joinNonNullStrings(String[] sa) { StringBuilder buffer = new StringBuilder("["); int len = sa.length; buffer.append(quote(0 < len ? sa[0] : "")); for (int i = 1; i < len; i++) { buffer.append(", "); buffer.append(quote(sa[i])); } buffer.append("]"); return buffer.toString(); } } public static class AutoRepeatArgument extends Argument { public boolean value; public String toString() { return String.format(LOCALE, "value=%s", Boolean.toString(value)); } } public static class ListArgument extends Argument { public String directory; public String[] files; public String toString() { String fmt = "directory=%s, files=%s"; return String.format(LOCALE, fmt, quote(directory), join(files)); } } public static class PlayingArgument extends Argument { public int filePosition; public long timeAtStart; public int offsetAtStart; public String toString() { String fmt = joinStrings(new String[] { "filePosition=%d", "timeAtStart=%d (%s)", "offsetAtStart=%d" }); return String.format(LOCALE, fmt, filePosition, timeAtStart, DATE_FORMAT.format(new Date(timeAtStart)), offsetAtStart); } private String joinStrings(String[] sa) { StringBuilder buffer = new StringBuilder(sa[0]); for (int i = 1; i < sa.length; i++) { buffer.append(", "); buffer.append(sa[i]); } return buffer.toString(); } } public static class PausedArgument extends Argument { public int filePosition; public int currentOffset; public String toString() { String fmt = "filePosition=%d, currentOffset=%d"; return String.format(LOCALE, fmt, filePosition, currentOffset); } } public static class PlayArgument extends Argument { public int filePosition; public int offset; public String toString() { String fmt = "filePosition=%d, offset=%d"; return String.format(LOCALE, fmt, filePosition, offset); } } public static class InitArgument extends Argument { public String directory; public String[] files; public String toString() { String fmt = "directory=%s, files=%s"; return String.format(LOCALE, fmt, quote(directory), join(files)); } } private interface Player { public void play(String path, int offset) throws IOException; public void pause(); public int getCurrentPosition(); public void release(); public void setOnCompletionListener(OnCompletionListener listener); public void seekTo(int offset); public boolean isPlaying(); } private class TruePlayer implements Player { private class SeekCompleteListener implements OnSeekCompleteListener { /** * Hmm, MediaPlayer.start() calls back OnSeekCompleteListener once * more (I do not know why). This causes sending MSG_PLAYING twice. */ private abstract class Proc { public abstract void run(MediaPlayer mp); } private class TrueProc extends Proc { @Override public void run(MediaPlayer mp) { mTimeAtStart = new Date().getTime(); mOffsetAtStart = mOffset; mp.start(); reply(mReplyTo, obtainPlayingMessage()); } } private class FakeProc extends Proc { @Override public void run(MediaPlayer mp) { } } private int mOffset; private Proc mProc = new TrueProc(); public SeekCompleteListener(int offset) { mOffset = offset; } public void onSeekComplete(MediaPlayer mp) { mProc.run(mp); mProc = new FakeProc(); writeCurrentPosition(); } } private class PreparedListener implements OnPreparedListener { private int mOffset; public PreparedListener(int offset) { mOffset = offset; } public void onPrepared(MediaPlayer mp) { mp.seekTo(mOffset); } } private MediaPlayer mMp = new MediaPlayer(); public TruePlayer() { mMp.setAudioStreamType(AudioManager.STREAM_MUSIC); } public void play(String path, int offset) throws IOException { mMp.setOnPreparedListener(new PreparedListener(offset)); enableOnSeekCompleteListener(offset); mMp.reset(); mMp.setDataSource(path); mMp.prepareAsync(); } public void pause() { mMp.pause(); } public int getCurrentPosition() { return mMp.getCurrentPosition(); } public void release() { mMp.reset(); mMp.release(); } public void setOnCompletionListener(OnCompletionListener listener) { mMp.setOnCompletionListener(listener); } @Override public void seekTo(int offset) { enableOnSeekCompleteListener(offset); mMp.seekTo(offset); } @Override public boolean isPlaying() { return mMp.isPlaying(); } private void enableOnSeekCompleteListener(int offset) { mMp.setOnSeekCompleteListener(new SeekCompleteListener(offset)); } } private static class FakePlayer implements Player { private int mPosition; public FakePlayer(int position) { mPosition = position; } public void play(String path, int offset) throws IOException { } public void pause() { } public int getCurrentPosition() { return mPosition; } public void release() { } public void setOnCompletionListener(OnCompletionListener listener) { } @Override public void seekTo(int offset) { } @Override public boolean isPlaying() { return false; } } private class CompletionListener implements OnCompletionListener { @Override public void onCompletion(MediaPlayer _) { mCompletionProc.run(); } } private static class IncomingHandler extends Handler { private interface MessageHandler { public void handle(Message msg); } private class WhatListHandler implements MessageHandler { @Override public void handle(Message msg) { ListArgument a = new ListArgument(); a.directory = mService.mDirectory; a.files = mService.mFiles; reply(msg, Message.obtain(null, MSG_LIST, a)); } } private class WhatStatusHandler implements MessageHandler { @Override public void handle(Message msg) { Message res = mService.mPlayer.isPlaying() ? mService.obtainPlayingMessage() : mService.obtainPausedMessage(); reply(msg, res); } } private class InitHandler implements MessageHandler { @Override public void handle(Message msg) { InitArgument a = (InitArgument)msg.obj; mService.mDirectory = a.directory; mService.mFiles = a.files; mService.writeList(); } } private class PlayHandler implements MessageHandler { private static final int NOTIFICATION_ID = 1; private Notification mNotification; public PlayHandler() { Context ctx = mService; Notification.Builder builder = new Notification.Builder(ctx); builder.setContentTitle(getApplicationName()); builder.setContentText("Playing"); builder.setSmallIcon(R.drawable.ic_launcher); mNotification = builder.getNotification(); } @Override public void handle(Message msg) { PlayArgument a = (PlayArgument)msg.obj; mService.updateFilePosition(a.filePosition); mService.play(a.offset); mService.startForeground(NOTIFICATION_ID, mNotification); } private String getApplicationName() { try { return ContextUtil.getApplicationName(mService); } catch (PackageManager.NameNotFoundException e) { String msg = "Cannot get the application name"; ContextUtil.showException(mService, msg, e); return "An Audio Player"; } } } private class PauseHandler implements MessageHandler { @Override public void handle(Message msg) { mService.stopForeground(true); mService.pause(); reply(msg, mService.obtainPausedMessage()); } } private class WhatAutoRepeatHandler implements MessageHandler { @Override public void handle(Message msg) { replyAutoRepeat(msg); } } private class ToggleAutoRepeatHandler implements MessageHandler { @Override public void handle(Message msg) { mService.toggleAutoRepeat(); replyAutoRepeat(msg); } } private AudioService mService; private SparseArray<MessageHandler> mHandlers; public IncomingHandler(AudioService service) { mService = service; mHandlers = new SparseArray<MessageHandler>(); mHandlers.put(MSG_WHAT_LIST, new WhatListHandler()); mHandlers.put(MSG_WHAT_STATUS, new WhatStatusHandler()); mHandlers.put(MSG_INIT, new InitHandler()); mHandlers.put(MSG_PLAY, new PlayHandler()); mHandlers.put(MSG_PAUSE, new PauseHandler()); mHandlers.put(MSG_WHAT_AUTO_REPEAT, new WhatAutoRepeatHandler()); mHandlers.put(MSG_TOGGLE_AUTO_REPEAT, new ToggleAutoRepeatHandler()); } @Override public void handleMessage(Message msg) { String s = Utils.getMessageString(msg); Log.i(LOG_TAG, String.format(LOCALE, "recv: %s", s)); mService.mReplyTo = msg.replyTo; mHandlers.get(msg.what).handle(msg); } private void reply(Message msg, Message res) { mService.reply(msg.replyTo, res); } private Message obtainAutoRepeatMessage() { AutoRepeatArgument a = new AutoRepeatArgument(); a.value = mService.mAutoRepeat; return Message.obtain(null, MSG_AUTO_REPEAT, a); } private void replyAutoRepeat(Message msg) { reply(msg, obtainAutoRepeatMessage()); } } private abstract class CompletionProcedure { public abstract void run(); } private class StopProcedure extends CompletionProcedure { @Override public void run() { reply(mReplyTo, obtainPausedMessage()); postProcessOfPause(); } } private abstract class PlayProcedure extends CompletionProcedure { @Override public void run() { updateFilePosition(getNextPosition()); play(0); } protected abstract int getNextPosition(); } private class PlayFirstProcedure extends PlayProcedure { @Override protected int getNextPosition() { return 0; } } private class PlayNextProcedure extends PlayProcedure { @Override protected int getNextPosition() { return mFilePosition + 1; } } public static final int MSG_WHAT_LIST = 0; public static final int MSG_LIST = 1; public static final int MSG_WHAT_STATUS = 2; public static final int MSG_PLAYING = 3; public static final int MSG_PAUSED = 4; public static final int MSG_INIT = 5; public static final int MSG_PLAY = 6; public static final int MSG_PAUSE = 7; public static final int MSG_WHAT_AUTO_REPEAT = 8; public static final int MSG_TOGGLE_AUTO_REPEAT = 9; public static final int MSG_AUTO_REPEAT = 10; private static final String PATH_DIRECTORY = "directory"; private static final String PATH_FILES = "files"; private static final String PATH_FILE_POSITION = "file_position"; private static final String PATH_CURRENT_POSITION = "current_position"; private static final String PATH_AUTO_REPEAT = "auto_repeat"; private static final String LOG_TAG = "service"; private static final Locale LOCALE = Locale.ROOT; private static final DateFormat DATE_FORMAT = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss.SSS", LOCALE); private String mDirectory; private String[] mFiles; private int mFilePosition; private boolean mAutoRepeat; /** * Path of a playing file. This is needed because MSG_INIT overwrites * mDirectory and mFiles, so this service misses which file is playing. */ private String mPlayingPath; private long mTimeAtStart; private int mOffsetAtStart; private IncomingHandler mHandler; private Messenger mMessenger; private Player mPlayer; private Player mTruePlayer; private CompletionProcedure mCompletionProc; private CompletionProcedure mStopProc; private CompletionProcedure mPlayNextProc; private CompletionProcedure mPlayFirstProc; private Messenger mReplyTo; @Override public IBinder onBind(Intent intent) { Log.i(LOG_TAG, "One client was bound with AudioService."); return mMessenger.getBinder(); } @Override public boolean onUnbind(Intent intent) { Log.i(LOG_TAG, "The client was unbound of AudioService."); return super.onUnbind(intent); } @Override public void onCreate() { super.onCreate(); readList(); mAutoRepeat = readAutoRepeat(); mHandler = new IncomingHandler(this); mMessenger = new Messenger(mHandler); mTruePlayer = new TruePlayer(); mTruePlayer.setOnCompletionListener(new CompletionListener()); mPlayer = new FakePlayer(readCurrentPosition()); mStopProc = new StopProcedure(); mPlayNextProc = new PlayNextProcedure(); mPlayFirstProc = new PlayFirstProcedure(); Log.i(LOG_TAG, "AudioService was created."); } @Override public void onDestroy() { super.onDestroy(); Player player = mPlayer; pause(); // NOTICE: This causes side effect to change mPlayer. player.release(); Log.i(LOG_TAG, "AudioService was destroyed."); } private CompletionProcedure getCompletionProcedureForLast() { return mAutoRepeat ? mPlayFirstProc : mStopProc; } private void updateCompletionProcedure() { mCompletionProc = mFilePosition == mFiles.length - 1 ? getCompletionProcedureForLast() : mPlayNextProc; } private String joinPath(String s, String t) { return String.format(LOCALE, "%s%s%s", s, File.separator, t); } private void play(int offset) { mPlayer = mTruePlayer; String name = mFiles[mFilePosition]; String path = joinPath(mDirectory, name); if (path.equals(mPlayingPath)) { mPlayer.seekTo(offset); return; } try { mPlayer.play(path, offset); } catch (IOException e) { ContextUtil.showException(this, "I/O error", e); return; } mPlayingPath = path; updateCompletionProcedure(); Log.i(LOG_TAG, String.format(LOCALE, "Play: %s from %d", path, offset)); } private void pause() { mPlayer.pause(); postProcessOfPause(); } private void postProcessOfPause() { mPlayer = new FakePlayer(mPlayer.getCurrentPosition()); writeCurrentPosition(); } private Message obtainPlayingMessage() { PlayingArgument a = new PlayingArgument(); a.timeAtStart = mTimeAtStart; a.offsetAtStart = mOffsetAtStart; a.filePosition = mFilePosition; return Message.obtain(null, MSG_PLAYING, a); } private Message obtainPausedMessage() { PausedArgument a = new PausedArgument(); a.filePosition = mFilePosition; a.currentOffset = mPlayer.getCurrentPosition(); return Message.obtain(null, MSG_PAUSED, a); } private void reply(Messenger replyTo, Message res) { String s = Utils.getMessageString(res); Log.i(LOG_TAG, String.format(LOCALE, "send: %s", s)); try { replyTo.send(res); } catch (RemoteException e) { ContextUtil.showException(this, "Cannot send a message", e); } } private String[] readArray(String path) { FileInputStream in; try { in = openFileInput(path); } catch (FileNotFoundException e) { Log.i(LOG_TAG, String.format(LOCALE, "%s not found.", path)); return new String[0]; } BufferedReader reader = new BufferedReader(new InputStreamReader(in)); try { try { List<String> l = new LinkedList<String>(); String s; while ((s = reader.readLine()) != null) { l.add(s); } return l.toArray(new String[0]); } finally { reader.close(); } } catch (IOException e) { e.printStackTrace(); } return null; } private String readDirectory() { String[] sa = readArray(PATH_DIRECTORY); return 0 < sa.length ? sa[0] : null; } private String[] readFiles() { return readArray(PATH_FILES); } private int readFilePosition() { String[] sa = readArray(PATH_FILE_POSITION); return 0 < sa.length ? Integer.valueOf(sa[0]) : 0; } private void readList() { mDirectory = readDirectory(); mFiles = readFiles(); mFilePosition = readFilePosition(); } private void writeArray(String path, String[] sa) { FileOutputStream out; try { out = openFileOutput(path, 0); } catch (FileNotFoundException e) { Log.i(LOG_TAG, String.format(LOCALE, "%s not found.", path)); return; } PrintWriter writer = new PrintWriter(out); try { for (String s: sa) { writer.println(s); } } finally { writer.close(); } } private void writeFilePosition() { String filePosition = Integer.toString(mFilePosition); writeArray(PATH_FILE_POSITION, new String[] { filePosition }); } private void updateFilePosition(int newValue) { mFilePosition = newValue; writeFilePosition(); } private void writeList() { writeArray(PATH_DIRECTORY, new String[] { mDirectory }); writeArray(PATH_FILES, mFiles); } private void writeCurrentPosition() { String pos = Integer.toString(mPlayer.getCurrentPosition()); writeArray(PATH_CURRENT_POSITION, new String[] { pos }); } private int readCurrentPosition() { String[] a = readArray(PATH_CURRENT_POSITION); return 0 < a.length ? Integer.parseInt(a[0]) : 0; } private void writeAutoRepeat() { String autoRepeat = Boolean.toString(mAutoRepeat); writeArray(PATH_AUTO_REPEAT, new String[] { autoRepeat }); } private boolean readAutoRepeat() { String[] a = readArray(PATH_AUTO_REPEAT); return 0 < a.length ? Boolean.parseBoolean(a[0]) : false; } private void toggleAutoRepeat() { mAutoRepeat = !mAutoRepeat; writeAutoRepeat(); updateCompletionProcedure(); } } // vim: tabstop=4 shiftwidth=4 expandtab softtabstop=4