600 lines
23 KiB
Java
600 lines
23 KiB
Java
|
package com.affectiva.affdexme;
|
||
|
|
||
|
import android.app.Activity;
|
||
|
import android.app.Fragment;
|
||
|
import android.content.SharedPreferences;
|
||
|
import android.content.res.Resources;
|
||
|
import android.graphics.SurfaceTexture;
|
||
|
import android.media.MediaPlayer;
|
||
|
import android.net.Uri;
|
||
|
import android.os.Build;
|
||
|
import android.os.Bundle;
|
||
|
import android.preference.PreferenceManager;
|
||
|
import android.util.Log;
|
||
|
import android.view.LayoutInflater;
|
||
|
import android.view.Surface;
|
||
|
import android.view.TextureView;
|
||
|
import android.view.View;
|
||
|
import android.view.ViewGroup;
|
||
|
import android.widget.Button;
|
||
|
import android.widget.GridLayout;
|
||
|
import android.widget.TextView;
|
||
|
|
||
|
import static com.affectiva.affdexme.MainActivity.NUM_METRICS_DISPLAYED;
|
||
|
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.HashMap;
|
||
|
/**
|
||
|
* A fragment to display a graphical menu which allows the user to select which metrics to display.
|
||
|
*
|
||
|
*/
|
||
|
public class MetricSelectionFragment extends Fragment implements View.OnClickListener {
|
||
|
|
||
|
final static String LOG_TAG = "Affectiva";
|
||
|
|
||
|
int numberOfSelectedItems = 0;
|
||
|
|
||
|
int messageAtOrUnderLimitColor;
|
||
|
int messageOverLimitColor;
|
||
|
|
||
|
SharedPreferences sharedPreferences;
|
||
|
|
||
|
TextView metricChooserTextView;
|
||
|
GridLayout gridLayout;
|
||
|
Button clearAllButton;
|
||
|
|
||
|
HashMap<MetricsManager.Metrics, MetricSelector> metricSelectors = new HashMap<>();
|
||
|
|
||
|
//An inner class object to control video playback in the metricSelectors
|
||
|
MetricSelectionFragmentMediaPlayer fragmentMediaPlayer;
|
||
|
|
||
|
@Override
|
||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||
|
View fragmentLayout = inflater.inflate(R.layout.metric_chooser, container, false);
|
||
|
|
||
|
initUI(fragmentLayout);
|
||
|
|
||
|
fragmentMediaPlayer = new MetricSelectionFragmentMediaPlayer();
|
||
|
|
||
|
restoreSettings(savedInstanceState);
|
||
|
|
||
|
//We post the methods used to populate the gridLayout view so that they run when gridLayout has been added to the layout and sized.
|
||
|
gridLayout.post(new Runnable() {
|
||
|
@Override
|
||
|
public void run() {
|
||
|
populateGrid();
|
||
|
updateAllGridItems();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return fragmentLayout;
|
||
|
}
|
||
|
|
||
|
void initUI(View fragmentLayout) {
|
||
|
gridLayout = (GridLayout) fragmentLayout.findViewById(R.id.metric_chooser_gridlayout);
|
||
|
metricChooserTextView = (TextView) fragmentLayout.findViewById(R.id.metrics_chooser_textview);
|
||
|
clearAllButton = (Button) fragmentLayout.findViewById(R.id.clear_all_button);
|
||
|
|
||
|
clearAllButton.setOnClickListener(
|
||
|
new View.OnClickListener() {
|
||
|
@Override
|
||
|
public void onClick(View v) {
|
||
|
clearItems();
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
|
||
|
Resources res = getResources();
|
||
|
messageAtOrUnderLimitColor = res.getColor(R.color.white);
|
||
|
messageOverLimitColor = res.getColor(R.color.red);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* A method to populate the metricSelectors array using information from either a saved instance bundle (if the activity is being re-created)
|
||
|
* or sharedPreferences (if the activity is being created for the first time)
|
||
|
*/
|
||
|
void restoreSettings(Bundle bundle) {
|
||
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||
|
|
||
|
Activity hostActivity = getActivity();
|
||
|
LayoutInflater inflater = hostActivity.getLayoutInflater();
|
||
|
Resources res = getResources();
|
||
|
String packageName = hostActivity.getPackageName();
|
||
|
|
||
|
//populate metricSelectors list with objects
|
||
|
for (MetricsManager.Metrics metric : MetricsManager.getAllMetrics()) {
|
||
|
metricSelectors.put(metric, new MetricSelector(hostActivity, inflater, res, packageName, metric));
|
||
|
}
|
||
|
|
||
|
if (bundle != null) { //if we were passed a bundle, use its data to configure the MetricSelectors
|
||
|
for (MetricsManager.Metrics metric : MetricsManager.getAllMetrics()) {
|
||
|
if (bundle.getBoolean(metric.toString(),false)) {
|
||
|
selectItem(metricSelectors.get(metric),true,false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else { //otherwise, we pull the data from application preferences
|
||
|
for (int i = 0; i < NUM_METRICS_DISPLAYED; i++) {
|
||
|
MetricsManager.Metrics chosenMetric = PreferencesUtils.getMetricFromPrefs(sharedPreferences, i);
|
||
|
selectItem(metricSelectors.get(chosenMetric),true,false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onSaveInstanceState(Bundle bundle) {
|
||
|
//save whether each MetricSelector has been selected, using the metric name as the key
|
||
|
for (MetricsManager.Metrics metric : MetricsManager.getAllMetrics()) {
|
||
|
bundle.putBoolean(metric.toString(), metricSelectors.get(metric).getIsSelected());
|
||
|
}
|
||
|
super.onSaveInstanceState(bundle);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* When the app is minimized, the active TextureView in fragmentMediaPlayer is destroyed, but throws an
|
||
|
* exception if we try to remove it from its parent while in the destroyed state. Since fragmentMediaPlayer
|
||
|
* begins assuming the TextureView is not attached to any parent, we run through all MetricSelectors and command
|
||
|
* them to let go of the TextureView if they are the current parent.
|
||
|
*/
|
||
|
@Override
|
||
|
public void onResume() {
|
||
|
super.onResume();
|
||
|
for (MetricsManager.Metrics metric : MetricsManager.getAllMetrics()) {
|
||
|
fragmentMediaPlayer.stopMetricSelectorPlayback(metricSelectors.get(metric));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onPause() {
|
||
|
super.onPause();
|
||
|
saveSettings();
|
||
|
}
|
||
|
|
||
|
/* Our goal in this method is to ensure that 6 and only 6 metrics are saved in Preferences. We attempt to fill all 6 slots
|
||
|
with metrics chosen by the user, but if the user did not choose 6 slots, we fill the remaining slots with any other metrics.
|
||
|
*/
|
||
|
void saveSettings() {
|
||
|
|
||
|
ArrayList<MetricsManager.Metrics> selectedMetrics = new ArrayList<>();
|
||
|
|
||
|
//Add all selected metrics
|
||
|
for (MetricsManager.Metrics metric : MetricsManager.getAllMetrics()) {
|
||
|
if (metricSelectors.get(metric).getIsSelected()) {
|
||
|
selectedMetrics.add(metric);
|
||
|
if (selectedMetrics.size() >= NUM_METRICS_DISPLAYED) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//fill remaining empty slots
|
||
|
if (selectedMetrics.size() < NUM_METRICS_DISPLAYED) {
|
||
|
for (MetricsManager.Metrics metric : MetricsManager.getAllMetrics()) {
|
||
|
if (!selectedMetrics.contains(metric)) {
|
||
|
selectedMetrics.add(metric);
|
||
|
if (selectedMetrics.size() >= NUM_METRICS_DISPLAYED) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//save list into application preferences
|
||
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||
|
for (int n = 0; n < selectedMetrics.size(); n++) {
|
||
|
PreferencesUtils.saveMetricToPrefs(editor, n, selectedMetrics.get(n));
|
||
|
}
|
||
|
editor.commit();
|
||
|
}
|
||
|
|
||
|
/* While Android offers a GridView which can automatically populate a grid from an array, we wished to divide our grid items into 'Emotions' and 'Expressions'
|
||
|
categories. Therefore, we implement a scrollable GridLayout.
|
||
|
Furthermore, while we wished for the grid items to take up the entire grid, Android versions lower than 21 do not support the concept of weights in a GridLayout,
|
||
|
so we manually size the grid items in this method.
|
||
|
Note that since this method is posted as a runnable of gridLayout, it should only be run once gridLayout has been added to the layout and sized.
|
||
|
*/
|
||
|
void populateGrid() {
|
||
|
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||
|
Resources res = getResources();
|
||
|
int minColumnWidth = res.getDimensionPixelSize(R.dimen.metric_chooser_column_width);
|
||
|
|
||
|
//calculate number of columns
|
||
|
int gridWidth = gridLayout.getWidth();
|
||
|
int numColumns = gridWidth / minColumnWidth; //intentional integer division
|
||
|
if (numColumns <= 0) {
|
||
|
Log.e(LOG_TAG, "Desired Column Width too large! Unable to populate Grid");
|
||
|
return;
|
||
|
}
|
||
|
int columnWidth = (int)((float) gridWidth / (float)numColumns);
|
||
|
|
||
|
//This integer reference will be used across methods to keep track of how many rows we have created.
|
||
|
//Each method we pass it into leaves it at a value indicating the next row number that views should be added to.
|
||
|
IntRef currentRow = new IntRef();
|
||
|
|
||
|
addHeader("Emotions", currentRow, numColumns, inflater);
|
||
|
addGridItems(currentRow, numColumns, inflater, res, columnWidth, MetricsManager.Emotions.values());
|
||
|
addHeader("Expressions", currentRow, numColumns, inflater);
|
||
|
addGridItems(currentRow, numColumns, inflater, res, columnWidth, MetricsManager.Expressions.values());
|
||
|
|
||
|
gridLayout.setColumnCount(numColumns);
|
||
|
gridLayout.setRowCount(currentRow.value);
|
||
|
}
|
||
|
|
||
|
//adds a header (consisting of a TextView and border line) to the grid
|
||
|
void addHeader(String name, IntRef currentRow, int numColumns, LayoutInflater inflater) {
|
||
|
|
||
|
View header = inflater.inflate(R.layout.grid_header, null);
|
||
|
|
||
|
//each header should take up one row and all available columns
|
||
|
GridLayout.LayoutParams params = new GridLayout.LayoutParams(GridLayout.spec(currentRow.value, 1), GridLayout.spec(0, numColumns));
|
||
|
params.width = gridLayout.getWidth();
|
||
|
header.setLayoutParams(params);
|
||
|
gridLayout.addView(header);
|
||
|
|
||
|
((TextView) header.findViewById(R.id.header_text)).setText(name);
|
||
|
|
||
|
currentRow.value += 1; //point currentRow to row where next views should be added
|
||
|
}
|
||
|
|
||
|
//adds a set of metrics (using the data from the MetricsManager object from index 'start' to index 'end')
|
||
|
void addGridItems(IntRef currentRow, int numColumns, LayoutInflater inflater, Resources res, int size, MetricsManager.Metrics[] metricsToAdd) {
|
||
|
|
||
|
//keeps track of the column we are adding to
|
||
|
int col = -1; //start col at -1 so it becomes 0 during first iteration of for loop
|
||
|
|
||
|
for (MetricsManager.Metrics metric : metricsToAdd) {
|
||
|
|
||
|
col += 1;
|
||
|
if (col >= numColumns) {
|
||
|
col = 0;
|
||
|
currentRow.value += 1;
|
||
|
}
|
||
|
|
||
|
MetricSelector item = metricSelectors.get(metric);
|
||
|
|
||
|
GridLayout.LayoutParams params = new GridLayout.LayoutParams();
|
||
|
params.width = size;
|
||
|
params.height = size;
|
||
|
params.columnSpec = GridLayout.spec(col);
|
||
|
params.rowSpec = GridLayout.spec(currentRow.value);
|
||
|
item.setLayoutParams(params);
|
||
|
|
||
|
item.setOnClickListener(this);
|
||
|
gridLayout.addView(item);
|
||
|
}
|
||
|
currentRow.value +=1; //point currentRow to row where next views should be added
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onClick(View v) {
|
||
|
MetricSelector item = (MetricSelector) v;
|
||
|
selectItem(item, !item.getIsSelected(), true); //select item if de-selected, and vice-versa
|
||
|
updateAllGridItems(); //each click will result in all items being updated
|
||
|
}
|
||
|
|
||
|
/* Updates numberOfSelectedItems as well as the message presented by the text at the top of the activity
|
||
|
*/
|
||
|
void selectItem(MetricSelector metricSelector, boolean isSelected, boolean playVideo) {
|
||
|
//update numberOfSelectedItems
|
||
|
boolean wasSelected = metricSelector.getIsSelected();
|
||
|
if (!wasSelected && isSelected) {
|
||
|
numberOfSelectedItems += 1;
|
||
|
|
||
|
if (playVideo) {
|
||
|
fragmentMediaPlayer.startMetricSelectorPlayback(metricSelector);
|
||
|
}
|
||
|
|
||
|
} else if (wasSelected && !isSelected) {
|
||
|
numberOfSelectedItems -= 1;
|
||
|
if (playVideo) {
|
||
|
fragmentMediaPlayer.stopMetricSelectorPlayback(metricSelector);
|
||
|
}
|
||
|
}
|
||
|
metricSelector.setIsSelected(isSelected);
|
||
|
|
||
|
//Create and display message at the top
|
||
|
/*String dMetricsChosen;
|
||
|
if (numberOfSelectedItems == 1) {
|
||
|
dMetricsChosen = "1 metric chosen.";
|
||
|
} else {
|
||
|
dMetricsChosen = String.format("%d metrics chosen.",numberOfSelectedItems);
|
||
|
}*/
|
||
|
|
||
|
if (numberOfSelectedItems == 1) {
|
||
|
metricChooserTextView.setText("1 metric chosen.");
|
||
|
} else {
|
||
|
metricChooserTextView.setText(String.format("%d metrics chosen.",numberOfSelectedItems));
|
||
|
}
|
||
|
|
||
|
if (numberOfSelectedItems <= NUM_METRICS_DISPLAYED) {
|
||
|
metricChooserTextView.setTextColor(messageAtOrUnderLimitColor);
|
||
|
} else {
|
||
|
metricChooserTextView.setTextColor(messageOverLimitColor);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*if (numberOfSelectedItems < NUM_METRICS_DISPLAYED) {
|
||
|
metricChooserTextView.setTextColor(messageAtOrUnderLimitColor);
|
||
|
metricChooserTextView.setText(String.format("%s Choose %d more.", dMetricsChosen, NUM_METRICS_DISPLAYED - numberOfSelectedItems));
|
||
|
} else if (numberOfSelectedItems == NUM_METRICS_DISPLAYED) {
|
||
|
metricChooserTextView.setTextColor(messageAtOrUnderLimitColor);
|
||
|
metricChooserTextView.setText(dMetricsChosen);
|
||
|
} else {
|
||
|
metricChooserTextView.setTextColor(messageOverLimitColor);
|
||
|
metricChooserTextView.setText(String.format("%s Please de-select %d.", dMetricsChosen, numberOfSelectedItems - NUM_METRICS_DISPLAYED));
|
||
|
}*/
|
||
|
|
||
|
}
|
||
|
|
||
|
void clearItems() {
|
||
|
for (MetricsManager.Metrics metric : MetricsManager.getAllMetrics()) {
|
||
|
selectItem(metricSelectors.get(metric),false,true);
|
||
|
}
|
||
|
updateAllGridItems();
|
||
|
}
|
||
|
|
||
|
//loop through all grid items, and update those which are tagged with an Integer (those which represent selectable metrics).
|
||
|
void updateAllGridItems() {
|
||
|
for (MetricsManager.Metrics metric : MetricsManager.getAllMetrics()) {
|
||
|
metricSelectors.get(metric).setUnderOrOverLimit(numberOfSelectedItems <= NUM_METRICS_DISPLAYED);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onDestroy() {
|
||
|
super.onDestroy();
|
||
|
fragmentMediaPlayer.destroy();
|
||
|
}
|
||
|
|
||
|
//IntRef represents a reference to a mutable integer value
|
||
|
//It is used to keep track of how many rows have been created in the populateGrid() method
|
||
|
class IntRef {
|
||
|
public int value;
|
||
|
public IntRef() {
|
||
|
value = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* The MetricSelector objects in this fragment will play a video when selected. To keep memory usage low, we use only one MediaPlayer
|
||
|
* object to control video playback. Video is rendered on a single TextureView.
|
||
|
* Chain of events that lead to video playback:
|
||
|
* -When a MetricSelector is clicked, the MediaPlayer.setDataSource() is called to set the video file
|
||
|
* -The TextureView is added to the view hierarchy of the MetricSelector, causing the onSurfaceTextureAvailable callback to fire
|
||
|
* -The TextureView is bound to the MediaPlayer through MediaPlayer.setSurface(), then MediaPlayer.prepareAsync() is called
|
||
|
* -Once preparation is complete, MediaPlayer.start() is called
|
||
|
* -MediaPlayer.stop() will be called when playback finishes or the item has been de-selected, at which point the TextureView will
|
||
|
* be removed from the MetricSelector's view hierarchy, causing onSurfaceTextureDestroyed(), where we call MediaPlayer.setSurface(null)
|
||
|
*/
|
||
|
class MetricSelectionFragmentMediaPlayer {
|
||
|
SafeMediaPlayer safePlayer;
|
||
|
TextureView textureView;
|
||
|
MetricSelector videoPlayingSelector;
|
||
|
|
||
|
public MetricSelectionFragmentMediaPlayer() {
|
||
|
safePlayer = new SafeMediaPlayer(getActivity());
|
||
|
safePlayer.setOnPreparedListener(new OnSafeMediaPlayerPreparedListener() {
|
||
|
@Override
|
||
|
public void onSafeMediaPlayerPrepared() {
|
||
|
safePlayer.start();
|
||
|
if (!greaterThanHoneyComb()) {
|
||
|
safePlayer.seekTo(1);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Although it is best to remove the MetricSelector video cover upon reception of the
|
||
|
* VIDEO_RENDERING event, this event is only available on SDK 17 and above.
|
||
|
*/
|
||
|
if (greaterThanHoneyComb()) {
|
||
|
safePlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
|
||
|
@Override
|
||
|
public boolean onInfo(MediaPlayer mp, int what, int extra) {
|
||
|
if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
|
||
|
videoPlayingSelector.removeCover();
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
});
|
||
|
} else {
|
||
|
safePlayer.setOnSeekListener(new MediaPlayer.OnSeekCompleteListener() {
|
||
|
@Override
|
||
|
public void onSeekComplete(MediaPlayer mp) {
|
||
|
videoPlayingSelector.removeCover();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
safePlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
|
||
|
@Override
|
||
|
public void onCompletion(MediaPlayer mp) {
|
||
|
Uri nextVideoUri = videoPlayingSelector.getNextVideoResourceURI();
|
||
|
if (nextVideoUri == null) {
|
||
|
endVideoPlayback();
|
||
|
} else {
|
||
|
safePlayer.stopAndReset();
|
||
|
safePlayer.setDataSource(nextVideoUri);
|
||
|
safePlayer.prepareAsync();
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
|
||
|
textureView = new TextureView(getActivity());
|
||
|
textureView.setVisibility(View.GONE);
|
||
|
textureView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||
|
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
|
||
|
@Override
|
||
|
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
|
||
|
safePlayer.setSurface(new Surface(surface));
|
||
|
safePlayer.prepareAsync();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
|
||
|
safePlayer.stopAndReset();
|
||
|
safePlayer.setSurface(null);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
private void startVideoPlayback(MetricSelector metricSelector) {
|
||
|
videoPlayingSelector = metricSelector;
|
||
|
videoPlayingSelector.initIndex();
|
||
|
safePlayer.setDataSource(metricSelector.getNextVideoResourceURI());
|
||
|
metricSelector.displayVideo(textureView); //will cause onSurfaceTextureAvailable to fire
|
||
|
}
|
||
|
|
||
|
private void endVideoPlayback() {
|
||
|
videoPlayingSelector.displayCover();
|
||
|
safePlayer.stopAndReset();
|
||
|
videoPlayingSelector.removeVideo(); //will cause onSurfaceTextureDestroyed() to fire
|
||
|
}
|
||
|
|
||
|
void startMetricSelectorPlayback(MetricSelector metricSelector) {
|
||
|
if (videoPlayingSelector != null) {
|
||
|
endVideoPlayback(); //stop previous video
|
||
|
}
|
||
|
startVideoPlayback(metricSelector);
|
||
|
}
|
||
|
|
||
|
void stopMetricSelectorPlayback(MetricSelector metricSelector) {
|
||
|
if (metricSelector == videoPlayingSelector) { //if de-selected item is a playing video, stop it
|
||
|
endVideoPlayback();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void destroy() {
|
||
|
safePlayer.release(); //release resources of media player
|
||
|
textureView = null;
|
||
|
}
|
||
|
|
||
|
boolean greaterThanHoneyComb() {
|
||
|
return Build.VERSION.SDK_INT >= 17;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* These are not all the MediaPlayer states defined by Android, but they are all the ones we are interested in.
|
||
|
* Note that SafeMediaPlayer never stays in the STOPPED state, so we don't include it.
|
||
|
*/
|
||
|
enum MediaPlayerState {
|
||
|
IDLE, INIT, PREPARED, PLAYING
|
||
|
};
|
||
|
|
||
|
interface OnSafeMediaPlayerPreparedListener {
|
||
|
void onSafeMediaPlayerPrepared();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A Facade to ensure our MediaPlayer does not throw an error due to an invalid state change.
|
||
|
*/
|
||
|
class SafeMediaPlayer {
|
||
|
MediaPlayerState state;
|
||
|
MediaPlayer mediaPlayer;
|
||
|
Activity activity;
|
||
|
OnSafeMediaPlayerPreparedListener listener = null;
|
||
|
|
||
|
public SafeMediaPlayer(Activity activity) {
|
||
|
mediaPlayer = new MediaPlayer();
|
||
|
state = MediaPlayerState.IDLE;
|
||
|
this.activity = activity;
|
||
|
|
||
|
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
|
||
|
@Override
|
||
|
public void onPrepared(MediaPlayer mp) {
|
||
|
state = MediaPlayerState.PREPARED;
|
||
|
if (listener != null) {
|
||
|
listener.onSafeMediaPlayerPrepared();
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void setDataSource(Uri source) {
|
||
|
if (state == MediaPlayerState.IDLE) {
|
||
|
try {
|
||
|
mediaPlayer.setDataSource(activity, source);
|
||
|
} catch (Exception e) {
|
||
|
Log.e(LOG_TAG, e.getMessage());
|
||
|
return; //If unable to setup video, exit function
|
||
|
}
|
||
|
state = MediaPlayerState.INIT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void prepareAsync() {
|
||
|
if (state == MediaPlayerState.INIT) {
|
||
|
mediaPlayer.prepareAsync();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void start() {
|
||
|
if (state == MediaPlayerState.PREPARED) {
|
||
|
mediaPlayer.start();
|
||
|
state = MediaPlayerState.PLAYING;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void seekTo(int msec) {
|
||
|
if (state == MediaPlayerState.PREPARED || state == MediaPlayerState.PLAYING) {
|
||
|
mediaPlayer.seekTo(msec);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void stopAndReset() {
|
||
|
if (state == MediaPlayerState.PLAYING || state == MediaPlayerState.PREPARED) {
|
||
|
mediaPlayer.stop();
|
||
|
}
|
||
|
mediaPlayer.reset(); //can be called from any state
|
||
|
state = MediaPlayerState.IDLE;
|
||
|
}
|
||
|
|
||
|
void setOnPreparedListener(OnSafeMediaPlayerPreparedListener listener) {
|
||
|
this.listener = listener;
|
||
|
}
|
||
|
|
||
|
//The rest of the methods are delegation methods
|
||
|
void setSurface(Surface surface) {
|
||
|
mediaPlayer.setSurface(surface);
|
||
|
}
|
||
|
|
||
|
void setOnCompletionListener(MediaPlayer.OnCompletionListener listener) {
|
||
|
mediaPlayer.setOnCompletionListener(listener);
|
||
|
}
|
||
|
|
||
|
void setOnSeekListener(MediaPlayer.OnSeekCompleteListener listener) {
|
||
|
mediaPlayer.setOnSeekCompleteListener(listener);
|
||
|
}
|
||
|
|
||
|
void setOnInfoListener(MediaPlayer.OnInfoListener listener) {
|
||
|
mediaPlayer.setOnInfoListener(listener);
|
||
|
}
|
||
|
|
||
|
void release() {
|
||
|
mediaPlayer.release();
|
||
|
mediaPlayer = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|