LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-v8-renderscript \
android-common \
+ android-visualizer \
eleven_support_v4 \
eleven_recyclerview
<!-- Allows Apollo to read from External Storage -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+ <!-- Audio Visualizer Permissions -->
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+
<application
android:name=".ElevenApplication"
android:allowBackup="true"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<include layout="@layout/loading_empty_container" />
+
+ <View
+ android:id="@+id/equalizerGradient"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/equalizer_background"
+ android:layout_gravity="bottom"/>
+
+ <com.cyngn.eleven.widgets.EqualizerView
+ android:id="@+id/equalizerView"
+ android:gravity="bottom"
+ android:layout_gravity="bottom"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:visibility="visible" />
</com.cyngn.eleven.widgets.SquareFrame>
<RelativeLayout
--- /dev/null
+<?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
--- /dev/null
+<?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">1</integer>
+</resources>
\ No newline at end of file
--- /dev/null
+<?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
<string name="settings_download_only_on_wifi_summary">To reduce carrier charges, don\'t download over mobile networks</string>
<string name="settings_download_missing_artwork_title">Download missing album art</string>
<string name="settings_download_artist_images_title">Download missing artist images</string>
+ <string name="settings_general_category">General</string>
+ <string name="settings_show_music_visualization_title">Show music visualization</string>
<!-- App widget -->
<string name="app_widget_small">Music: 4 \u00d7 1</string>
<!--android:key="download_missing_artist_images"-->
<!--android:title="@string/settings_download_artist_images_title" />-->
<!--</PreferenceCategory>-->
+ <PreferenceCategory android:title="@string/settings_general_category" >
+
+ <!-- Music visualizer -->
+ <CheckBoxPreference
+ android:defaultValue="true"
+ android:key="music_visualization"
+ android:title="@string/settings_show_music_visualization_title" />
+ </PreferenceCategory>
<!-- Storage catetory -->
<PreferenceCategory android:title="@string/settings_storage_category" >
import com.cyngn.eleven.utils.MusicUtils;
import com.cyngn.eleven.widgets.BlurScrimImage;
+import java.util.HashSet;
+
/**
* This class is used to display the {@link ViewPager} used to swipe between the
* main {@link Fragment}s used to browse the user's music.
None,
}
+ public static interface ISlidingPanelListener {
+ public void onBeginSlide();
+ public void onFinishSlide(SlidingPanelActivity.Panel visiblePanel);
+ }
+
private SlidingUpPanelLayout mFirstPanel;
private SlidingUpPanelLayout mSecondPanel;
protected Panel mTargetNavigatePanel;
+ private HashSet<ISlidingPanelListener> mSlidingPanelListeners
+ = new HashSet<ISlidingPanelListener>();
private final ShowPanelClickListener mShowBrowse = new ShowPanelClickListener(Panel.Browse);
private final ShowPanelClickListener mShowMusicPlayer = new ShowPanelClickListener(Panel.MusicPlayer);
} else if (slideOffset < 0.75f) {
getActionBar().show();
}
+
+ onSlide();
}
@Override
if (mTargetNavigatePanel == Panel.None) {
mFirstPanel.setSlidingEnabled(false);
}
+
+ onSlide();
}
@Override
}
public void showPanel(Panel panel) {
+ // if we are already at our target panel, then don't do anything
+ if (panel == getCurrentPanel()) {
+ return;
+ }
+
// TODO: Add ability to do this instantaneously as opposed to animate
switch (panel) {
case Browse:
}
}
+ protected void onSlide() {
+ for (ISlidingPanelListener listener : mSlidingPanelListeners) {
+ listener.onBeginSlide();
+ }
+ }
+
/**
* This checks if we are at our target panel and resets our flag if we are there
*/
protected void checkTargetNavigation() {
- if (mTargetNavigatePanel == getCurrentPanel()) {
+ final Panel currentPanel = getCurrentPanel();
+ // This checks if we are at our target panel and resets our flag if we are there
+ if (mTargetNavigatePanel == currentPanel) {
mTargetNavigatePanel = Panel.None;
}
+
+ // if we are at the target panel
+ if (mTargetNavigatePanel == Panel.None) {
+ for (ISlidingPanelListener listener : mSlidingPanelListeners) {
+ listener.onFinishSlide(currentPanel);
+ }
+ }
}
protected Panel getCurrentPanel() {
showPanel(mTargetPanel);
}
}
+
+ public void addSlidingPanelListener(final ISlidingPanelListener listener) {
+ mSlidingPanelListeners.add(listener);
+ }
+
+ public void removeSlidingPanelListener(final ISlidingPanelListener listener) {
+ mSlidingPanelListeners.remove(listener);
+ }
}
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.media.AudioManager;
-import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
-import android.provider.MediaStore.Audio.Albums;
-import android.provider.MediaStore.Audio.Artists;
-import android.provider.MediaStore.Audio.Playlists;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.util.Log;
import com.cyngn.eleven.menu.CreateNewPlaylist;
import com.cyngn.eleven.menu.DeleteDialog;
import com.cyngn.eleven.menu.FragmentMenuItems;
+import com.cyngn.eleven.ui.activities.SlidingPanelActivity;
import com.cyngn.eleven.utils.ApolloUtils;
import com.cyngn.eleven.utils.MusicUtils;
import com.cyngn.eleven.utils.NavUtils;
+import com.cyngn.eleven.utils.PreferenceUtils;
import com.cyngn.eleven.widgets.BrowseButton;
+import com.cyngn.eleven.widgets.EqualizerView;
import com.cyngn.eleven.widgets.LoadingEmptyContainer;
import com.cyngn.eleven.widgets.NoResultsContainer;
import com.cyngn.eleven.widgets.PlayPauseProgressButton;
import static com.cyngn.eleven.utils.MusicUtils.mService;
-public class AudioPlayerFragment extends Fragment implements ServiceConnection {
+public class AudioPlayerFragment extends Fragment implements ServiceConnection,
+ SlidingPanelActivity.ISlidingPanelListener {
private static final String TAG = AudioPlayerFragment.class.getSimpleName();
/**
// Total time
private TextView mTotalTime;
+ // Equalizer View
+ private EqualizerView mEqualizerView;
+
+ // Equalizer Gradient
+ private View mEqualizerGradient;
+
// Broadcast receiver
private PlaybackStatus mPlaybackStatus;
// Initialize the broadcast receiver
mPlaybackStatus = new PlaybackStatus(this);
+
+ // add a listener for the sliding
+ ((SlidingPanelActivity)getActivity()).addSlidingPanelListener(this);
}
/**
initHeaderBar();
initPlaybackControls();
+
+ mEqualizerView = (EqualizerView) mRootView.findViewById(R.id.equalizerView);
+ mEqualizerView.initialize(getActivity());
+ mEqualizerGradient = mRootView.findViewById(R.id.equalizerGradient);
+
return mRootView;
}
// resumes the update callback for the play pause progress button
mPlayPauseProgressButton.resume();
+
+ mEqualizerView.onStart();
}
@Override
mPlayPauseProgressButton.pause();
mImageFetcher.flush();
+
+ mEqualizerView.onStop();
}
@Override
mToken = null;
}
+ ((SlidingPanelActivity)getActivity()).removeSlidingPanelListener(this);
+
// Unregister the receiver
try {
getActivity().unregisterReceiver(mPlaybackStatus);
if(queueSize == 0) {
mAlbumArtViewPager.setVisibility(View.GONE);
mQueueEmpty.showNoResults();
+ mEqualizerGradient.setVisibility(View.GONE);
+ mEqualizerView.checkStateChanged();
mAddToPlaylistButton.setVisibility(View.GONE);
} else {
mAlbumArtViewPager.setVisibility(View.VISIBLE);
mQueueEmpty.hideAll();
+ if (PreferenceUtils.getInstance(getActivity()).getShowVisualizer()) {
+ mEqualizerGradient.setVisibility(View.VISIBLE);
+ } else {
+ mEqualizerGradient.setVisibility(View.GONE);
+ }
+ mEqualizerView.checkStateChanged();
mAddToPlaylistButton.setVisibility(View.VISIBLE);
}
}
return super.onContextItemSelected(item);
}
+ @Override
+ public void onBeginSlide() {
+ mEqualizerView.setPanelVisible(false);
+ }
+
+ @Override
+ public void onFinishSlide(SlidingPanelActivity.Panel visiblePanel) {
+ if (visiblePanel == SlidingPanelActivity.Panel.MusicPlayer) {
+ mEqualizerView.setPanelVisible(true);
+ }
+ }
+
/**
* Used to update the current time string
*/
// datetime cutoff for determining which songs go in last added playlist
public static final String LAST_ADDED_CUTOFF = "last_added_cutoff";
+ // show visualizer flag
+ public static final String SHOW_VISUALIZER = "music_visualization";
+
private static PreferenceUtils sInstance;
private final SharedPreferences mPreferences;
public long getLastAddedCutoff() {
return mPreferences.getLong(LAST_ADDED_CUTOFF, 0L);
}
+
+ public boolean getShowVisualizer() {
+ return mPreferences.getBoolean(SHOW_VISUALIZER, true);
+ }
}
--- /dev/null
+/*
+ * Copyright (C) 2014 Cyanogen, Inc.
+ */
+package com.cyngn.eleven.widgets;
+
+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 android.util.Log;
+
+import com.cyngn.eleven.R;
+import com.cyngn.eleven.utils.MusicUtils;
+import com.cyngn.eleven.utils.PreferenceUtils;
+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 mStarted = false;
+ private boolean mPanelVisible = false;
+
+ private final Runnable mLinkVisualizer = new Runnable() {
+ @Override
+ public void run() {
+ if (!mLinked) {
+ animate().alpha(1).setDuration(300);
+ link(0);
+ mLinked = true;
+ }
+ }
+ };
+
+ private final Runnable mUnlinkVisualizer = new Runnable() {
+ @Override
+ public void run() {
+ if (mLinked) {
+ animate().alpha(0).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 = (int) (10 * Math.log10(magnitude));
+
+ 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();
+ Paint paint = new Paint();
+ paint.setStrokeWidth(res.getDimensionPixelSize(R.dimen.eqalizer_path_stroke_width));
+ paint.setAntiAlias(true);
+ paint.setColor(res.getColor(R.color.equalizer_fill_color));
+ 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);
+ addRenderer(new TileBarGraphRenderer(bars, paint,
+ res.getInteger(R.integer.equalizer_db_fuzz),
+ res.getInteger(R.integer.equalizer_db_fuzz_factor)));
+ }
+
+ /**
+ * Follows Fragment onStart to determine if the containing fragment/activity is started
+ */
+ public void onStart() {
+ mStarted = true;
+ checkStateChanged();
+ }
+
+ /**
+ * Follows Fragment onStop to determine if the containing fragment/activity is stopped
+ */
+ public void onStop() {
+ mStarted = false;
+ checkStateChanged();
+ }
+
+ /**
+ * Separate method to toggle panel visibility - currently used when the user slides to
+ * improve performance of the sliding panel
+ */
+ public void setPanelVisible(boolean panelVisible) {
+ if (mPanelVisible != panelVisible) {
+ mPanelVisible = panelVisible;
+ checkStateChanged();
+ }
+ }
+
+ /**
+ * Checks the state of the EqualizerView to determine whether we want to link up the equalizer
+ */
+ public void checkStateChanged() {
+ if (mPanelVisible && mStarted
+ && PreferenceUtils.getInstance(mContext).getShowVisualizer()
+ && MusicUtils.getQueueSize() > 0) {
+ mLinkVisualizer.run();
+ } else {
+ mUnlinkVisualizer.run();
+ }
+ }
+}