Updated Equalizer and Visualizer to use late initialization with LiveData

This commit is contained in:
Nite 2020-10-17 12:35:30 +02:00
parent 356af198e0
commit 0482c540bd
No known key found for this signature in database
GPG Key ID: 1D1AD59B1C6386C1
8 changed files with 232 additions and 313 deletions

View File

@ -49,10 +49,14 @@ import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.ViewFlipper;
import androidx.lifecycle.Observer;
import com.mobeta.android.dslv.DragSortListView;
import org.koin.java.KoinJavaComponent;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.audiofx.EqualizerController;
import org.moire.ultrasonic.audiofx.VisualizerController;
import org.moire.ultrasonic.data.ActiveServerProvider;
import org.moire.ultrasonic.domain.MusicDirectory;
import org.moire.ultrasonic.domain.MusicDirectory.Entry;
@ -117,8 +121,6 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
private int swipeDistance;
private int swipeVelocity;
private VisualizerView visualizerView;
private boolean visualizerAvailable;
private boolean equalizerAvailable;
private boolean jukeboxAvailable;
private SilentBackgroundTask<Void> onProgressChangedTask;
LinearLayout visualizerViewLayout;
@ -133,6 +135,9 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
private Drawable hollowStar;
private Drawable fullStar;
private boolean isEqualizerAvailable;
private boolean isVisualizerAvailable;
/**
* Called when the activity is first created.
*/
@ -506,8 +511,55 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
mediaPlayerController.setShufflePlayEnabled(true);
}
visualizerAvailable = (mediaPlayerController != null) && (mediaPlayerController.getVisualizerController() != null);
equalizerAvailable = (mediaPlayerController != null) && (mediaPlayerController.getEqualizerController() != null);
visualizerViewLayout.setVisibility(View.GONE);
VisualizerController.get().observe(this, new Observer<VisualizerController>() {
@Override
public void onChanged(VisualizerController visualizerController) {
if (visualizerController != null) {
Timber.d("VisualizerController Observer.onChanged received controller");
visualizerView = new VisualizerView(DownloadActivity.this);
visualizerViewLayout.addView(visualizerView, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
if (!visualizerView.isActive())
{
visualizerViewLayout.setVisibility(View.GONE);
}
else
{
visualizerViewLayout.setVisibility(View.VISIBLE);
}
visualizerView.setOnTouchListener(new View.OnTouchListener()
{
@Override
public boolean onTouch(final View view, final MotionEvent motionEvent)
{
visualizerView.setActive(!visualizerView.isActive());
getMediaPlayerController().setShowVisualization(visualizerView.isActive());
return true;
}
});
isVisualizerAvailable = true;
} else {
Timber.d("VisualizerController Observer.onChanged has no controller");
visualizerViewLayout.setVisibility(View.GONE);
isVisualizerAvailable = false;
}
}
});
EqualizerController.get().observe(this, new Observer<EqualizerController>() {
@Override
public void onChanged(EqualizerController equalizerController) {
if (equalizerController != null) {
Timber.d("EqualizerController Observer.onChanged received controller");
isEqualizerAvailable = true;
} else {
Timber.d("EqualizerController Observer.onChanged has no controller");
isEqualizerAvailable = false;
}
}
});
new Thread(new Runnable()
{
@ -528,36 +580,6 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
final View nowPlayingMenuItem = findViewById(R.id.menu_now_playing);
menuDrawer.setActiveView(nowPlayingMenuItem);
if (visualizerAvailable)
{
visualizerView = new VisualizerView(this);
visualizerViewLayout.addView(visualizerView, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
if (!visualizerView.isActive())
{
visualizerViewLayout.setVisibility(View.GONE);
}
else
{
visualizerViewLayout.setVisibility(View.VISIBLE);
}
visualizerView.setOnTouchListener(new View.OnTouchListener()
{
@Override
public boolean onTouch(final View view, final MotionEvent motionEvent)
{
visualizerView.setActive(!visualizerView.isActive());
getMediaPlayerController().setShowVisualization(visualizerView.isActive());
return true;
}
});
}
else
{
visualizerViewLayout.setVisibility(View.GONE);
}
}
@Override
@ -768,14 +790,14 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
if (equalizerMenuItem != null)
{
equalizerMenuItem.setEnabled(equalizerAvailable);
equalizerMenuItem.setVisible(equalizerAvailable);
equalizerMenuItem.setEnabled(isEqualizerAvailable);
equalizerMenuItem.setVisible(isEqualizerAvailable);
}
if (visualizerMenuItem != null)
{
visualizerMenuItem.setEnabled(visualizerAvailable);
visualizerMenuItem.setVisible(visualizerAvailable);
visualizerMenuItem.setEnabled(isVisualizerAvailable);
visualizerMenuItem.setVisible(isVisualizerAvailable);
}
final MediaPlayerController mediaPlayerController = getMediaPlayerController();

View File

@ -30,6 +30,8 @@ import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.lifecycle.Observer;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.audiofx.EqualizerController;
import org.moire.ultrasonic.service.MediaPlayerController;
@ -38,6 +40,7 @@ import java.util.HashMap;
import java.util.Map;
import kotlin.Lazy;
import timber.log.Timber;
import static org.koin.java.KoinJavaComponent.inject;
@ -55,21 +58,34 @@ public class EqualizerActivity extends ResultActivity
private EqualizerController equalizerController;
private Equalizer equalizer;
private Lazy<MediaPlayerController> mediaPlayerControllerLazy = inject(MediaPlayerController.class);
@Override
public void onCreate(Bundle bundle)
{
super.onCreate(bundle);
setContentView(R.layout.equalizer);
setup();
EqualizerController.get().observe(this, new Observer<EqualizerController>() {
@Override
public void onChanged(EqualizerController controller) {
if (controller != null) {
Timber.d("EqualizerController Observer.onChanged received controller");
equalizerController = controller;
equalizer = controller.equalizer;
setup();
} else {
Timber.d("EqualizerController Observer.onChanged has no controller");
equalizerController = null;
equalizer = null;
}
}
});
}
@Override
protected void onPause()
{
super.onPause();
if (equalizerController == null) return;
equalizerController.saveSettings();
}
@ -77,16 +93,13 @@ public class EqualizerActivity extends ResultActivity
protected void onResume()
{
super.onResume();
if (equalizerController == null)
{
setup();
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo)
{
super.onCreateContextMenu(menu, view, menuInfo);
if (equalizer == null) return;
short currentPreset;
try
@ -112,6 +125,7 @@ public class EqualizerActivity extends ResultActivity
@Override
public boolean onContextItemSelected(MenuItem menuItem)
{
if (equalizer == null) return true;
try
{
short preset = (short) menuItem.getItemId();
@ -128,9 +142,6 @@ public class EqualizerActivity extends ResultActivity
private void setup()
{
equalizerController = mediaPlayerControllerLazy.getValue().getEqualizerController();
equalizer = equalizerController.getEqualizer();
initEqualizer();
final View presetButton = findViewById(R.id.equalizer_preset);
@ -158,12 +169,14 @@ public class EqualizerActivity extends ResultActivity
private void setEqualizerEnabled(boolean enabled)
{
if (equalizer == null) return;
equalizer.setEnabled(enabled);
updateBars();
}
private void updateBars()
{
if (equalizer == null) return;
try
{
for (Map.Entry<Short, SeekBar> entry : bars.entrySet())
@ -183,6 +196,7 @@ public class EqualizerActivity extends ResultActivity
private void initEqualizer()
{
if (equalizer == null) return;
LinearLayout layout = (LinearLayout) findViewById(R.id.equalizer_layout);
try

View File

@ -21,6 +21,10 @@ package org.moire.ultrasonic.audiofx;
import android.content.Context;
import android.media.MediaPlayer;
import android.media.audiofx.Equalizer;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import timber.log.Timber;
import org.moire.ultrasonic.util.FileUtil;
@ -35,61 +39,83 @@ import java.io.Serializable;
*/
public class EqualizerController
{
private final Context context;
private Equalizer equalizer;
private boolean released;
private static Boolean available = null;
private static MutableLiveData<EqualizerController> instance = new MutableLiveData<>();
private Context context;
public Equalizer equalizer;
private int audioSessionId;
// Class initialization fails when this throws an exception.
static
/**
* Retrieves the EqualizerController as LiveData
*/
public static LiveData<EqualizerController> get()
{
try
{
Class.forName("android.media.audiofx.Equalizer");
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
return instance;
}
/**
* Throws an exception if the {@link Equalizer} class is not available.
* Initializes the EqualizerController instance with a MediaPlayer
*/
public static void checkAvailable() throws Throwable
public static void create(Context context, MediaPlayer mediaPlayer)
{
// Calling here forces class initialization.
}
if (mediaPlayer == null) return;
if (!isAvailable()) return;
public EqualizerController(Context context, MediaPlayer mediaPlayer)
{
this.context = context;
EqualizerController controller = new EqualizerController();
controller.context = context;
try
{
if (mediaPlayer == null)
{
return;
}
controller.audioSessionId = mediaPlayer.getAudioSessionId();
controller.equalizer = new Equalizer(0, controller.audioSessionId);
controller.loadSettings();
audioSessionId = mediaPlayer.getAudioSessionId();
equalizer = new Equalizer(0, audioSessionId);
instance.postValue(controller);
}
catch (Throwable x)
{
equalizer = null;
Timber.w(x, "Failed to create equalizer.");
}
}
public void saveSettings()
/**
* Releases the EqualizerController instance when the underlying MediaPlayer is no longer available
*/
public static void release()
{
EqualizerController controller = instance.getValue();
if (controller == null) return;
controller.equalizer.release();
instance.postValue(null);
}
/**
* Checks if the {@link Equalizer} class is available.
*/
private static boolean isAvailable()
{
if (available != null) return available;
try
{
if (isAvailable())
{
FileUtil.serialize(context, new EqualizerSettings(equalizer), "equalizer.dat");
}
Class.forName("android.media.audiofx.Equalizer");
available = true;
}
catch (Exception ex)
{
Timber.i(ex, "CheckAvailable received an exception getting class for the Equalizer");
available = false;
}
return available;
}
public void saveSettings()
{
if (!available) return;
try
{
FileUtil.serialize(context, new EqualizerSettings(equalizer), "equalizer.dat");
}
catch (Throwable x)
{
@ -99,16 +125,14 @@ public class EqualizerController
public void loadSettings()
{
if (!available) return;
try
{
if (isAvailable())
{
EqualizerSettings settings = FileUtil.deserialize(context, "equalizer.dat");
EqualizerSettings settings = FileUtil.deserialize(context, "equalizer.dat");
if (settings != null)
{
settings.apply(equalizer);
}
if (settings != null)
{
settings.apply(equalizer);
}
}
catch (Throwable x)
@ -117,46 +141,8 @@ public class EqualizerController
}
}
public boolean isAvailable()
{
return equalizer != null;
}
public void release()
{
if (isAvailable())
{
released = true;
equalizer.release();
}
}
public Equalizer getEqualizer()
{
if (released)
{
released = false;
try
{
equalizer = new Equalizer(0, audioSessionId);
}
catch (Throwable x)
{
equalizer = null;
Timber.w(x, "Failed to create equalizer.");
}
}
return equalizer;
}
private static class EqualizerSettings implements Serializable
{
/**
*
*/
private static final long serialVersionUID = 626565082425206061L;
private final short[] bandLevels;
private short preset;

View File

@ -20,6 +20,10 @@ package org.moire.ultrasonic.audiofx;
import android.media.MediaPlayer;
import android.media.audiofx.Visualizer;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import timber.log.Timber;
/**
@ -31,89 +35,76 @@ import timber.log.Timber;
public class VisualizerController
{
private static final int PREFERRED_CAPTURE_SIZE = 128; // Must be a power of two.
private static Boolean available = null;
private static MutableLiveData<VisualizerController> instance = new MutableLiveData<>();
private Visualizer visualizer;
private boolean released;
public Visualizer visualizer;
private int audioSessionId;
// Class initialization fails when this throws an exception.
static
/**
* Retrieves the VisualizerController as LiveData
*/
public static LiveData<VisualizerController> get()
{
try
{
Class.forName("android.media.audiofx.Visualizer");
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
return instance;
}
/**
* Throws an exception if the {@link Visualizer} class is not available.
* Initializes the VisualizerController instance with a MediaPlayer
*/
public static void checkAvailable() throws Throwable
public static void create(MediaPlayer mediaPlayer)
{
// Calling here forces class initialization.
}
if (mediaPlayer == null) return;
if (!isAvailable()) return;
VisualizerController controller = new VisualizerController();
public VisualizerController(MediaPlayer mediaPlayer)
{
try
{
if (mediaPlayer == null)
{
return;
}
controller.audioSessionId = mediaPlayer.getAudioSessionId();
controller.visualizer = new Visualizer(controller.audioSessionId);
audioSessionId = mediaPlayer.getAudioSessionId();
visualizer = new Visualizer(audioSessionId);
int[] captureSizeRange = Visualizer.getCaptureSizeRange();
int captureSize = Math.max(PREFERRED_CAPTURE_SIZE, captureSizeRange[0]);
captureSize = Math.min(captureSize, captureSizeRange[1]);
controller.visualizer.setCaptureSize(captureSize);
instance.postValue(controller);
}
catch (Throwable x)
{
Timber.w(x, "Failed to create visualizer.");
}
if (visualizer != null)
{
int[] captureSizeRange = Visualizer.getCaptureSizeRange();
int captureSize = Math.max(PREFERRED_CAPTURE_SIZE, captureSizeRange[0]);
captureSize = Math.min(captureSize, captureSizeRange[1]);
visualizer.setCaptureSize(captureSize);
}
}
public boolean isAvailable()
/**
* Releases the VisualizerController instance when the underlying MediaPlayer is no longer available
*/
public static void release()
{
return visualizer != null;
VisualizerController controller = instance.getValue();
if (controller == null) return;
controller.visualizer.release();
instance.postValue(null);
}
public void release()
/**
* Checks if the {@link Visualizer} class is available.
*/
private static boolean isAvailable()
{
if (isAvailable())
if (available != null) return available;
try
{
visualizer.release();
released = true;
Class.forName("android.media.audiofx.Visualizer");
available = true;
}
}
public Visualizer getVisualizer()
{
if (released)
catch (Exception ex)
{
released = false;
try
{
visualizer = new Visualizer(audioSessionId);
}
catch (Throwable x)
{
visualizer = null;
Timber.w(x, "Failed to create visualizer.");
}
Timber.i(ex, "CheckAvailable received an exception getting class for the Visualizer");
available = false;
}
return visualizer;
return available;
}
}

View File

@ -55,9 +55,6 @@ public class LocalMediaPlayer
public Runnable onPrepared;
public Runnable onNextSongRequested;
public static boolean equalizerAvailable;
public static boolean visualizerAvailable;
public PlayerState playerState = IDLE;
public DownloadFile currentPlaying;
public DownloadFile nextPlaying;
@ -77,8 +74,6 @@ public class LocalMediaPlayer
private AudioManager audioManager;
private RemoteControlClient remoteControlClient;
private EqualizerController equalizerController;
private VisualizerController visualizerController;
private CancellableTask bufferTask;
private PositionCache positionCache;
private int secondaryProgress = -1;
@ -86,32 +81,6 @@ public class LocalMediaPlayer
private final AudioFocusHandler audioFocusHandler;
private final Context context;
static
{
try
{
EqualizerController.checkAvailable();
equalizerAvailable = true;
}
catch (Throwable t)
{
equalizerAvailable = false;
}
}
static
{
try
{
VisualizerController.checkAvailable();
visualizerAvailable = true;
}
catch (Throwable t)
{
visualizerAvailable = false;
}
}
public LocalMediaPlayer(AudioFocusHandler audioFocusHandler, Context context)
{
this.audioFocusHandler = audioFocusHandler;
@ -164,6 +133,15 @@ public class LocalMediaPlayer
}
}).start();
// Create Equalizer and Visualizer on a new thread as this can potentially take some time
new Thread(new Runnable() {
@Override
public void run() {
EqualizerController.create(context, mediaPlayer);
VisualizerController.create(mediaPlayer);
}
}).start();
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
wakeLock.setReferenceCounted(false);
@ -172,28 +150,6 @@ public class LocalMediaPlayer
Util.registerMediaButtonEventReceiver(context, true);
setUpRemoteControlClient();
if (equalizerAvailable)
{
equalizerController = new EqualizerController(context, mediaPlayer);
if (!equalizerController.isAvailable())
{
equalizerController = null;
}
else
{
equalizerController.loadSettings();
}
}
if (visualizerAvailable)
{
visualizerController = new VisualizerController(mediaPlayer);
if (!visualizerController.isAvailable())
{
visualizerController = null;
}
}
Timber.i("LocalMediaPlayer created");
}
@ -208,6 +164,9 @@ public class LocalMediaPlayer
i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());
context.sendBroadcast(i);
EqualizerController.release();
VisualizerController.release();
mediaPlayer.release();
if (nextMediaPlayer != null)
{
@ -216,16 +175,6 @@ public class LocalMediaPlayer
mediaPlayerLooper.quit();
if (equalizerController != null)
{
equalizerController.release();
}
if (visualizerController != null)
{
visualizerController.release();
}
if (bufferTask != null)
{
bufferTask.cancel();
@ -249,36 +198,6 @@ public class LocalMediaPlayer
Timber.i("LocalMediaPlayer destroyed");
}
public EqualizerController getEqualizerController()
{
if (equalizerAvailable && equalizerController == null)
{
equalizerController = new EqualizerController(context, mediaPlayer);
if (!equalizerController.isAvailable())
{
equalizerController = null;
}
else
{
equalizerController.loadSettings();
}
}
return equalizerController;
}
public VisualizerController getVisualizerController()
{
if (visualizerAvailable && visualizerController == null)
{
visualizerController = new VisualizerController(mediaPlayer);
if (!visualizerController.isAvailable())
{
visualizerController = null;
}
}
return visualizerController;
}
public synchronized void setPlayerState(final PlayerState playerState)
{
Timber.i("%s -> %s (%s)", this.playerState.name(), playerState.name(), currentPlaying);

View File

@ -93,10 +93,6 @@ public interface MediaPlayerController
String getSuggestedPlaylistName();
EqualizerController getEqualizerController();
VisualizerController getVisualizerController();
boolean isJukeboxEnabled();
boolean isJukeboxAvailable();

View File

@ -501,22 +501,6 @@ public class MediaPlayerControllerImpl implements MediaPlayerController
return suggestedPlaylistName;
}
@Override
public EqualizerController getEqualizerController()
{
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
if (mediaPlayerService == null) return null;
return localMediaPlayer.getEqualizerController();
}
@Override
public VisualizerController getVisualizerController()
{
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
if (mediaPlayerService == null) return null;
return localMediaPlayer.getVisualizerController();
}
@Override
public boolean isJukeboxEnabled()
{

View File

@ -25,11 +25,15 @@ import android.graphics.Paint;
import android.media.audiofx.Visualizer;
import android.view.View;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.Observer;
import org.moire.ultrasonic.audiofx.VisualizerController;
import org.moire.ultrasonic.domain.PlayerState;
import org.moire.ultrasonic.service.MediaPlayerController;
import kotlin.Lazy;
import timber.log.Timber;
import static org.koin.java.KoinJavaComponent.inject;
@ -45,20 +49,35 @@ public class VisualizerView extends View
private static final int PREFERRED_CAPTURE_RATE_MILLIHERTZ = 20000;
private final Paint paint = new Paint();
private Lazy<MediaPlayerController> mediaPlayerControllerLazy = inject(MediaPlayerController.class);
private byte[] data;
private float[] points;
private boolean active;
private Visualizer visualizer;
private Lazy<MediaPlayerController> mediaPlayerControllerLazy = inject(MediaPlayerController.class);
public VisualizerView(Context context)
public VisualizerView(final Context context)
{
super(context);
paint.setStrokeWidth(2f);
paint.setAntiAlias(true);
paint.setColor(Color.rgb(0, 153, 204));
VisualizerController.get().observe((LifecycleOwner) context, new Observer<VisualizerController>() {
@Override
public void onChanged(VisualizerController controller) {
if (controller != null) {
Timber.d("VisualizerController Observer.onChanged received controller");
visualizer = controller.visualizer;
setActive(true);
} else {
Timber.d("VisualizerController Observer.onChanged has no controller");
visualizer = null;
setActive(false);
}
}
});
}
public boolean isActive()
@ -66,15 +85,9 @@ public class VisualizerView extends View
return active;
}
public void setActive(boolean active)
public void setActive(boolean value)
{
this.active = active;
Visualizer visualizer = getVizualizer();
if (visualizer == null)
{
return;
}
active = value;
int captureRate = Math.min(PREFERRED_CAPTURE_RATE_MILLIHERTZ, Visualizer.getMaxCaptureRate());
if (active)
{
@ -94,19 +107,13 @@ public class VisualizerView extends View
}
else
{
visualizer.setDataCaptureListener(null, captureRate, false, false);
if (visualizer != null) visualizer.setDataCaptureListener(null, captureRate, false, false);
}
visualizer.setEnabled(active);
if (visualizer != null) visualizer.setEnabled(active);
invalidate();
}
private Visualizer getVizualizer()
{
VisualizerController visualizerController = mediaPlayerControllerLazy.getValue().getVisualizerController();
return visualizerController == null ? null : visualizerController.getVisualizer();
}
private void updateVisualizer(byte[] waveform)
{
this.data = waveform;