OSDN Git Service

Upgrade visualizer
authorUtkarsh Gupta <utkarsh.eminem@gmail.com>
Tue, 21 Jul 2015 11:04:10 +0000 (16:34 +0530)
committerUtkarsh Gupta <utkarsh.eminem@gmail.com>
Tue, 21 Jul 2015 11:12:23 +0000 (16:42 +0530)
* A different visualizer: http://imgur.com/npV2zyO
* Much smoother than the current one,
  Always stays above 60fps (even in powersave mode).
* Abandon android-visualizer library.
* Set visualizer color alpha to 75%

Change-Id: I08ad5815893735f4427e84d460c4f7a06bf4b019
Signed-off-by: Utkarsh Gupta <utkarsh.eminem@gmail.com>
Android.mk
res/layout/main_album_flow.xml
res/values-xhdpi/equalizer_config.xml [deleted file]
res/values-xxhdpi/equalizer_config.xml [deleted file]
res/values/colors.xml
res/values/equalizer_config.xml [deleted file]
src/com/cyanogenmod/eleven/ui/activities/HomeActivity.java
src/com/cyanogenmod/eleven/ui/fragments/AudioPlayerFragment.java
src/com/cyanogenmod/eleven/widgets/EqualizerView.java [deleted file]
src/com/cyanogenmod/eleven/widgets/VisualizerView.java [new file with mode: 0644]

index 17708a5..206db89 100644 (file)
@@ -14,7 +14,6 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-v7-palette \
     android-support-v7-cardview \
     android-common \
-    android-visualizer \
     eleven_support_v4 \
     eleven_recyclerview \
     guava
index 49b0afb..aa14181 100644 (file)
@@ -32,8 +32,8 @@
         android:background="@drawable/equalizer_background"
         android:layout_gravity="bottom"/>
 
-    <com.cyanogenmod.eleven.widgets.EqualizerView
-        android:id="@+id/equalizerView"
+    <com.cyanogenmod.eleven.widgets.VisualizerView
+        android:id="@+id/visualizerView"
         android:gravity="bottom"
         android:layout_gravity="bottom"
         android:layout_width="match_parent"
diff --git a/res/values-xhdpi/equalizer_config.xml b/res/values-xhdpi/equalizer_config.xml
deleted file mode 100644 (file)
index 0208508..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2012-2014 The CyanogenMod Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<resources>
-    <!-- Height of each filled in block in each eq bar -->
-    <dimen name="eqalizer_path_effect_1">8dp</dimen>
-    <!-- Height of each empty block in each eq bar -->
-    <dimen name="eqalizer_path_effect_2">1dp</dimen>
-    <!-- Width of each eq bar -->
-    <dimen name="eqalizer_path_stroke_width">22dp</dimen>
-
-    <!-- The amount of divisions to make for eq bars -->
-    <integer name="equalizer_divisions">12</integer>
-
-    <!-- fudge factors to tweak display for various configs
-        ends up being dB = ((dB * fuzz_factor) + db_fuzz) -->
-    <integer name="equalizer_db_fuzz_factor">15</integer>
-    <integer name="equalizer_db_fuzz">0</integer>
-</resources>
\ No newline at end of file
diff --git a/res/values-xxhdpi/equalizer_config.xml b/res/values-xxhdpi/equalizer_config.xml
deleted file mode 100644 (file)
index 59aca03..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2012-2014 The CyanogenMod Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<resources>
-    <!-- Height of each filled in block in each eq bar -->
-    <dimen name="eqalizer_path_effect_1">8dp</dimen>
-    <!-- Height of each empty block in each eq bar -->
-    <dimen name="eqalizer_path_effect_2">1dp</dimen>
-    <!-- Width of each eq bar -->
-    <dimen name="eqalizer_path_stroke_width">20dp</dimen>
-
-    <!-- The amount of divisions to make for eq bars -->
-    <integer name="equalizer_divisions">16</integer>
-
-    <!-- fudge factors to tweak display for various configs
-        ends up being dB = ((dB * fuzz_factor) + db_fuzz) -->
-    <integer name="equalizer_db_fuzz_factor">35</integer>
-    <integer name="equalizer_db_fuzz">0</integer>
-</resources>
\ No newline at end of file
index 07de9d9..a12a6f5 100644 (file)
         <item>@color/letter_tile_deep_orange_color_dark</item>
         <item>@color/letter_tile_brown_color_dark</item>
     </array>
+
+    <!-- Color for the visualizer bars -->
+    <color name="visualizer_fill_color">#bfffffff</color>
 </resources>
diff --git a/res/values/equalizer_config.xml b/res/values/equalizer_config.xml
deleted file mode 100644 (file)
index baf5945..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2012-2014 The CyanogenMod Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<resources>
-    <!-- Height of each filled in block in each eq bar -->
-    <dimen name="eqalizer_path_effect_1">8dp</dimen>
-    <!-- Height of each empty block in each eq bar -->
-    <dimen name="eqalizer_path_effect_2">1dp</dimen>
-    <!-- Width of each eq bar -->
-    <dimen name="eqalizer_path_stroke_width">20dp</dimen>
-
-    <!-- Color for the Equalizer tile -->
-    <color name="equalizer_fill_color">#32ffffff</color>
-
-    <!-- The amount of divisions to make for eq bars -->
-    <integer name="equalizer_divisions">16</integer>
-
-    <!-- fudge factors to tweak display for various configs
-        ends up being dB = (dB * fuzz_factor + db_fuzz) -->
-    <integer name="equalizer_db_fuzz_factor">0</integer>
-    <integer name="equalizer_db_fuzz">0</integer>
-</resources>
\ No newline at end of file
index 775fe55..68819b1 100644 (file)
@@ -182,10 +182,10 @@ public class HomeActivity extends SlidingPanelActivity implements
 
     @Override
     public void onWindowFocusChanged(boolean hasFocus) {
-        getAudioPlayerFragment().onWindowFocusChanged(hasFocus
-                && (getCurrentPanel() == Panel.MusicPlayer));
-
         super.onWindowFocusChanged(hasFocus);
+
+        getAudioPlayerFragment().setVisualizerVisible(hasFocus
+                && getCurrentPanel() == Panel.MusicPlayer);
     }
 
     private void updateStatusBarColor() {
@@ -203,7 +203,7 @@ public class HomeActivity extends SlidingPanelActivity implements
                 }
                 @Override
                 protected void onPostExecute(BitmapWithColors bmc) {
-                    updateEqualizerColor(bmc != null
+                    updateVisualizerColor(bmc != null
                             ? bmc.getVibrantColor() : Color.TRANSPARENT);
                     updateStatusBarColor(bmc != null
                             ? bmc.getVibrantDarkColor() : Color.TRANSPARENT);
@@ -212,11 +212,12 @@ public class HomeActivity extends SlidingPanelActivity implements
         }
     }
 
-    private void updateEqualizerColor(int color) {
+    private void updateVisualizerColor(int color) {
         if (color == Color.TRANSPARENT) {
-            color = getResources().getColor(R.color.equalizer_fill_color);
+            color = getResources().getColor(R.color.visualizer_fill_color);
         }
-        getAudioPlayerFragment().updateVisualizerColor(color);
+
+        getAudioPlayerFragment().setVisualizerColor(color);
     }
 
     private void updateStatusBarColor(int color) {
index 3b87698..4ecce67 100644 (file)
@@ -61,7 +61,6 @@ import com.cyanogenmod.eleven.utils.MusicUtils;
 import com.cyanogenmod.eleven.utils.NavUtils;
 import com.cyanogenmod.eleven.utils.PreferenceUtils;
 import com.cyanogenmod.eleven.widgets.BrowseButton;
-import com.cyanogenmod.eleven.widgets.EqualizerView;
 import com.cyanogenmod.eleven.widgets.LoadingEmptyContainer;
 import com.cyanogenmod.eleven.widgets.NoResultsContainer;
 import com.cyanogenmod.eleven.widgets.PlayPauseProgressButton;
@@ -69,6 +68,7 @@ import com.cyanogenmod.eleven.widgets.QueueButton;
 import com.cyanogenmod.eleven.widgets.RepeatButton;
 import com.cyanogenmod.eleven.widgets.RepeatingImageButton;
 import com.cyanogenmod.eleven.widgets.ShuffleButton;
+import com.cyanogenmod.eleven.widgets.VisualizerView;
 
 import java.lang.ref.WeakReference;
 
@@ -132,8 +132,8 @@ public class AudioPlayerFragment extends Fragment implements ServiceConnection,
     // Total time
     private TextView mTotalTime;
 
-    // Equalizer View
-    private EqualizerView mEqualizerView;
+    // Visualizer View
+    private VisualizerView mVisualizerView;
 
     // Equalizer Gradient
     private View mEqualizerGradient;
@@ -196,8 +196,8 @@ public class AudioPlayerFragment extends Fragment implements ServiceConnection,
 
         initPlaybackControls();
 
-        mEqualizerView = (EqualizerView) mRootView.findViewById(R.id.equalizerView);
-        mEqualizerView.initialize(getActivity());
+        mVisualizerView = (VisualizerView) mRootView.findViewById(R.id.visualizerView);
+        mVisualizerView.initialize(getActivity());
         mEqualizerGradient = mRootView.findViewById(R.id.equalizerGradient);
 
         mLyricsText = (TextView) mRootView.findViewById(R.id.audio_player_lyrics);
@@ -482,10 +482,8 @@ public class AudioPlayerFragment extends Fragment implements ServiceConnection,
             mQueueEmpty.hideAll();
             if (PreferenceUtils.getInstance(getActivity()).getShowVisualizer()) {
                 mEqualizerGradient.setVisibility(View.VISIBLE);
-                mEqualizerView.setEnabled(true);
             } else {
                 mEqualizerGradient.setVisibility(View.GONE);
-                mEqualizerView.setEnabled(false);
             }
             mAddToPlaylistButton.setVisibility(View.VISIBLE);
         }
@@ -737,20 +735,24 @@ public class AudioPlayerFragment extends Fragment implements ServiceConnection,
 
     @Override
     public void onBeginSlide() {
-        mEqualizerView.setVisible(false);
+        mVisualizerView.setVisible(false);
     }
 
     @Override
     public void onFinishSlide(SlidingPanelActivity.Panel visiblePanel) {
-        mEqualizerView.setVisible(visiblePanel == SlidingPanelActivity.Panel.MusicPlayer);
+        setVisualizerVisible(visiblePanel == SlidingPanelActivity.Panel.MusicPlayer);
     }
 
-    public void onWindowFocusChanged(boolean hasFocus) {
-        mEqualizerView.setVisible(hasFocus);
+    public void setVisualizerVisible(boolean visible) {
+        if (visible && PreferenceUtils.getInstance(getActivity()).getShowVisualizer()) {
+            mVisualizerView.setVisible(true);
+        } else {
+            mVisualizerView.setVisible(false);
+        }
     }
 
-    public void updateVisualizerColor(int color) {
-        mEqualizerView.setColor(color);
+    public void setVisualizerColor(int color) {
+        mVisualizerView.setColor(color);
     }
 
     /**
@@ -811,7 +813,7 @@ public class AudioPlayerFragment extends Fragment implements ServiceConnection,
                 audioPlayerFragment.updateNowPlayingInfo();
                 audioPlayerFragment.dismissPopupMenu();
             } else if (action.equals(MusicPlaybackService.PLAYSTATE_CHANGED)) {
-                audioPlayerFragment.mEqualizerView.setPlaying(MusicUtils.isPlaying());
+                audioPlayerFragment.mVisualizerView.setPlaying(MusicUtils.isPlaying());
                 // Set the play and pause image
                 audioPlayerFragment.mPlayPauseProgressButton.getPlayPauseButton().updateState();
             } else if (action.equals(MusicPlaybackService.REPEATMODE_CHANGED)
diff --git a/src/com/cyanogenmod/eleven/widgets/EqualizerView.java b/src/com/cyanogenmod/eleven/widgets/EqualizerView.java
deleted file mode 100644 (file)
index 58a178e..0000000
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
-* Copyright (C) 2014 The CyanogenMod Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-package com.cyanogenmod.eleven.widgets;
-
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.DashPathEffect;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-
-import com.cyanogenmod.eleven.R;
-import com.pheelicks.visualizer.AudioData;
-import com.pheelicks.visualizer.FFTData;
-import com.pheelicks.visualizer.VisualizerView;
-import com.pheelicks.visualizer.renderer.Renderer;
-
-public class EqualizerView extends VisualizerView {
-    private boolean mLinked = false;
-    private boolean mVisible = false;
-    private boolean mPlaying = false;
-    private boolean mEnabled = false;
-    private int mColor;
-
-    private TileBarGraphRenderer mBarRenderer;
-    private ObjectAnimator mVisualizerColorAnimator;
-
-    private final Runnable mLinkVisualizer = new Runnable() {
-        @Override
-        public void run() {
-            if (!mLinked) {
-                link(0);
-                animate().alpha(1f).setDuration(300);
-                mLinked = true;
-            }
-        }
-    };
-
-    private final Runnable mUnlinkVisualizer = new Runnable() {
-        @Override
-        public void run() {
-            if (mLinked) {
-                animate().alpha(0f).setDuration(300);
-                unlink();
-                mLinked = false;
-            }
-        }
-    };
-
-    private static class TileBarGraphRenderer extends Renderer {
-        private int mDivisions;
-        private Paint mPaint;
-        private int mDbFuzz;
-        private int mDbFuzzFactor;
-
-        /**
-         * Renders the FFT data as a series of lines, in histogram form
-         *
-         * @param divisions - must be a power of 2. Controls how many lines to draw
-         * @param paint     - Paint to draw lines with
-         * @param dbfuzz    - final dB display adjustment
-         * @param dbFactor  - dbfuzz is multiplied by dbFactor.
-         */
-        public TileBarGraphRenderer(int divisions, Paint paint, int dbfuzz, int dbFactor) {
-            super();
-            mDivisions = divisions;
-            mPaint = paint;
-            mDbFuzz = dbfuzz;
-            mDbFuzzFactor = dbFactor;
-        }
-
-        @Override
-        public void onRender(Canvas canvas, AudioData data, Rect rect) {
-            // Do nothing, we only display FFT data
-        }
-
-        @Override
-        public void onRender(Canvas canvas, FFTData data, Rect rect) {
-            for (int i = 0; i < data.bytes.length / mDivisions; i++) {
-                mFFTPoints[i * 4] = i * 4 * mDivisions;
-                mFFTPoints[i * 4 + 2] = i * 4 * mDivisions;
-                byte rfk = data.bytes[mDivisions * i];
-                byte ifk = data.bytes[mDivisions * i + 1];
-                float magnitude = (rfk * rfk + ifk * ifk);
-                int dbValue = magnitude > 0 ? (int) (10 * Math.log10(magnitude)) : 0;
-
-                mFFTPoints[i * 4 + 1] = rect.height();
-                mFFTPoints[i * 4 + 3] = rect.height() - (dbValue * mDbFuzzFactor + mDbFuzz);
-            }
-
-            canvas.drawLines(mFFTPoints, mPaint);
-        }
-    }
-
-    public EqualizerView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    public EqualizerView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public EqualizerView(Context context) {
-        this(context, null, 0);
-    }
-
-    public void initialize(Context context) {
-        setEnabled(false);
-
-        Resources res = mContext.getResources();
-        mColor = res.getColor(R.color.equalizer_fill_color);
-        Paint paint = new Paint();
-        paint.setStrokeWidth(res.getDimensionPixelSize(R.dimen.eqalizer_path_stroke_width));
-        paint.setAntiAlias(true);
-        paint.setColor(mColor);
-        paint.setPathEffect(new DashPathEffect(new float[]{
-                res.getDimensionPixelSize(R.dimen.eqalizer_path_effect_1),
-                res.getDimensionPixelSize(R.dimen.eqalizer_path_effect_2)
-        }, 0));
-
-        int bars = res.getInteger(R.integer.equalizer_divisions);
-        mBarRenderer = new TileBarGraphRenderer(bars, paint,
-                res.getInteger(R.integer.equalizer_db_fuzz),
-                res.getInteger(R.integer.equalizer_db_fuzz_factor));
-        addRenderer(mBarRenderer);
-    }
-
-    public void setVisible(boolean visible) {
-        if (mVisible != visible) {
-            mVisible = visible;
-            checkStateChanged();
-        }
-    }
-
-    public void setPlaying(boolean playing) {
-        if (mPlaying != playing) {
-            mPlaying = playing;
-            checkStateChanged();
-        }
-    }
-
-    public void setEnabled(boolean enabled) {
-        if (mEnabled != enabled) {
-            mEnabled = enabled;
-            checkStateChanged();
-        }
-    }
-
-    public void setColor(int color) {
-        if (mColor != color) {
-            mColor = color;
-            if (mLinked) {
-                if (mVisualizerColorAnimator != null) {
-                    mVisualizerColorAnimator.cancel();
-                }
-                mVisualizerColorAnimator = ObjectAnimator.ofArgb(mBarRenderer.mPaint, "color",
-                        mBarRenderer.mPaint.getColor(), mColor);
-                mVisualizerColorAnimator.setStartDelay(900);
-                mVisualizerColorAnimator.setDuration(1200);
-                mVisualizerColorAnimator.start();
-            } else {
-                mBarRenderer.mPaint.setColor(mColor);
-            }
-        }
-    }
-
-    private void checkStateChanged() {
-        if (mVisible && mPlaying && mEnabled) {
-            mLinkVisualizer.run();
-        } else {
-            mUnlinkVisualizer.run();
-        }
-    }
-}
diff --git a/src/com/cyanogenmod/eleven/widgets/VisualizerView.java b/src/com/cyanogenmod/eleven/widgets/VisualizerView.java
new file mode 100644 (file)
index 0000000..a176bda
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+* Copyright (C) 2014 The CyanogenMod Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.cyanogenmod.eleven.widgets;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.media.audiofx.Visualizer;
+import android.os.AsyncTask;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.cyanogenmod.eleven.R;
+
+public class VisualizerView extends View {
+    private Paint mPaint;
+    private Visualizer mVisualizer;
+    private ObjectAnimator mVisualizerColorAnimator;
+
+    private ValueAnimator[] mValueAnimators = new ValueAnimator[32];
+    private float[] mFFTPoints = new float[128];
+
+    private boolean mVisible = false;
+    private boolean mPlaying = false;
+    private int mColor;
+
+    private Visualizer.OnDataCaptureListener mVisualizerListener =
+            new Visualizer.OnDataCaptureListener() {
+        byte rfk, ifk;
+        int dbValue;
+        float magnitude;
+
+        @Override
+        public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes, int samplingRate) {
+        }
+
+        @Override
+        public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {
+            for (int i = 0; i < 32; i++) {
+                mValueAnimators[i].cancel();
+
+                rfk = fft[i * 2];
+                ifk = fft[i * 2 + 1];
+                magnitude = rfk * rfk + ifk * ifk;
+                dbValue = magnitude > 0 ? (int) (10 * Math.log10(magnitude)) : 0;
+
+                mValueAnimators[i].setFloatValues(mFFTPoints[i * 4 + 1],
+                        mFFTPoints[i * 4 + 3] - (dbValue * 16f));
+                mValueAnimators[i].start();
+            }
+        }
+    };
+
+    private final Runnable mLinkVisualizer = new Runnable() {
+        @Override
+        public void run() {
+            try {
+                mVisualizer = new Visualizer(0);
+            } catch (Exception e) {
+                return;
+            }
+
+            mVisualizer.setEnabled(false);
+            mVisualizer.setCaptureSize(64);
+            mVisualizer.setDataCaptureListener(mVisualizerListener, Visualizer.getMaxCaptureRate(),
+                    false, true);
+            mVisualizer.setEnabled(true);
+        }
+    };
+
+    private final Runnable mUnlinkVisualizer = new Runnable() {
+        @Override
+        public void run() {
+            mVisualizer.setEnabled(false);
+            mVisualizer.release();
+            mVisualizer = null;
+        }
+    };
+
+    public VisualizerView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public VisualizerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public VisualizerView(Context context) {
+        this(context, null, 0);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        float barUnit = w / 32f;
+        float barWidth = barUnit * 8f / 9f;
+        barUnit = barWidth + (barUnit - barWidth) * 32f / 31f;
+        mPaint.setStrokeWidth(barWidth);
+
+        for (int i = 0; i < 32; i++) {
+            mFFTPoints[i * 4] = mFFTPoints[i * 4 + 2] = i * barUnit + (barWidth / 2);
+            mFFTPoints[i * 4 + 3] = h;
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (mVisualizer != null) {
+            canvas.drawLines(mFFTPoints, mPaint);
+        }
+    }
+
+    public void initialize(Context context) {
+        mColor = context.getResources().getColor(R.color.visualizer_fill_color);
+
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setColor(mColor);
+
+        for (int i = 0; i < 32; i++) {
+            final int j = i * 4 + 1;
+            mValueAnimators[i] = new ValueAnimator();
+            mValueAnimators[i].setDuration(128);
+            mValueAnimators[i].addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    mFFTPoints[j] = (float) animation.getAnimatedValue();
+                }
+            });
+        }
+
+        mValueAnimators[31].addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                postInvalidate();
+            }
+        });
+    }
+
+    public void setVisible(boolean visible) {
+        if (mVisible != visible) {
+            mVisible = visible;
+            checkStateChanged();
+        }
+    }
+
+    public void setPlaying(boolean playing) {
+        if (mPlaying != playing) {
+            mPlaying = playing;
+            checkStateChanged();
+        }
+    }
+
+    public void setColor(int color) {
+        color = Color.argb(191, Color.red(color), Color.green(color), Color.blue(color));
+
+        if (mColor != color) {
+            mColor = color;
+
+            if (mVisualizer != null) {
+                if (mVisualizerColorAnimator != null) {
+                    mVisualizerColorAnimator.cancel();
+                }
+
+                mVisualizerColorAnimator = ObjectAnimator.ofArgb(mPaint, "color",
+                        mPaint.getColor(), mColor);
+                mVisualizerColorAnimator.setStartDelay(900);
+                mVisualizerColorAnimator.setDuration(1200);
+                mVisualizerColorAnimator.start();
+            } else {
+                mPaint.setColor(mColor);
+            }
+        }
+    }
+
+    private void checkStateChanged() {
+        if (mVisible && mPlaying) {
+            if (mVisualizer == null) {
+                AsyncTask.execute(mLinkVisualizer);
+                animate().alpha(1f).setDuration(300);
+            }
+        } else {
+            if (mVisualizer != null) {
+                animate().alpha(0f).setDuration(0);
+                AsyncTask.execute(mUnlinkVisualizer);
+            }
+        }
+    }
+}