aboutsummaryrefslogtreecommitdiffstats
path: root/libs/audiowife/src
diff options
context:
space:
mode:
Diffstat (limited to 'libs/audiowife/src')
-rw-r--r--libs/audiowife/src/main/AndroidManifest.xml9
-rw-r--r--libs/audiowife/src/main/java/nl/changer/audiowife/AudioWife.java699
-rw-r--r--libs/audiowife/src/main/res/drawable-mdpi/ic_launcher.pngbin0 -> 5237 bytes
-rw-r--r--libs/audiowife/src/main/res/drawable-xhdpi/ic_launcher.pngbin0 -> 14383 bytes
-rw-r--r--libs/audiowife/src/main/res/drawable-xxhdpi/ic_launcher.pngbin0 -> 19388 bytes
-rw-r--r--libs/audiowife/src/main/res/drawable/gray_border_wo_padding.xml15
-rw-r--r--libs/audiowife/src/main/res/layout/aw_player.xml49
-rw-r--r--libs/audiowife/src/main/res/values/strings.xml5
-rw-r--r--libs/audiowife/src/main/res/values/styles.xml20
9 files changed, 797 insertions, 0 deletions
diff --git a/libs/audiowife/src/main/AndroidManifest.xml b/libs/audiowife/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..dad095276
--- /dev/null
+++ b/libs/audiowife/src/main/AndroidManifest.xml
@@ -0,0 +1,9 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="nl.changer.audiowife">
+
+ <application android:allowBackup="true"
+ android:label="@string/app_name">
+
+ </application>
+
+</manifest>
diff --git a/libs/audiowife/src/main/java/nl/changer/audiowife/AudioWife.java b/libs/audiowife/src/main/java/nl/changer/audiowife/AudioWife.java
new file mode 100644
index 000000000..776108499
--- /dev/null
+++ b/libs/audiowife/src/main/java/nl/changer/audiowife/AudioWife.java
@@ -0,0 +1,699 @@
+/***
+ * The MIT License (MIT)
+ * <p/>
+ * Copyright (c) 2014 Jaydeep
+ * <p/>
+ * 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:
+ * <p/>
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ * <p/>
+ * 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 nl.changer.audiowife;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.net.Uri;
+import android.os.Handler;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+/***
+ * A simple audio player wrapper for Android
+ ***/
+public class AudioWife {
+
+ private static final String TAG = AudioWife.class.getSimpleName();
+ /****
+ * Playback progress update time in milliseconds
+ ****/
+ private static final int AUDIO_PROGRESS_UPDATE_TIME = 100;
+ // TODO: externalize the error messages.
+ private static final String ERROR_PLAYVIEW_NULL = "Play view cannot be null";
+ private static final String ERROR_PLAYTIME_CURRENT_NEGATIVE = "Current playback time cannot be negative";
+ private static final String ERROR_PLAYTIME_TOTAL_NEGATIVE = "Total playback time cannot be negative";
+ /***
+ * Keep a single copy of this in memory unless required to create a new instance explicitly.
+ ****/
+ private static AudioWife mAudioWife;
+ /***
+ * Audio URI
+ ****/
+ private static Uri mUri;
+ private Handler mProgressUpdateHandler;
+ private MediaPlayer mMediaPlayer;
+ private SeekBar mSeekBar;
+ @Deprecated
+ /***
+ * Set both current playack time and total runtime
+ * of the audio in the UI.
+ */
+ private TextView mPlaybackTime;
+ private View mPlayButton;
+ private View mPauseButton;
+ /***
+ * Indicates the current run-time of the audio being played
+ */
+ private TextView mRunTime;
+ /***
+ * Indicates the total duration of the audio being played.
+ */
+ private TextView mTotalTime;
+ /***
+ * Set if AudioWife is using the default UI provided with the library.
+ **/
+ private boolean mHasDefaultUi;
+ /****
+ * Array to hold custom completion listeners
+ ****/
+ private ArrayList<OnCompletionListener> mCompletionListeners = new ArrayList<OnCompletionListener>();
+ private ArrayList<View.OnClickListener> mPlayListeners = new ArrayList<View.OnClickListener>();
+ private ArrayList<View.OnClickListener> mPauseListeners = new ArrayList<View.OnClickListener>();
+ private Runnable mUpdateProgress = new Runnable() {
+
+ public void run() {
+
+ if (mSeekBar == null) {
+ return;
+ }
+
+ if (mProgressUpdateHandler != null && mMediaPlayer.isPlaying()) {
+ mSeekBar.setProgress((int) mMediaPlayer.getCurrentPosition());
+ int currentTime = mMediaPlayer.getCurrentPosition();
+ updatePlaytime(currentTime);
+ updateRuntime(currentTime);
+ // repeat the process
+ mProgressUpdateHandler.postDelayed(this, AUDIO_PROGRESS_UPDATE_TIME);
+ } else {
+ // DO NOT update UI if the player is paused
+ }
+ }
+ };
+ private OnCompletionListener mOnCompletion = new OnCompletionListener() {
+
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ // set UI when audio finished playing
+ int currentPlayTime = 0;
+ mSeekBar.setProgress((int) currentPlayTime);
+ updatePlaytime(currentPlayTime);
+ updateRuntime(currentPlayTime);
+ setPlayable();
+ // ensure that our completion listener fires first.
+ // This will provide the developer to over-ride our
+ // completion listener functionality
+
+ fireCustomCompletionListeners(mp);
+ }
+ };
+ private View playerUi;
+
+ public static AudioWife getInstance() {
+
+ if (mAudioWife == null) {
+ mAudioWife = new AudioWife();
+ }
+
+ return mAudioWife;
+ }
+
+ /***
+ * Starts playing audio file associated. Before playing the audio, visibility of appropriate UI
+ * controls is made visible. Calling this method has no effect if the audio is already being
+ * played.
+ ****/
+ public void play() {
+
+ // if play button itself is null, the whole purpose of AudioWife is
+ // defeated.
+ if (mPlayButton == null) {
+ throw new IllegalStateException(ERROR_PLAYVIEW_NULL);
+ }
+
+ if (mUri == null) {
+ throw new IllegalStateException("Uri cannot be null. Call init() before calling this method");
+ }
+
+ if (mMediaPlayer == null) {
+ throw new IllegalStateException("Call init() before calling this method");
+ }
+
+ if (mMediaPlayer.isPlaying()) {
+ return;
+ }
+
+ mProgressUpdateHandler.postDelayed(mUpdateProgress, AUDIO_PROGRESS_UPDATE_TIME);
+
+ // enable visibility of all UI controls.
+ setViewsVisibility();
+
+ mMediaPlayer.start();
+
+ setPausable();
+ }
+
+ /**
+ * Ensure the views are visible before playing the audio.
+ */
+ private void setViewsVisibility() {
+
+ if (mSeekBar != null) {
+ mSeekBar.setVisibility(View.VISIBLE);
+ }
+
+ if (mPlaybackTime != null) {
+ mPlaybackTime.setVisibility(View.VISIBLE);
+ }
+
+ if (mRunTime != null) {
+ mRunTime.setVisibility(View.VISIBLE);
+ }
+
+ if (mTotalTime != null) {
+ mTotalTime.setVisibility(View.VISIBLE);
+ }
+
+ if (mPlayButton != null) {
+ mPlayButton.setVisibility(View.VISIBLE);
+ }
+
+ if (mPauseButton != null) {
+ mPauseButton.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /***
+ * Pause the audio being played. Calling this method has no effect if the audio is already
+ * paused
+ */
+ public void pause() {
+
+ if (mMediaPlayer == null) {
+ return;
+ }
+
+ if (mMediaPlayer.isPlaying()) {
+ mMediaPlayer.pause();
+ setPlayable();
+ }
+ }
+
+ @Deprecated
+ private void updatePlaytime(int currentTime) {
+
+ if (mPlaybackTime == null) {
+ return;
+ }
+
+ if (currentTime < 0) {
+ throw new IllegalArgumentException(ERROR_PLAYTIME_CURRENT_NEGATIVE);
+ }
+
+ StringBuilder playbackStr = new StringBuilder();
+
+ // set the current time
+ // its ok to show 00:00 in the UI
+ playbackStr.append(String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes((long) currentTime), TimeUnit.MILLISECONDS.toSeconds((long) currentTime) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes((long) currentTime))));
+
+ playbackStr.append("/");
+
+ // show total duration.
+ long totalDuration = 0;
+
+ if (mMediaPlayer != null) {
+ try {
+ totalDuration = mMediaPlayer.getDuration();
+ } catch (IllegalStateException e) {
+ //e.printStackTrace();
+ } catch (Exception e) {
+ //e.printStackTrace();
+ }
+ }
+
+ // set total time as the audio is being played
+ if (totalDuration != 0) {
+ playbackStr.append(String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes((long) totalDuration), TimeUnit.MILLISECONDS.toSeconds((long) totalDuration) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes((long) totalDuration))));
+ } else {
+ Log.w(TAG, "Something strage this audio track duration in zero");
+ }
+
+ mPlaybackTime.setText(playbackStr);
+
+ // DebugLog.i(currentTime + " / " + totalDuration);
+ }
+
+ private void updateRuntime(int currentTime) {
+
+ if (mRunTime == null) {
+ // this view can be null if the user
+ // does not want to use it. Don't throw
+ // an exception.
+ return;
+ }
+
+ if (currentTime < 0) {
+ throw new IllegalArgumentException(ERROR_PLAYTIME_CURRENT_NEGATIVE);
+ }
+
+ StringBuilder playbackStr = new StringBuilder();
+
+ // set the current time
+ // its ok to show 00:00 in the UI
+ playbackStr.append(String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes((long) currentTime), TimeUnit.MILLISECONDS.toSeconds((long) currentTime) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes((long) currentTime))));
+
+ mRunTime.setText(playbackStr);
+
+ // DebugLog.i(currentTime + " / " + totalDuration);
+ }
+
+ private void setTotalTime() {
+
+ if (mTotalTime == null) {
+ // this view can be null if the user
+ // does not want to use it. Don't throw
+ // an exception.
+ return;
+ }
+
+ StringBuilder playbackStr = new StringBuilder();
+ long totalDuration = 0;
+
+ // by this point the media player is brought to ready state
+ // by the call to init().
+ if (mMediaPlayer != null) {
+ try {
+ totalDuration = mMediaPlayer.getDuration();
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ if (totalDuration < 0) {
+ throw new IllegalArgumentException(ERROR_PLAYTIME_TOTAL_NEGATIVE);
+ }
+
+ // set total time as the audio is being played
+ if (totalDuration != 0) {
+ playbackStr.append(String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes((long) totalDuration), TimeUnit.MILLISECONDS.toSeconds((long) totalDuration) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes((long) totalDuration))));
+ }
+
+ mTotalTime.setText(playbackStr);
+ }
+
+ /***
+ * Changes audiowife state to enable play functionality.
+ */
+ private void setPlayable() {
+ if (mPlayButton != null) {
+ mPlayButton.setVisibility(View.VISIBLE);
+ }
+
+ if (mPauseButton != null) {
+ mPauseButton.setVisibility(View.GONE);
+ }
+ }
+
+ /****
+ * Changes audio wife to enable pause functionality.
+ */
+ private void setPausable() {
+ if (mPlayButton != null) {
+ mPlayButton.setVisibility(View.GONE);
+ }
+
+ if (mPauseButton != null) {
+ mPauseButton.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /***
+ * Initialize the audio player. This method should be the first one to be called before starting
+ * to play audio using {@link nl.changer.audiowife.AudioWife}
+ *
+ * @param ctx {@link android.app.Activity} Context
+ * @param uri Uri of the audio to be played.
+ ****/
+ public AudioWife init(Context ctx, Uri uri) {
+
+ if (uri == null) {
+ throw new IllegalArgumentException("Uri cannot be null");
+ }
+
+ if (mAudioWife == null) {
+ mAudioWife = new AudioWife();
+ }
+
+ mUri = uri;
+
+ mProgressUpdateHandler = new Handler();
+
+ initPlayer(ctx);
+
+ return this;
+ }
+
+ /***
+ * Sets the audio play functionality on click event of this view. You can set {@link android.widget.Button} or
+ * an {@link android.widget.ImageView} as audio play control
+ *
+ * @see nl.changer.audiowife.AudioWife#addOnPauseClickListener(android.view.View.OnClickListener)
+ ****/
+ public AudioWife setPlayView(View play) {
+
+ if (play == null) {
+ throw new NullPointerException("PlayView cannot be null");
+ }
+
+ if (mHasDefaultUi) {
+ Log.w(TAG, "Already using default UI. Setting play view will have no effect");
+ return this;
+ }
+
+ mPlayButton = play;
+
+ initOnPlayClick();
+ return this;
+ }
+
+ private void initOnPlayClick() {
+ if (mPlayButton == null) {
+ throw new NullPointerException(ERROR_PLAYVIEW_NULL);
+ }
+
+ // add default click listener to the top
+ // so that it is the one that gets fired first
+ mPlayListeners.add(0, new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ play();
+ }
+ });
+
+ // Fire all the attached listeners
+ // when the play button is clicked
+ mPlayButton.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ for (View.OnClickListener listener : mPlayListeners) {
+ listener.onClick(v);
+ }
+ }
+ });
+ }
+
+ /***
+ * Sets the audio pause functionality on click event of the view passed in as a parameter. You
+ * can set {@link android.widget.Button} or an {@link android.widget.ImageView} as audio pause control. Audio pause
+ * functionality will be unavailable if this method is not called.
+ *
+ * @see nl.changer.audiowife.AudioWife#addOnPauseClickListener(android.view.View.OnClickListener)
+ ****/
+ public AudioWife setPauseView(View pause) {
+
+ if (pause == null) {
+ throw new NullPointerException("PauseView cannot be null");
+ }
+
+ if (mHasDefaultUi) {
+ Log.w(TAG, "Already using default UI. Setting pause view will have no effect");
+ return this;
+ }
+
+ mPauseButton = pause;
+
+ initOnPauseClick();
+ return this;
+ }
+
+ private void initOnPauseClick() {
+ if (mPauseButton == null) {
+ throw new NullPointerException("Pause view cannot be null");
+ }
+
+ // add default click listener to the top
+ // so that it is the one that gets fired first
+ mPauseListeners.add(0, new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ pause();
+ }
+ });
+
+ // Fire all the attached listeners
+ // when the pause button is clicked
+ mPauseButton.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ for (View.OnClickListener listener : mPauseListeners) {
+ listener.onClick(v);
+ }
+ }
+ });
+ }
+
+ /***
+ * @deprecated Use {@link nl.changer.audiowife.AudioWife#setRuntimeView(android.widget.TextView)} and
+ * {@link nl.changer.audiowife.AudioWife#setTotalTimeView(android.widget.TextView)} instead. <br/>
+ * Sets current and total playback time. Use this if you have a playback time
+ * counter in the UI.
+ ****/
+ public AudioWife setPlaytime(TextView playTime) {
+
+ if (mHasDefaultUi) {
+ Log.w(TAG, "Already using default UI. Setting play time will have no effect");
+ return this;
+ }
+
+ mPlaybackTime = playTime;
+
+ // initialize the playtime to 0
+ updatePlaytime(0);
+ return this;
+ }
+
+ /***
+ * Sets current playback time view. Use this if you have a playback time counter in the UI.
+ *
+ * @see nl.changer.audiowife.AudioWife#setTotalTimeView(android.widget.TextView)
+ ****/
+ public AudioWife setRuntimeView(TextView currentTime) {
+
+ if (mHasDefaultUi) {
+ Log.w(TAG, "Already using default UI. Setting play time will have no effect");
+ return this;
+ }
+
+ mRunTime = currentTime;
+
+ // initialize the playtime to 0
+ updateRuntime(0);
+ return this;
+ }
+
+ /***
+ * Sets the total playback time view. Use this if you have a playback time counter in the UI.
+ *
+ * @see nl.changer.audiowife.AudioWife#setRuntimeView(android.widget.TextView)
+ ****/
+ public AudioWife setTotalTimeView(TextView totalTime) {
+
+ if (mHasDefaultUi) {
+ Log.w(TAG, "Already using default UI. Setting play time will have no effect");
+ return this;
+ }
+
+ mTotalTime = totalTime;
+
+ setTotalTime();
+ return this;
+ }
+
+ public AudioWife setSeekBar(SeekBar seekbar) {
+
+ if (mHasDefaultUi) {
+ Log.w(TAG, "Already using default UI. Setting seek bar will have no effect");
+ return this;
+ }
+
+ mSeekBar = seekbar;
+ initMediaSeekBar();
+ return this;
+ }
+
+ /****
+ * Add custom playback completion listener. Adding multiple listeners will queue up all the
+ * listeners and fire them on media playback completes.
+ */
+ public AudioWife addOnCompletionListener(OnCompletionListener listener) {
+
+ // add default click listener to the top
+ // so that it is the one that gets fired first
+ mCompletionListeners.add(0, listener);
+
+ return this;
+ }
+
+ /****
+ * Add custom play view click listener. Calling this method multiple times will queue up all the
+ * listeners and fire them all together when the event occurs.
+ ***/
+ public AudioWife addOnPlayClickListener(View.OnClickListener listener) {
+
+ mPlayListeners.add(listener);
+
+ return this;
+ }
+
+ /***
+ * Add custom pause view click listener. Calling this method multiple times will queue up all
+ * the listeners and fire them all together when the event occurs.
+ ***/
+ public AudioWife addOnPauseClickListener(View.OnClickListener listener) {
+
+ mPauseListeners.add(listener);
+
+ return this;
+ }
+
+ /****
+ * Initialize and prepare the audio player
+ ****/
+ private void initPlayer(Context ctx) {
+
+ mMediaPlayer = new MediaPlayer();
+ mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+
+ try {
+ mMediaPlayer.setDataSource(ctx, mUri);
+ } catch (Exception ignored) {
+ }
+
+ try {
+ mMediaPlayer.prepare();
+ } catch (Exception ignored) {
+ }
+
+ mMediaPlayer.setOnCompletionListener(mOnCompletion);
+ }
+
+ private void initMediaSeekBar() {
+
+ if (mSeekBar == null) {
+ return;
+ }
+
+ // update seekbar
+ long finalTime = mMediaPlayer.getDuration();
+ mSeekBar.setMax((int) finalTime);
+
+ mSeekBar.setProgress(0);
+
+ mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ mMediaPlayer.seekTo(seekBar.getProgress());
+
+ // if the audio is paused and seekbar is moved,
+ // update the play time in the UI.
+ updateRuntime(seekBar.getProgress());
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+
+ }
+ });
+ }
+
+ private void fireCustomCompletionListeners(MediaPlayer mp) {
+ for (OnCompletionListener listener : mCompletionListeners) {
+ listener.onCompletion(mp);
+ }
+ }
+
+ public void cleanPlayerUi() {
+ ((ViewGroup) playerUi.getParent()).removeView(playerUi);
+ }
+
+ public View getPlayerUi() {
+ return playerUi;
+ }
+
+ public AudioWife useDefaultUi(ViewGroup playerContainer, LayoutInflater inflater) {
+ if (playerContainer == null) {
+ throw new NullPointerException("Player container cannot be null");
+ }
+
+ if (inflater == null) {
+ throw new IllegalArgumentException("Inflater cannot be null");
+ }
+
+ playerUi = inflater.inflate(R.layout.aw_player, playerContainer, false); // IMPORTANT, sonst geht meine Lösung nicht xD
+
+ // init play view
+ View playView = playerUi.findViewById(R.id.play);
+ setPlayView(playView);
+
+ // init pause view
+ View pauseView = playerUi.findViewById(R.id.pause);
+ setPauseView(pauseView);
+
+ // init seekbar
+ SeekBar seekBar = (SeekBar) playerUi.findViewById(R.id.media_seekbar);
+ setSeekBar(seekBar);
+
+ // init playback time view
+ TextView playbackTime = (TextView) playerUi.findViewById(R.id.playback_time);
+ setPlaytime(playbackTime);
+
+ // this has to be set after all the views
+ // have finished initializing.
+ mHasDefaultUi = true;
+ return this;
+ }
+ public void release() {
+
+ if (mMediaPlayer != null) {
+ mMediaPlayer.stop();
+ mMediaPlayer.reset();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ mProgressUpdateHandler = null;
+ }
+ }
+}
diff --git a/libs/audiowife/src/main/res/drawable-mdpi/ic_launcher.png b/libs/audiowife/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 000000000..359047dfa
--- /dev/null
+++ b/libs/audiowife/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/libs/audiowife/src/main/res/drawable-xhdpi/ic_launcher.png b/libs/audiowife/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..71c6d760f
--- /dev/null
+++ b/libs/audiowife/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/libs/audiowife/src/main/res/drawable-xxhdpi/ic_launcher.png b/libs/audiowife/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..4df189464
--- /dev/null
+++ b/libs/audiowife/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/libs/audiowife/src/main/res/drawable/gray_border_wo_padding.xml b/libs/audiowife/src/main/res/drawable/gray_border_wo_padding.xml
new file mode 100644
index 000000000..409ed7ccd
--- /dev/null
+++ b/libs/audiowife/src/main/res/drawable/gray_border_wo_padding.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" >
+
+
+ <!-- view border color and width -->
+ <stroke
+ android:width="1dp"
+ android:color="#F2F2F2" >
+ </stroke>
+
+ <gradient
+ android:endColor="@android:color/white"
+ android:startColor="@android:color/white" />
+
+</shape> \ No newline at end of file
diff --git a/libs/audiowife/src/main/res/layout/aw_player.xml b/libs/audiowife/src/main/res/layout/aw_player.xml
new file mode 100644
index 000000000..6d2904e31
--- /dev/null
+++ b/libs/audiowife/src/main/res/layout/aw_player.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/player_layout"
+ android:layout_width="match_parent"
+ android:layout_height="70dp"
+ android:gravity="center_vertical"
+ android:paddingLeft="4dp">
+
+ <ImageView
+ android:id="@+id/play"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="6"
+ android:src="@android:drawable/ic_media_play" />
+
+ <ImageView
+ android:id="@+id/pause"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="6"
+ android:src="@android:drawable/ic_media_pause"
+ android:visibility="gone" />
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="0.8"
+ android:paddingBottom="5dp"
+ android:paddingRight="10dp"
+ android:paddingTop="5dp" >
+
+ <SeekBar
+ android:id="@+id/media_seekbar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical" />
+
+ <TextView
+ android:id="@+id/playback_time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right|top"
+ android:ellipsize="end"
+ android:inputType="text"
+ android:textColor="@android:color/darker_gray"
+ android:textSize="11sp" />
+ </FrameLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/libs/audiowife/src/main/res/values/strings.xml b/libs/audiowife/src/main/res/values/strings.xml
new file mode 100644
index 000000000..66966868f
--- /dev/null
+++ b/libs/audiowife/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+<resources>
+
+ <string name="app_name">LibAudioWife</string>
+
+</resources>
diff --git a/libs/audiowife/src/main/res/values/styles.xml b/libs/audiowife/src/main/res/values/styles.xml
new file mode 100644
index 000000000..6ce89c7ba
--- /dev/null
+++ b/libs/audiowife/src/main/res/values/styles.xml
@@ -0,0 +1,20 @@
+<resources>
+
+ <!--
+ Base application theme, dependent on API level. This theme is replaced
+ by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+ -->
+ <style name="AppBaseTheme" parent="android:Theme.Light">
+ <!--
+ Theme customizations available in newer API levels can go in
+ res/values-vXX/styles.xml, while customizations related to
+ backward-compatibility can go here.
+ -->
+ </style>
+
+ <!-- Application theme. -->
+ <style name="AppTheme" parent="AppBaseTheme">
+ <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+ </style>
+
+</resources>